본문 바로가기

프로그래밍(TA, AA)/C C++

[시스템프로그래밍] 시스템 정보

유닉스 시스템과 관련된 기본 정보와 사용자 정보 검색, 시스템의 시간을 관리하는 함수를 다룹니다. 유닉스 시스템의 기본 환경 정보를 검색할 수 있는 함수들은 기본 환경과 관련된 구조체나 상수를 사용해 정보를 검색합니다. 주요 대상 정보는 하드웨어와 운영체제의 종류 관련 정보와 메모리 페이지의 크기나 최대 패스워드 길이 등 시스템 환경 설정에 관한 정보입니다. 아래 표에서 시스템 정보 검색 관련 함수를 소개합니다. 이중 sysinfo 함수를 사용하면 정보 검색 뿐만 아니라 호스트명 같은 일부 환경 정보를 설정할 수도 있습니다. sysconf 함수를 사용하면 시스템 자원 관련 정보를 검색할 수 있고, pathconf와 fpathconf 함수를 사용하면 파일과 디렉토리 관련 자원 정보를 검색할 수 있습니다.


 기능

 함수원형

 시스템 기본 정보 검색

 int uname(struct utsname *name);

 시스템 정보 검색과 설정

 long sysinfo(int command, char *buf, long count);

 시스템 자원 정보 검색

 long sysconf(int name);

 파일과 디렉토리 관련 자원 검색

 long pathconf(const char *path, int name);

 long fpathconf(int fildes, int name);


유닉스 시스템의 사용자 관련 정보로는 사용자 계정 정보, 그룹 정보, 로그인 기록 정보가 있습니다. 사용자 계정 검색 관련 함수는 로그인명, UID, 패스워드 정보 같은 개별적인 정보 검색을 수행하는 함수와 패스워드 파일(/etc/passwd)이나 섀도우 파일(/etc/shadow)에서 사용자 정보를 읽어오는 함수로 구성되어 있습니다. 그룹 정보 관련 함수도 GID 검색과 그룹 파일(/etc/group)에서 그룹 정보를 읽어오는 함수로 구성되어 있습니다. 로그인 기록 정보 관련 함수는 /var/adm/utmpx 파일에서 로그인 기록을 읽어옵니다.


 기능

 함수원형

 로그인명 검색

 char *getlogin(void);
 char *cuserid(char *s);

 UID 검색

 uid_t getuid(void);
 uid_t geteuid(void);

 패스워드 검색

 struct passwd *getpwuid(uid_t uid);
 struct passwd *getpwnam(const char *name);

 패스워드 파일 검색

 struct passwd *getpwent(void);

 void setpwent(void);

 void endpwent(void);

 struct passwd *fgetpwent(FILE *fp);

 섀도우 정보 검색

 struct spwd *getspnam(const char *name);

 섀도우 파일 검색

 struct spwd *getspent(void);
 void setspent(void);
 void endspent(void);

 struct spwd *fgetspent(FILE *fp);

 그룹 정보 검색

 gid_t getgid(void);
 gid_t getegid(void);

 그룹 파일 검색

 struct group *getgrnam(const char *name);

 struct group *getgrgid(gid_t gid);

 struct group *getgrent(void);

 void setgrent(void);

 void endgrent(void);

 struct group *fgetgrent(FILE *fp);

 로그인 기록 검색

 struct utmpx *getutxent(void);

 void setutxent(void);

 void endutxent(void);

 int utmpxname(const char *file);


유닉스 시스템에서 파일이나 디렉토리의 생성, 사용자 정보 변경 등 다양한 부분에서 시간 정보가 필요합니다. 유닉스에서는 시간 정보 관련 함수로 현재 시간 정보를 검색하는 함수, 시간대를 설정하는 함수, 시간 정보를 분해해 구조체로 리턴하는 함수, 시간 정보의 출력 형식을 지정할 수 있는 함수를 제공합니다.


 기능

 함수원형

 현재 시간 정보 검색

 time_t time(time_t *tloc);
 int gettimeofday(struct timeval *tp, void *tzp);

 시간대 설정

 void tzset(void);

 시간 정보 분해

 struct tm *localtime(const time_t *clock);

 struct tm *gmtime(const time_t *clock);

 초 단위 시간 생성

 time_t mktime(struct tm *timeptr);

 형식 지정 시간 출력

 char *ctime(const time_t *clock);

 char *asctime(const struct tm *tm);

 size_t strftime(char *restrict s, size_t maxsize, const char *restrict format, const struct tm *restrict timeptr);



유닉스 시스템 관련 정보에는 시스템에 설치된 운영체제에 관한 정보, 호스트명 정보, 하드웨어 종류에 관한 정보가 있습니다. 유닉스 시스템은 하드웨어에 따라 사용할 수 있는 자원의 최댓값을 설정해놓았는데, 함수를 사용해 자원값을 검색하거나 수정할 수 있습니다. 이러한 자원값으로는 최대 프로세스 개수, 프로세스 하나가 열 수 있는 최대 파일 개수, 메모리 페이지 크기 등이 있습니다.


유닉스 시스템에서는 시스템의 기본 환경을 검색하고 설정하는 데 사용하는 다양한 구조체와 매개변수를 제공합니다. 적절한 매개변수와 함수를 활용해 시스템의 기본 환경에 관한 정보를 검색하고, 커널의 설정을 변경하거나 시스템 자원을 적절하게 제한할 수 있습니다.



운영체제 기본 정보 검색


시스템에 설치된 운영체제의 이름과 버전, 호스트명, 하드웨어 종류 등을 검색하려면 uname 명령을 사용합니다. uname 명령에 -a 옵션을 지정하면 현재 시스템에 설치되어 있는 운영체제에 관한 정보가 출력됩니다.


 SunOS

 hanbit

 5.10

 Generic_118855-33

 i86pc

 i386

 i86pc

 운영체제명

 호스트명

 릴리즈 레벨

 버전 번호

 하드웨어 형식명

 CPU명

 플랫폼명


위의 결과를 보면 시스템은 인텔 PC고 솔라리스 10(SunOS 5.10) 운영체제가 설치되어 있으며, 호스트명이 hanbit임을 알 수 있습니다. uname 명령과 이름이 동일한 uname 함수가 있습니다. uname 함수를 사용하면 현재 시스템에 설치되어 있는 운영체제에 관한 정보를 검색할 수 있습니다.


운영체제 정보 검색: uname(2) 

#include <sys/utsname.h>

int uname(struct utsname *name);

uname 함수는 운영체제 정보를 검색해 utsname 구조체에 저장합니다. utsname 구조체는 <sys/utsname.h>에 다음과 같이 정의되어 있습니다.


struct utsname {
	char sysname[_SYS_NMLN];
	char nodename[_SYS_NMLN];
	char release[_SYS_NMLN];
	char version[_SYS_NMLN];
	char machine[_SYS_NMLN];
}

utsname 구조체의 각 항목은 문자형 배열이며, 각 값은 널 종료 문자열(null terminated string)로 저장됩니다. 항목을 구성하는 각 배열의 크기인 상수 _SYS_NMLN은 257로 정의되어 있습니다. 각 항목에 저장되는 값은 다음과 같습니다.


 항목

 설명

 sysname

 현재 운영체제의 이름을 저장한다.

 nodename

 네트워크를 통해 통신할 때 사용하는 시스템의 이름을 저장한다.

 release

 운영체제의 릴리즈 번호를 저장한다.

 version

 운영체제의 버전 번호를 저장한다.

 machine

 운영체제가 동작하는 하드웨어의 표준 이름(아키텍처)를 저장한다.


uname 함수는 성공하면 음수가 아닌 값을 리턴합니다. 만일 실패하면 -1을 리턴하고, 전역 변수 errno에 오류 코드를 지정합니다. uname 함수로 시스템의 운영체제 정보를 검색해 출력해보겠습니다.


#include <sys/utsname.h>
#include <stdlib.h>
#include <stdio.h>

int main(void) {
    struct utsname uts;

    if(uname(&uts) == -1) {
        perror("uname");
        exit(1);
    }

    printf("OSname : %s\n", uts.sysname);
    printf("Nodename : %s\n", uts.nodename);
    printf("Release : %s\n", uts.release);
    printf("Version : %s\n", uts.version);
    printf("Machine : %s\n", uts.machine);

    return 0;
}

[root@dev-web ch04]# ./ex4_1_exe

OSname : Linux

Nodename : dev-web

Release : 3.10.0-229.14.1.el7.x86_64

Version : #1 SMP Tue Aug 15 15:05:51 UTC 2015

Machine : x86_64




시스템 정보의 검색과 설정


SVR4에서는 uname과 비슷한 기능을 수행하는 시스템 호출인 sysinfo 함수를 제공합니다. sysinfo 함수는 uname 함수보다 다양한 정보를 검색할 수 있고, 시스템 정보를 설정할 수도 있습니다.


시스템 정보 검색과 설정: sysinfo(2)

#include <sys/systeminfo.h>

long sysinfo(int command, char *buf, long count);

sysinfo 함수는 command에 지정한 검색 또는 설정 명령에 따른 값을 buf에 저장합니다. 이때 buf의 길이는 count 값으로 설정하며, 최댓값은 257입니다. 첫 번째 인자인 command에 올 수 있는 값은 <sys/systeminfo.h> 파일에 상수로 정의되어 있습니다. 상수값은 크게 네 범주로 구분됩니다.


 상수 범위

 예약된 용도

 1 ~ 256

 - 유닉스 표준에서 정의한 함수

 - 정보를 검색(get)하는 데 사용하기 위해 예약된 번호

 257 ~ 512

 - 유닉스 표준에서 정의한 함수

 - 정보를 정의(set)하는 데 사용하기 위해 예약된 번호

 - 검색 번호 + 256으로 번호 할당

 513 ~ 768

 - 솔라리스에서 추가로 정의한 상수

 - 정보를 검색(get)하는데 사용하기 위해 예약된 번호

 769 ~ 1024

 - 솔라리스에서 추가로 정의한 상수

 - 정보를 정의(set)하는데 사용하기 위해 예약된 번호

 - 검색 번호 + 256으로 번호 할당


구분된 상수의 범위 중 현재 명령으로 지정되어 있는 상수값은 다음과 같습니다. 괄호 안의 숫자는 해당 상수값으로 정의된 숫자입니다.


 상수

 설명

 SI_SYSNAME(1)

 운영체제명을 리턴한다. uname 함수의 sysname 항목과 같은 값이다.

 SI_HOSTNAME(2)

 uname 함수의 nodename 항목과 같은 값으로, 현재 시스템의 호스트명을 리턴한다.

 SI_VERSION(4)

 uname 함수의 version 항목과 같은 값을 리턴한다.

 SI_MACHINE(5)

 하드웨어 형식 값을 리턴한다. uname 함수의 machine 항목과 같은 값이다.

 SI_ARCHITECTURE(6)

 하드웨어의 명령어 집합 아키텍처(ISA, Instruction Set Architecture) 정보를 리턴한다. 예를 들면, sparc, mc68030, i386 등이다.

 SI_HW_SERIAL(7)

 하드웨어 장비의 일련번호를 리턴한다. 기본적으로 이 일련번호는 중복되지 않는다. SI_HW_PROVIDER 값과 함께 사용하면 모든 SVR4 업체의 제품을 구별하는 유일한 번호가 된다.

 SI_HW_PROVIDER(8)

 하드웨어 제조사 정보를 리턴한다.

 SI_SRPC_DOMAIN(9)

 Secure RPC(Remote Procedure Call) 도메인명을 리턴한다.



 상수

 설명

 SI_SET_HOSTNAME(258)

 호스트명을 설정한다. 이 명령은 root 사용자만 사용할 수 있다.

 SI_SET_SPRC_DOMAIN(265)

 Secure RPC 도메인을 설정한다.



 상수

 설명

 SI_PLATFORM(513)

 하드웨어 플랫폼 모델명을 리턴한다. 예를 들면, 'SUNW, Sun_4_75', 'SUNW, SPARCsystem-600', 'i86pc' 등이다.

 SI_ISALIST(514)

 현재 시스템에서 실행 가능한 하드웨어 명령어 집합 아키텍처 정보의 목록을 리턴한다.

 SI_DHCP_CACHE(515)

 부팅 시 DHCP 서버에서 DHCPACK 응답으로 받은 값을 리턴한다.

 SI_ARCHITECTURE_32(516),
 SI_ARCHITECTURE_64(517),
 SI_ARCHITECTURE_K(518),
 SI_ARCHITECTURE_NATVIE(519)

 32비트 또는 64비트 하드웨어의 명령어 집합 아키텍처 정보를 리턴한다.


솔라리스에서 정의한 정보 설정용 상수값은 아직 없습니다. sysinfo 함수가 성공하면 결과값은 두 번째 인자에 저장됩니다. 결과값이 두 번째 인자의 길이보다 길면 'buf 길이 -1'만큼만 저장됩니다. sysinfo 함수는 오류가 발생하면 -1을 리턴합니다.



시스템 자원 정보 검색


유닉스 시스템에서는 하드웨어에 따라 사용할 수 있는 자원들의 최댓값을 설정해 놓았습니다. 제한된 값은 대부분 POSIX 표준에 따라 <limits.h>에 정의되어 있습니다. 프로그래밍할 때는 sysconf, fpathconf, pathconf 함수를 통해 현재 설정된 자원값을 검색할 수 있습니다.


시스템 자원 정보 검색: sysconf(3)

#include <unistd.h>

long sysinfo(int name);

sysconf 함수는 검색하려는 시스템 변수를 나타내는 상수를 인자로 받고 현재 설정되어 있는 시스템 자원값 또는 옵션값을 리턴합니다. 오류가 발생하면 -1을 리턴합니다.


sysconf 함수의 인자로 지정할 수 있는 상수는 <sys/unistd.h> 파일에 정의되어 있습니다. 주요 상수값은 다음과 같습니다. 다른 상수값에 관한 설명은 man sysconf 명령으로 확인할 수 있습니다. 



 상수

 설명

 _SC_ARG_MAX(1)

 argv[]와 envp[]를 합한 최대 크기로, 바이트 단위로 표시한다.

 _SC_CHILD_MAX(2)

 한 UID에 허용되는 최대 프로세스 개수를 나타낸다.

 _SC_CLK_TCK(3) 초당 클록 틱 수를 나타낸다.
 _SC_OPEN_MAX(5) 프로세스당 열 수 있는 최대 파일 개수를 나타낸다.
 _SC_VERSION(8)

 시스템이 지원하는 POSIX.1의 버전을 나타낸다.



 상수

 설명

 _SC_PASS_MAX(9)

 패스워드의 최대 길이를 나타낸다.

 _SC_LOGNAME_MAX(10)

 로그인명의 최대 길이를 나타낸다.

 _SC_PAGESIZE(11)

 시스템 메모리의 페이지 크기를 나타낸다.


 

 상수

 설명

 _SC_MEMLOCK(25)

 프로세스 메모리 잠금 기능을 제공하는지 여부를 나타낸다.

 _SC_MQ_OPEN_MAX(29)

 한 프로세스가 열 수 있는 최대 메시지 큐 개수를 나타낸다.

 _SC_SEMAPHORES(35)

 시스템에서 세마포어를 지원하는지 여부를 나타낸다.


 

 상수

 설명

 _SC_2_C_BIND(45)

 C 언어의 바인딩 옵션을 지원하는지 여부를 알려준다.

 _SC_2_C_VERSION(47)

 ISO POSIX-2 표준의 버전을 나타낸다.



파일과 디렉토리 관련 자원 검색 : fpathconf(3), pathconf(3)

#include <unistd.h>

long pathconf(const char *path, int name);
long fpathconf(int fildes, int name);

pathconf 함수는 path에 지정한 파일이나 디렉토리와 관련해 설정된 자원값이나 옵션값을 리턴합니다. name에는 검색할 정보를 나타내는 상수를 지정합니다. fpathconf 함수는 파일이나 경로 대신 열린 파일의 파일 기술자인 fildes를 이용해 검색합니다. 이들 함수는 성공하면 결과를 정수로 리턴합니다. 오류가 발생하면 -1을 리턴합니다.


pathconf 함수와 fpathconf 함수의 두 번째 인자로 지정할 수 있는 상수는 <sys/unistd.h> 파일에 정의되어 있습니다. 주요 상수값은 다음과 같습니다.

 

 상수

 설명

 _PC_LINK_MAX(1)

 디렉토리 혹은 파일 하나에 가능한 최대 링크 수를 나타낸다.

 _PC_NAME_MAX(4)

 파일명의 최대 길이를 바이트 크기로 나타낸다.

 _PC_PATH_MAX(5)

 경로명의 최대 길이를 바이트 크기로 나타낸다.


pathconf 함수로 현재 디렉토리의 최대 링크 수와 디렉토리명의 최대 길이, 경로의 최대 길이를 검색해보겠습니다.



유닉스 시스템에서 사용자 관련 정보로는 각 사용자에 관한 정보, 그룹에 관한 정보, 로그인 기록 정보가 있습니다. 이들 정보와 직접 관련이 있는 파일은 패스워드 파일(/etc/passwd)과 섀도우 파일(/etc/shadow), 그룹 파일(/etc/group), 로그인 기록 파일(/var/adm/utmpx)입니다.



로그인명과 UID 검색


사용자 관련 정보 중 가장 기본적인 것이 로그인명(login name)입니다. 로그인명마다 지정되는 사용자 ID 또한 기본적인 정보입니다. 사용자 계정을 등록할 때 로그인명과 사용자 ID(UID, UserID)가 지정됩니다. 유닉스 시스템에서는 현재 사용자의 로그인명과 UID를 검색할 수 있는 함수를 제공합니다.


로그인명 검색: getlongin(3)

#include <unistd.h>

char *getlongin(void);

getlogin 함수는 /var/adm/utmpx 파일을 검색해 현재 프로세스를 실행한 사용자의 로그인명을 찾아 리턴합니다(void는 인자로 아무것도 지정하지 않았음을 의미한다). 만약 이 프로세스를 실행한 사용자가 로그아웃했거나 rsh(remote shell) 등으로 원격에서 실행한 프로세스에서 getlogin 함수를 호출하면 사용자명을 찾지 못하고 널 포인터를 리턴하므로 주의해야 합니다. /var/adm/utmpx 파일의 구조는 '로그인 기록 검색'에서 살펴봅니다.



로그인명 검색: cuserid(3)

#include <stdio.h>

char *cuserid(char *s);

cuserid 함수는 현재 프로세스의 소유자 정보로 로그인명을 찾아 리턴합니다. 인자로 지정한 s가 널 포인터면 cuserid 함수 내부적으로 메모리를 할당해 로그인명을 저장하고 그 주소를 리턴합니다. 만일 사용자명을 찾지 못하면 널 포인터를 리턴합니다.



uid 검색 : getuid(2), geteuid(2)

#include <sys/types.h>
#include <unistd.h>

uid_t getuid(void);
uid_t geteuid(void);

getuid 함수는 현재 프로세스의 실제 사용자 ID를, geteuid 함수는 유효 사용자 ID를 리턴합니다. 두 함수 모두 인자를 받지 않습니다. 유닉스 시스템에서 프로세스 관련 UID는 두가지 입니다.

  • 실제 사용자 ID(RUID, Real User ID): 로그인할 때 사용한 로그인명에 대응하는 UID로 프로그램을 실행하는 사용자를 가리킨다.
  • 유효 사용자 ID(EUID, Effective User ID): 프로세스에 대한 접근 권한을 부여할 때 사용한다. 처음 로그인할 때는 실제 사용자 ID와 유효 사용자 ID가 같지만, setuid가 설정된 프로그램을 실행하거나 다른 사용자 ID로 변경할 경우 유효 사용자 ID는 달라진다.

getuid, geteuid 함수를 사용해 로그인명과 UID, EUID를 검색하고, getlogin, cuserid로 로그인명을 검색해 출력해보겠습니다.


#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int main(void) {
    uid_t uid, euid;
    char *name, *cname;

    uid = getuid();
    euid = geteuid();

    name = getlogin();
    //cname = cuserid(NULL);

    printf("Login Name=%s, UID=%d, EUID=%d\n", name, (int)uid, (int)euid);

    return 0;
}

[root@dev-web ch04]# ./ex4_6_exe

Login Name=root, UID=0, EUID=0


위 결과에서 UID와 EUID가 모두 0임을 알 수 있습니다. 사용자를 변경하지 않았으므로 UID와 EUID의 값이 같습니다. 만약 이 실행 파일에 setuid가 설정되어 있고, 일반 사용자가 실행한다면 결과는 달라질 것입니다. 



패스워드 파일 검색


/etc/passwd 파일에는 로그인명, UID, GID, 사용자의 홈 디렉토리, 로그인 쉘 등 사용자에 관한 기본적인 정보가 들어 있습니다. 이들 정보를 검색하려면 /etc/passwd 파일 관련 구조체와 함수를 알아야 합니다.


/etc/passwd 파일의 구조

/etc/passwd 파일은 사용자 정보를 각 행에 저장하며, 각 행에는 콜론(:)으로 구분된 정보가 있습니다. /etc/passwd 파일의 예를 보면 다음과 같습니다.


[root@dev-web ch04]# cat /etc/passwd

root:x:0:0:root:/root:/bin/bash

bin:x:1:1:bin:/bin:/sbin/nologin

.....

nginx:x:998:997:nginx user:/var/cache/nginx:/sbin/nologin

systemd-bus-proxy:x:997:995:systemd Bus Proxy:/:/sbin/nologin

systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin

dockerroot:x:996:993:Docker User:/var/lib/docker:/sbin/nologin

hbooks:x:100:1:Hanbit Books:/export/home/han:/bin/ksh


마지막 행에 위치한 hbooks 사용자 정보를 구체적으로 살펴보면 다음과 같습니다.


 hbooks

x

100

1

Hanbit Books

 /export/home/han

/bin/ksh

 로그인ID

패스워드

UID

GID

설명

홈 디렉토리

로그인 쉘


로그인 ID가 hbooks고, UID는 100입니다. hbooks 사용자가 속한 그룹의 ID는 1인데, /etc/group 파일을 보면 이는 other 그룹입니다. hbooks 사용자에 대한 설명을 보면 'Hanbit Books'를 위한 계정임을 알 수 있고, 홈 디렉토리는 /export/home/han 입니다. 로그인 쉘은 콘 쉘로 지정되어 있습니다.


passwd 구조체

/etc/passwd 파일의 정보를 읽어오려면 passwd 구조체를 사용해야 합니다. passwd 구조체는 pwd.h 파일에 정의되어 있으며, passwd 구조체의 각 항목은 /etc/passwd 파일의 내용에 대응합니다.

struct passwd {
    char *pw_name;
    char *pw_passwd;
    uid_t pw_uid;
    gid_t pw_gid;
    char *pw_age;
    char *pw_comment;
    char *pw_gecos;
    char *pw_dir;
    char *pw_shell;
};
  • pw_name: 로그인명을 저장한다.
  • pw_passwd: 암호를 저장한다. 그러나 최근 유닉스 시스템은 암호를 별도의 파일(/etc/shadow)에 저장하므로 의미없는 항목이다.
  • pw_uid: UID를 저장한다.
  • pw_gid: 기본 그룹 ID를 저장한다.
  • pw_age, pw_comment: 이 두항목은 사용되지 않는다. 패스워드 파일에도 이와 관련 있는 내용은 없으며, 단지 초기에 passwd 구조체의 항목으로 등록된 이후 계속 남아 있다.
  • pw_gecos: 사용자의 설명이나 기타 정보를 저장한다. 이 항목의 이름은 역사적인 이유에 기인한다.
  • pw_dir: 홈 디렉토리를 저장한다.
  • pw_shell: 로그인 쉘을 저장한다.


UID로 passwd 파일 읽기 : getpwuid(3)

#include <pwd.h>

struct psswd *getpwuid(uid_t uid);

getpwuid 함수는 /etc/passwd 파일에서 uid를 찾아서 passwd 구조체에 결과를 저장하고 주소를 리턴한다. 만약 uid에 해당하는 사용자를 찾지 못하면 널 포인터를 리턴합니다. 현재 프로세스를 실행한 사용자의 UID를 검색하고, 이 UID를 사용해 패스워드 파일에서 정보를 검색해 보겠습니다.


#include <unistd.h>
#include <pwd.h>

int main(void) {
    struct passwd *pw;

    pw = getpwuid(getuid());
    printf("UID : %d\n", (int)pw->pw_uid);
    printf("Login Name : %s\n", pw->pw_name);

    return 0;
}

[root@dev-web ch04]# ./ex4_7_exe

UID : 0

Login Name : root



이름으로 passwd 파일 읽기 : getpwnam(3)

#include <pwd.h>

struct passwd *getpwnam(const char *name);

getpwnam 함수는 로그인명을 받아 /etc/passwd 파일에서 사용자 정보를 검색합니다. 검색 결과를 passwd 구조체에 저장하고 주소를 리턴합니다. 만일 로그인명에 해당하는 사용자를 찾지 못하면 널 포인터를 리턴합니다.


로그인명이 testUser인 사용자의 정보를 패스워드 파일에서 검색해보겠습니다.


#include <pwd.h>

int main(void) {
    struct passwd *pw;

    pw = getpwnam("testUser");
    printf("UID : %d\n", (int)pw->pw_uid);
    printf("Home Directory : %s\n", pw->pw_dir);

    return 0;
}

[root@dev-web ch04]# ./ex4_8_exe

UID : 1001

Home Directory : /home/testUser



/etc/passwd 파일을 순차적으로 읽기 : getpwent(3), setpwent(3), endpwent(3), fgetwent(3)

getpwuid 함수와 getpwnam 함수는 인자로 지정한 특정 사용자의 정보를 읽어옵니다. /etc/passwd 파일을 순차적으로 읽어오려면 다른 함수를 사용해야 합니다.


#include <pwd.h>

struct passwd *getpwent(void);
void setpwent(void);
void endpwent(void);
struct passwd *fgetpwent(FILE *fp);

getpwent 함수는 /etc/passwd 파일에서 사용자 정보를 순차적으로 읽어옵니다. /etc/passwd 파일의 끝을 만나면 널 포인터를 리턴합니다. setpwent 함수는 /etc/passwd 파일의 오프셋을 파일의 처음에 놓습니다. endpwent 함수는 /etc/passwd 파일을 닫습니다. fgetpwent 함수는 인자로 파일 포인터를 받습니다. 즉, /etc/passwd 파일이 아닌 파일 포인터가 가리키는 파일에서 사용자 정보를 읽어옵니다. 따라서 파일 포인터가 가리키는 파일의 내부 구조는 /etc/passwd 파일과 동일해야 합니다.


#include <pwd.h>

int main(void) {
    struct passwd *pw;
    int n;

    for (n=0; n<3; n++) {
        pw = getpwent();
        printf("UID: %d, LoginName: %s\n", (int)pw->pw_uid, pw->pw_name);
    }

    return 0;
}

[root@dev-web ch04]# ./ex4_9_exe

UID: 0, LoginName: root

UID: 1, LoginName: bin

UID: 2, LoginName: daemon




섀도우 파일 검색


초기 유닉스 시스템은 사용자 계정의 패스워드 정보를 /etc/passwd 파일에 저장했습니다. 그러나 /etc/passwd 파일은 누구나 읽을 수 있기 때문에 보안 문제가 발생할 수 있어 현재 대부분의 유닉스 시스템은 사용자 패스워드를 /etc/shadow 파일에 별도로 저장하고 있습니다. /etc/passwd 파일처럼 /etc/shadow 파일을 검색할 때 사용하는 구조체와 함수가 제공됩니다. 물론 이 함수들은 root 사용자만 실행할 수 있습니다.



/etc/shadow 파일의 구조

/etc/shadow 파일은 사용자의 패스워드 정보를 각 행에 저장하며, 각 행에는 콜론(:)으로 구분된 정보가 있습니다. /etc/shadow 파일의 예를 보면 다음과 같습니다.


systemd-bus-proxy:!!:17314::::::

systemd-network:!!:17314::::::

dockerroot:!!:17314::::::

testUser:$6$RJbEUZq6$VyuAscqjJpEjh4KcgpE/p5r27k6E5xOqSI9Q3t8D7DTkQof7xKsDCFCCfAbv2e/.vCJ.7H9vOJzR4WMubbQI3/:17410:0:99999:7:::


각 항목에 관한 설명은 spwd 구조체의 항목 설명을 참조합니다.


spwd 구조체

/etc/shadow 파일은 사용자 패스워드 정보와 패스워드의 주기 정보를 저장합니다. 사용자 계정별로 한 행씩 저장하며, 각 행에는 콜론(:)으로 구분된 정보가 있습니다. 이를 읽어오려면 spwd 구조체를 사용해야 합니다. spwd 구조체는 <shadow.h> 파일에 있습니다.


struct spwd {
    char *sp_namp;  // 로그인명을 저장한다.
    char *sp_pwdp;  // 사용자 계정의 패스워드를 13개 문자로 암호화해 저장
    int sp_lstchg;  // 패스워드를 변경한 날짜 정보. 1970년 1월 1일부터 일 수로 계산해 저장.
    int sp_min;     // 변경된 패스워드를 사용해야 하는 최소 일 수
    int sp_max;     // 현재 패스워드를 사용할 수 있는 최대 일 수
    int sp_warn;    // 패스워드를 변경할 날이 되기 전에 경고를 시작하는 일수
    int sp_inact;   // 사용자 계정으로 로그인할 수 없는 일 수
    int sp_expire;  // 사용자 계정이 만료되는 날짜 정보. 1970년 1월 1일부터 일 수로 표시.
    int sp_flag;    // 현재는 미사용
}


/etc/shadow 파일 검색: getspnam(3)

#include <shadow.h>

struct spwd *getspnam(const char *name);

getspnam 함수는 인자로 지정한 사용자의 패스워드와 관련된 정보를 읽어옵니다. getspnam 함수로 testUser 사용자의 패스워드 정보를 검색해 보겠습니다.


#include <shadow.h>

int main(void) {
    struct spwd *spw;

    spw = getspnam("testUser");
    printf("Login Name : %s\n", spw->sp_namp);
    printf("Passwd : %s\n", spw->sp_pwdp);
    printf("Last Change : %d\n", spw->sp_lstchg);

    return 0;
}

[root@dev-web ch04]# ./ex4_10_exe

Login Name : testUser

Passwd : $6$RJbEUZq6$VyuAscqjJpEjh4KcgpE/p5r27k6E5xOqSI9Q3t8D7DTkQof7xKsDCFCCfAbv2e/.vCJ.7H9vOJzR4WMubbQI3/

Last Change : 17410



/etc/shadow 파일을 순차적으로 읽기: getspent(3), setspent(3), endspent(3), fgetspent(3)

#include <shadow.h>

struct spwd *getspent(void);
void setspent(void);
void endspent(void);
struct spwd *fgetspent(FILE *fp);

getspent 함수는 /etc/shadow 파일에서 패스워드 정보를 순차적으로 읽어옵니다. /etc/shadow 파일의 끝을 만나면 널 포인터를 리턴합니다. setspent 함수는 /etc/shadow 파일의 오프셋을 파일의 처음으로 위치시킵니다. endspent 함수는 /etc/shadow 파일을 닫습니다. fgetspent 함수는 /etc/shadow 파일이 아닌 파일 포인터로 지정한 다른 파일에서 패스워드 정보를 읽어옵니다. 당연히 이 파일의 구조는 /etc/shadow 파일과 동일해야 합니다.



그룹 정보 검색


유닉스 시스템에서 사용자는 하나 이상의 그룹에 속합니다. 그룹도 사용자와 마찬가지로 그룹명, 그룹 ID(GID, Group ID)가 있습니다. GID를 검색하는 함수로는 getgid와 getegid가 있습니다.


그룹 ID 검색하기: getgid(2), getegid(2)

#include <sys/types.h>
#include <unistd.h>

gid_t getgid(void);
gid_t getegid(void);

getgid 함수는 실제 그룹 ID를, getegid 함수는 유효 그룹 ID를 리턴합니다. UID와 마찬가지로 프로세스와 관련된 GID의 종류도 두가지입니다. 실제 그룹 ID는 로그인할 때 사용한 사용자 계정의 기본 그룹입니다. 유효 그룹 ID은 프로세스에 대한 접근 권한을 부여할 때 사용합니다.



그룹 파일 검색


유닉스에서는 그룹에 관한 정보를 /etc/group 파일에 별도로 저장합니다. 사용자가 속한 그룹 중 /etc/passwd 파일의 GID 항목에 지정된 그룹이 기본 그룹이며, 2차 그룹은 /etc/group 파일에서 지정합니다.


/etc/group 파일의 구조

/etc/passwd 파일처럼 그룹 정보는 /etc/group 파일에 한 행씩 저장하며, 각 행은 콜론(:)으로 항목을 구분합니다. /etc/group 파일의 예는 다음과 같습니다.

[root@dev-web ch04]# cat /etc/group

root:x:0:

bin:x:1:

daemon:x:2:

sys:x:3:

adm:x:4:

tty:x:5:

disk:x:6:



group 구조체

/etc/group 파일에 저장된 그룹 정보를 읽어오려면 group 구조체를 사용해야 합니다. group 구조체는 <grp.h> 파일에 정의되어 있습니다.

struct group {
    char *gr_name;
    char *gr_passwd;
    gid_t gr_gdi;
    char **gr_mem;
}

group 구조체의 각 항목은 /etc/group 파일의 내용에 대응합니다.

  • gr_name: 그룹명을 저장한다.
  • gr_passwd: 그룹 패스워드를 저장한다. 보통은 공백이다. 만일 그룹 패스워드를 지정하면 사용자 패스워드처럼 암호화된 문자가 저장된다. 그룹 패스워드를 설정하는 명령은 없으므로 사용자 패스워드 파일에서 복사해 삽입해야 한다. 만약 그룹 패스워드가 지정되어 있으면 사용자는 newgrp 명령을 사용해 다른 그룹으로 변경할 때 이 패스워드를 입력해야 한다.
  • gr_gid: 그룹 ID 번호를 저장한다.
  • gr_mem: 그룹의 멤버인 로그인명을 저장한다. 문자열을 가리키는 포인터다.

/etc/group 파일 검색 : getgrnam(3), getgrgid(3)


#include <grp.h>

struct group *getgrnam(const char *name);
struct group *getgrgid(gid_t gid);

getgrnam 함수는 검색하려는 그룹명을 읽어오고, getgrgid 함수는 검색하려는 그룹의 ID를 읽어옵니다. 검색한 그룹 정보는 group 구조체에 저장하고 주소를 리턴합니다.


getgrnam 함수를 사용해 adm 그룹의 정보를 검색해 보겠습니다.

#include <grp.h>
#include <stddef.h>

int main(void) {
    struct group *grp;
    int n;

    grp = getgrnam("adm");
    printf("Group Name : %s\n", grp->gr_name);
    printf("GID : %d\n", (int)grp->gr_gid);

    n = 0;
    printf("Members : ");
    while(grp->gr_mem[n] != NULL)
        printf("%s ", grp->gr_mem[n++]);
    printf("\n");

    return 0;
}


/etc/group 파일을 순차적으로 읽기 : getgrent(3), setgrent(3), endgrent(3), fgetgrent(3)

#include <grp.h>

struct group *getgrent(void);
void setgrent(void);
void endgrent(void);
struct group *fgetgrent(FILE *fp);

getgrent 함수는 /etc/group 파일에서 그룹 정보를 순차적으로 읽어옵니다. /etc/group 파일의 끝을 만나면 널 포인터를 리턴합니다. setgrent 함수는 /etc/group 파일의 오프셋을 파일의 처음으로 위치시킵니다. endgrent 함수는 /etc/group 파일을 닫습니다. fgetgrent 함수는 /etc/group 파일이 아닌 파일 포인터로 지정한 다른 파일에서 그룹 정보를 일겅옵니다. 물론 이 파일의 구조는 /etc/group 파일과 동일해야 합니다.



로그인 기록 검색


who 명령으로 현재 시스템에 로그인하고 있는 사용자에 관한 정보를 검색할 수 있습니다. last 명령으로는 시스템의 부팅 시각 정보나 사용자 로그인 기록 등을 검색할 수 있습니다. 이러한 정보는 /var/adm/utmpx와 /var/adm/wtmpx 파일에 저장됩니다. 솔라리스의 경우 버전 2.7까지는 utmpx, wtmpx 파일 외에 utmp, wtmp 파일에도 정보를 저장했지만, 2.8부터는 utmpx와 wtmpx 파일만 사용합니다. 이 두 파일은 바이너리 형태로 저장되기 때문에 vi 같은 텍스트 편집기로 내용을 확인할 수 없습니다. 이들 파일에서 정보를 읽어오려면 파일의 구조와 관련된 구조체와 함수가 필요합니다. 이들 파일에서 정보를 읽어오는 방법을 알아보겠습니다.


utmpx 구조체

utmpx와 wtmpx 파일은 구조가 동일합니다. 이 두 파일을 읽으려면 utmpx 구조체를 사용합니다. utmpx 구조체는 <utmpx.h>파일에 정의되어 있습니다.

struct utmpx {
    char    ut_user[32];
    char    ut_id[4];
    char    ut_line[32];
    pid_t   ut_pid;
    short   ut_type;
    struct  exit_status ut_exit;
    struct  timeval ut_tv;
    int     ut_session;
    int     pad[5];
    short   ut_syslen;

    char    ut_host[257];
};
  • ut_user: 사용자명을 지정한다.
  • ut_id: /etc/inittab 파일에서 읽어온 ID다.
  • ut_line: 사용자가 로그인한 장치명을 나타낸다.
  • ut_pid: 현재 실행 중인 프로세스의 ID를 나타낸다.
  • ut_type: 현재 읽어온 항목에 저장된 데이터의 형식을 나타낸다. ut_type에 올 수 있는 값은 <utmp.h>에 다음과 같이 상수로 정의되어 있다.
     - EMPTY(0) : 빈 항목이다.
     - RUN_LVL(1) : 시스템의 런레벨이 변경되었음을 나타낸다. 변경된 런레벨은 ut_id에 저장된다.
     - BOOT_TIME(2) : 시스템 부팅 정보를 나타낸다. 부팅 시각은 ut_time에 저장된다.
     - OLD_TIME(3) : date 명령으로 시스템 시간이 변경되었음을 나타낸다. 변경되기 전의 시간을 저장한다.
     - NEW_TIME(4) : date 명령으로 시스템 시간이 변경되었음을 나타낸다. 변경된 시간을 저장한다.
     - INIT_PROCESS(5) : int으로 생성한 프로세스임을 나타낸다. 프로세스명은 ut_name에 저장하고, 프로세스 ID는 ut_pid에 저장한다.
     - LOGIN_PROCESS(6) : 사용자가 로그인하기를 기다리는 getty 프로세스를 나타낸다.
     - USER_PROCESS(7) : 사용자 프로세스를 나타낸다.
     - DEAD_PROCESS(8) : 종료한 프로세스를 나타낸다.
     - ACCOUNTING(9) : 로그인 정보를 기록한 것임을 나타낸다.
     - DOWN_TIME(10) : 시스템을 다운시킨 시간을 나타낸다. ut_type이 담을 수 있는 가장 큰 값이다.
  • ut_exit: 정보의 정류가 DEAD_PROCESS인 경우 프로세스의 종료 상태를 저장한다. exit_status 구조체에 정보를 저장한다. exit_status 구조체는 <utmp.h>에 다음과 같이 정의되어 있다.
struct exit_status {
	short e_termination;	/* 프로세스의 종료 상태 저장 */
	short e_exit;		/* 프로세스의 exit 상태 저장 */
};
  • ut_tv : 해당 정보를 마지막으로 변경한 시각이다.
  • ut_session : 해당 정보의 세션 번호다.
  • pad : 추후 사용을 위해 예약해놓은 부분이다.
  • ut_syslen : ut_host의 길이다.
  • ut_host : 원격 접속한 경우 원격 호스트명을 저장한다.

/var/adm/utmpx 파일 순차적으로 읽기 : getutxent(3), setutxent(3), endutxent(3), utmpxname(3)
#include 

struct utmpx *getutxent(void);
void setutxent(void);
void endutxent(void);
int utmpxname(const char *file);

getutxent 함수는 /var/adm/utmpx 파일에서 로그인 정보를 순차적으로 읽어옵니다. /var/adm/utmpx 파일의 끝을 만나면 널 포인터를 리턴합니다. setutxent 함수는 /var/adm/utmpx 파일의 오프셋을 파일의 시작에 위치시킵니다. endutxent 함수는 /var/adm/utmpx 파일을 닫습니다. utmpxname 함수는 로그인 정보 파일을 file로 지정한 다른 파일로 변경합니다. 예를 들어, last 명령에서 사용하는 /var/adm/wtmpx 파일로 변경할 때 사용합니다.


getutxent 함수를 사용해 /var/adm/utmpx 파일을 읽고 로그인명과 터미널 정보를 출력해보겠습니다.

#include <sys/types.h>
#include <utmpx.h>
#include <stdio.h>

int main(void) {
    struct utmpx *utx;

    printf("LoginName Line\n");
    printf("--------------\n");

    while ((utx=getutxent()) != NULL) {
        if(utx->ut_type != USER_PROCESS)
            continue;

        printf("%s      %s\n", utx->ut_user, utx->ut_line);
    }

    return 0;
}


유닉스 시스템은 1970년 1월1일 0시0분0초(그리니치 표준시, UTC 시간대)부터 현재까지 경과한 시간을 초 단위로 저장하고, 이를 기준으로 시간 정보를 관리합니다. 이렇게 초단위로 저장된 시간을 그대로 활용하기도 하지만, 사람이 보기 편한 형태로 바꿔야 할 때도 있습니다. 유닉스는 시간을 관리하는 다양한 함수를 제공합니다. 여기서는 시간 정보 활용 관련 구조체와 함수를 배웁니다.


기본 시간 정보 확인


유닉스에서 현재 시간 정보를 구하는 기본적인 함수는 time입니다. BSD에서 도입한 gettimeofday 함수도 시간을 초 단위로 알려줍니다.


초 단위로 현재 시간 정보 얻기 : time(2)

#include <sys/types.h>
#include <time.h>

time_t time(time_t *tloc);

time 함수는 1970년1월1일 0시0분0초(UTC)부터 현재까지 경과된 시간을 초 단위로 알려줍니다. tloc가 널이 아니면 tloc가 가리키는 주소에 시간 정보를 저장하고, 널이면 시간 정보를 리턴합니다. time 함수는 실패하면 -1을 리턴합니다.


마이크로 초 단위로 시간 정보 얻기 : gettimeofday(3)

#include <sys/time.h>

int gettimeofday(struct timeval *tp, void *tzp);
int settimeofday(struct timeval *tp, void *tzp);


BSD 계열 유닉스에서 도입한 gettimeofday 함수는 시간 정보를 timeval 구조체에 저장해 리턴합니다. tzp는 시간대를 나타내는 값으로 무시합니다. timeval 구조체는 다음과 같이 초와 마이크로초 단위로 시간 정보를 알려줍니다.

struct timeval {
	time_t		tv_sec;		/* 초 */
	suseconds_t	tv_usec;	/* 마이크로초 */
};

만일 tp가 널이면 시간 정보를 읽어올 수 없습니다. gettimeofday 함수는 성공하면 0을, 실패하면 -1을 리턴합니다. settimeofday는 반대로 시간을 설정하는 함수입니다. 시간을 설정하려면 root 권한으로 실행해야 합니다.



시간대 정보


전세계 사람이 동일한 시간대를 사용할까요? 아닙니다. 지역의 시간은 각기 다릅니다. 영국의 그리니치 표준시를 기준으로 각 지역의 시차가 정해집니다. 우리나라는 UTC보다 9시간 빠른 시간을 사용합니다. SVR4 계열의 유닉스 기본 시간대 정보를 환경 변수 TZ에 설정합니다. 시스템의 기본 시간대 정보는 /etc/default/init 파일에 "TZ=ROK"와 같은 형태로 지정되어 있습니다. 유닉스에서는 현재 지역의 시간대를 설정할 수 있는 함수를 제공합니다.


시간대 설정 : tzset(3)

#include <time.h>

void tzset(void);

tzet 함수는 현재 지역의 시간대로 시간대를 설정합니다. 이 함수를 호출하면 전역 변수 4개에 정보가 설정됩니다.


extern time_t timezone, altzone;
extern int daylight;
extern char *tzname[2];
  • timezone: UTC와 지역 시간대와의 시차를 초 단위로 저장한다.
  • altzone: UTC와 일광 절약제(DST, Daylight Saving Time) 등으로 보정된 지역 시간대와의 시차를 초 단위로 저장한다.
  • daylight: 일광 절약제를 시행하고 0이 아니고, 그렇지 않으면 0이다.
  • tzname: 지역 시간대와 보정된 시간대명을 약어로 저장한다.

time의 보여주는 형식을 보여주는 함수는 이외에도 여러가지가 있지만, strftime 함수로 출력 형식을 지정하여 출력하는 예제를 작성하고, 이번 절을 마무리하겠습니다.

#include 
#include 

char *output[] = {
    "%x %X",
    "%G년, %m월 %d일 %U주 %H:%M",
    "%r"
};

int main(void) {
    struct tm *tm;
    int n;
    time_t t;
    char buf[257];

    time(&t);
    tm = localtime(&t);

    for(n = 0; n < 3; n++) {
        strftime(buf, sizeof(buf), output[n], tm);
        printf("%s = %s\n", output[n], buf);
    }

    return 0;
}

%x %X = 09/05/17 09:07:47

%G년, %m월 %d일 %U주 %H:%M = 2017년, 09월 05일 36주 09:07

%r = 09:07:47 AM