2017/10/18 - [프로그래밍/UNIX 고급 프로그래밍] - 제5장 표준 입출력 라이브러리
6.1 소개
유닉스 시스템의 통상적인 운영에는 여럭 가지 파일이 요구된다. 이런 자료 파일들에 대한 이식성 잇는 인터페이스가 이번 장의 주제이다. 또한 이번 장에서는 시스템 식별 함수들과 시간 및 날짜 함수들도 살펴본다.
6.2 패스워드 파일
POSIX.1에서는 사용자 데이터베이스라고 부르는 UNIX 시스템의 패스워드 파일에는 도해 6.1과 같은 필드들이 들어 있다. 이 필드들은 <pwd.h>에 정의된 passwd 구조체의 필드들에 대응된다.
도해 6.1 /etc/passwd 파일의 필드들
설명 |
해당 passwd 구조체 멤버 |
POSIX.1 |
FreeBSD 8.0 |
Linux 3.2.0 |
Mac OS X 10.6.8 |
Solaris 10 |
사용자 이름 |
char *pw_name |
O |
O |
O |
O |
O |
암호화된 패스워드 |
char *pw_passwd |
O |
O |
O |
O |
|
수치 사용자 ID |
uid_t pw_uid |
O |
O |
O |
O |
O |
수치 그룹 ID |
gid_t pw_gid |
O |
O |
O |
O |
O |
주석 필드 |
char *pw_gecos |
|
O |
O |
O |
O |
초기 작업 디렉터리 |
char *pw_dir |
O |
O |
O |
O |
O |
초기 셸 (사용자 프로그램) |
char *pw_shell |
O |
O |
O |
O |
O |
사용자 접근 부류 |
char *pw_class |
|
O |
|
O |
|
다음 번 패스워드 변경 일시 |
time_t pw_change |
|
O |
|
O |
|
계정 만료 일시 | time_t pw_expire |
| O |
| O |
POSIX.1은 passwd 구조체의 열 가지 필드들 중 다섯 개만 명시함을 주의. 대부분의 플랫폼은 적어도 일곱 개의 필드를 지원한다. BSD 파생 플랫폼들은 열 개 모두 지원한다.
예전부터 패스워드 파일은 /etc/passwd에 ASCII 파일 형식으로 저장되었다. 이 파일의 각 줄은 도해 6.1에 나온 필드들이 콜론으로 구되어 있는 형태이다.
- 일반적으로 패스워드 파일에는 사용자 이름이 root인 항목이 존재한다. 이 항목의 사용자ID는 0(슈퍼사용자)이다.
- 암호화된 패스워드 필드에는 그냥 문자 하나만 있다.
- 패스워드 파일의 일부 필드는 비어 있을 수 있다.
- 빈 주석 필드는 아무런 영향도 주지 않는다.
- 셀 필드에는 사용자의 로그인 셸로 쓰일 실행 가능한 프로그램의 이름이 저장된다. 셸필드가 비어있는 경우의 기본값은 일반적으로 /bin/sh이다.
- /dev/null을 이용하는 것 외에도 특정 사용자가 시스템에 로그인하지 못하게 하는 방법들이 있다. /bin/false는 아무 일도 하지 않고 그냥 성공적이지 못한 상태(0이 아닌값)를 돌려주면서 실행을 종료한다. 셸은 그 종료 상태를 false로 해석한다. /bin/true는 아무 일도 하지 않고 그냥 성공적인 상태(0)를 돌려주면서 실행을 종료한다. nologin이라는 명령은 커스텀화 가능한 오류 메시지를 출력하고, 0이 아닌 종료 상태와 함께 종료된다.
- nobody 사용자 이름은 누구나 이 시스템에 로그인할 수 있게 하는 용도로 쓰인다.
- 일부 시스템은 주석 필드에 추가적인 정보를 다아서 활용할 수 있게하는 finger(1)명령을 제공한다.
6.3 그림자 패스워드
암호화된 패스워드는 사용자 패스워드를 단방향 암호화 알고리즘으로 암호화한 것이다.
원천 자료(암호화된 패스워드)의 획득을 좀 더 어렵게 만들기 위해, 요즘 시스템들은 암호화된 패스워드를 개별적인 파일에 저장한다. 그런 파일을 흔히 그림자 패스워드 파일(shadow password file)이라고 부른다. 그림자 패스워드 파일은 적어도 사용자 이름과 암호화된 패스워드의 쌍을 담아야 하며, 패스워드에 관련된 그 외의 정보를 담는 것도 허용된다.
도해6.3 /etc/shadow 파일의 필드들
설명 |
struct 구조체의 멤버 |
사용자 로그인 이름 |
char *sp_namp |
암호화된 패스워드 |
char *sp_pwdp |
패스워드 최종 변경 이후의 일(day) 수 |
int sp_lstchg |
패스워드 변경 허용 시기까지의 일 수 |
int sp_min |
패스워드 필수 변경 시기까지의 일 수 |
int sp_max |
만료 경고 일 수 |
int sp_warn |
계정 비활성화까지의 일 수 |
int sp_inact |
계정 비활성화 이후의 일 수 |
int sp_expire |
추후 용도로 예약됨 |
unsigned int sp_flag |
필수적인 필드는 사용자의 로그인 이름과 암호화된 패스워드뿐이다.
Linux 3.2.0과 Solaris 10은 그림자 패스워드 파일의 접근을 위한 개별적인 함수들을 제공한다. 이들은 보통의 패스워드 파일에 접근하는 데 쓰이는 일단의 함수들과 비슷하다.
#includestruct spwd *getspnam(const char *name); struct spwd *getspent(void); // 반환값(둘 다): 성공 시 포인터, 오류 시 NULL void setspent(void); void endspent(void);
FreeBSD 8.0과 Mac OS X 10.6.8에는 그림자 패스워드 구조체가 존재하지 않는다. 추가적인 계정 정보는 패스워드 파일에 저장된다(도해 6.1 참고).
6.4 그룹 파일
POSIX.1에서 그룹 데이터베이스(group database)라고 부르는 UNIX 시스템의 그룹 파일에는 도해 6.4에 나온 필드들이 담겨 있다. 이 필드들은 <grp.h>에 정의된 group구조체의 멤버들에 대응된다.
도해6.4 /etc/group 파일의 필드들
설명 |
group 구조체 멤버 |
POSIX.1 |
FreeBSD 8.0 |
Linux 3.2.0 |
Mac OS X 10.6.8 |
Solaris 10 |
그룹 이름 |
char *gr_name |
O |
O |
O |
O |
O |
암호화된 패스워드 |
char *gr_passwd |
|
O |
O |
O |
O |
수치 그룹 ID |
int gr_gid |
O |
O |
O |
O |
O |
개별 사용자 이름을 가리키는 포인터들의 배열 |
char **gr_mem |
O |
O |
O |
O |
O |
gr_mem 필드는 이 그룹에 속한 사용자 이름들을 가리키는 포인터들의 배열이다. 이 배열은 널 포인터로 끝난다.
그룹 이름이나 수치 그룹ID로 특정 그룹에 대한 항목을 조회할 때에는 getgrgid, getgrnam 함수를 이용한다. 그룹 파일 전체를 검색할 때에는 이 두 함수 이외의 함수들이 필요하다.
#includestruct group * getgrent(void); // 반환값: 성공 시 포인터, 오류 또는 파일 끝 도달 시 NULL void setgrent(void); void endgrent(void);
6.5 추가 그룹ID
사용자는 패스워드 파일 항목의 그룹ID에 해당하는 그룹에 속할 뿐만 아니라, 최대 16개까지의 추가적인 그룹에도 속할 수 있게 되었다. 파일 접근 권한 점검 방식도, 파일의 그룹ID를 프로세스 유효 그룹ID와 비교할 뿐만 아니라 모든 추가 그룹ID와도 비교하도록 바뀌었다.
추가 그룹ID를 사용하면 사용자가 명시적으로 그룹을 변경할 필요가 없다는 장점이 생긴다. 한 사용자가 동시에 여러 개의 그룹에 속하는 것은 드물지 않은 일이다.
추가 그룹ID의 조회 및 설정을 위해 다음 세 함수가 제공된다.
#includeint getgroups(int gidsetsize, gid_t groupllist[]); // 반환값: 성공 시 추가 그룹ID들의 개수, 오류 시 -1 #include /* Linux에서 */ #include /* FreeBSD와 Mac OS X, Solaris에서 */ int setgroups(int ngroups, const gid_t grouplist[]); #include /* Linux에서 */ #include /* FreeBSD와 Mac OS X에서 */ int initgroups(int ngroups, const gid_t grouplist[]); // 반환값(둘 다): 성공 시 0, 오류 시 -1
일반적으로 setgroups함수는 initgroups함수에서 호출된다. initgroups함수는 그룹 파일 전체를 읽어 들여서(앞에 설명한 getgrent, setgrent, endgrent를 이용) username에 해당하는 사용자가 속한 그룹을 파악한다. 그런 다음에는 setgroups를 호출해서 그 사용자에 대한 추가 그룹ID 목록을 초기화한다. 이처럼 initgroups 함수는 setgroups를 호출하므로, initgroups 역시 수퍼사용자만 호출할 수 있다.
6.6 구현들의 차이점
도해6.5는 이 책에서 다루는 네 플랫폼의 사용자 정보와 그룹정보를 저장하는 방식을 정리한 것이다.
도해6.5 구현들의 계정 정보 저장 방식의 차이
정보 |
FreeBSD 8.0 |
Linux 3.2.0 |
Mac OS X 10.6.8 |
Solaris 10 |
계정정보 |
/etc/passwod |
/etc/passwd |
디렉터리 서비스 |
/etc/passwd |
암호화된 패스워드 |
/etc/master.passwd |
/etc/shadow |
디렉터리 서비스 |
/etc/shadow |
패스워드 해싱 여부 |
해싱함 |
안함 |
안함 |
안함 |
그룹 정보 |
/etc/group |
/etc/group |
디렉터리 서비스 |
/etc/group |
사용자 데이터베이스와 그룹 데이터베이스를 NIS(Network Information Service)를 이용해서 구현하고 있는 시스템들이 많이 있다. NIS를 이용한 구현에서는 관리자가 데이터베이스의 주 복사본을 편집하면 변경 사항이 조직 내의 모든 서버에 자동으로 배포되게 할 수 있다. 클라이언트 시스템들은 서버에 접속해서 사용자와 그룹에 대한 정보를 조회한다. NIS+와 LDAP(Lightweight Directory Access Protocol)도 그와 비슷한 기능성을 제공한다. 여러 시스템들은 각 종류의 정보를 관리하는 데 쓰이는 방법ㅇ을 구성 파일 /etc/nsswitch.conf를 통해서 제어한다.
6.7 기타 자료 파일
인터페이스에 대한 일반적인 원칙은, 각 자료 파일마다 적어도 다음과 같은 세 종류의 함수를 둔다는 것이다.
- 다음 레코드를 읽는 get 함수.
- 파일을 되감는 set 함수.
- 자료 파일을 닫는 end 함수.
설명 |
자료 파일 |
헤더 |
구조체 |
추가적인 키 참조 함수들 |
패스워드 |
/etc/passwd |
<pwd.h> |
passwd |
getpwnam, getpsuid |
그룹 |
/etc/group |
<grp.h> |
group |
getgrnam, getgrgid |
그림자 |
/etc/shadow |
<shadow.h> |
spwd |
getspnam |
호스트 |
/etc/hosts |
<netdb.h> |
hostent |
getnameinfo, getaddrinfo |
네트워크 |
/etc/networks |
<netdb.h> |
netent |
getnetbyname, getnetbyaddr |
프로토콜 | /etc/protocols | <netdb.h> | protoent | getprotobyname, getprotobynumber |
서비스 |
/etc/services |
<netdb.h> |
servent |
getservbyname, getservbyport |
6.8 로그인 계정 관리
대부분의 유닉스 시스템은 로그인 계정 관리와 관련해서 두 가지 자료 파일을 제공한다. 하나는 현재 로그인되어 있는 모든 사용자를 기록하는 utmp 파일이고, 또 하나는 모든 로그인 및 로그아웃 역사를 기록하는 wtmp 파일이다.
struct utmp { char ut_line[8]; /* tty 회선: "ttyh0", "ttyd0", "ttyp0*, ... */ char ut_name[8]; /* 로그인 이름 */ long ut_time; /* UNIX 기원 이후 흐른 초들의 개수 */ };
사용자가 로그인하면 login 프로그램은 이 구조체 하나를 채워서 utmp 파일에 기록하고, 같은 구조체를 wtmp 파일에 추가한다. 사용자가 로그아웃하면 init 프로세스가 utmp 파일의 해당 항목을 삭제하고(널 바이트로 채움) 새 항목을 wtmp 파일에 추가한다.
Solaris에서 이 레코드들의 구체적인 형식은 utmpx(4) 매뉴얼 페이지에 나온다. Solaris 10에서 두 파일은 /var/adm 디렉터리에 들어 있다.
FreeBSD 8.0과 Linux 3.2.0이 사용하는 로그인 레코드 형식들은 해당 utmp(5)매뉴얼 페이지에 나온다. 이 두 파일의 경로 이름은 /var/run/utmp와 /var/log/wtmp이다. Mac OS X 10.6.8에는 utmp 파일과 wtmp 파일이 없다.
6.9 시스템 식별
POSIX.1은 uname 함수가 현재 호스트 및 운영체제에 관한 정보를 돌려준다 정의한다.
역사적으로, BSD 기반 시스템들의 gethostname 함수는 오직 호스트의 이름만 돌려주었다. 이 이름은 일반적으로 TCP/IP네트워크의 한 호스트의 이름이다. 호스트 이름을 조회하거나 설정하는 hostname(1) 명령도 있다. (호스트 이름은 슈퍼사용자가 비슷한 함수 sethostname으로 설정한다.)
6.10 시간 및 날짜 함수들
UNIX 커널이 제공하는 기본적인 시간 서비스는 UNIX 기원(UTC 기준 1970년 1월 1일 0시 0분 0초)부터 흐른 초(second)들의 개수를 시간 값으로 사용한다. 시간과 관련해서, 예전부터 UNIX 시스템은 (a)지역 시간 대신 UTC(Coordinated Universal Time; 협정 세계시)를 사용한다는 점과 (b)일광절약시간 등의 변환을 자동으로 처리하고 (c)시간과 날짜를 하나의 수량으로 표현한다는 점에서 다른 운영체제들과 차별화된다.
일반적으로 UNIX 기원 이후 흐른 초의 개수에 해당하는 정수 값을 얻은 후에는 그 값을 어떤 함수에 넘겨주어 '분할된 시간'을 담은 구조체를 얻고, 그런 다음 그 구조체를 또 다른 함수를 이용해서 사람이 읽기 편한 시간 및 날짜로 변환한다. 도해 6.9에 여러 시간 함수들 사이의 관계가 나와있다. (이 그림의 함수들 중 파선 화살표로 표시된 세 함수, 즉 localtime과 mktime, strftime은 모두 환경변수 TZ에 영향을 받는다. 점선으로 표시된 것은 시간 관련 구조체에서 달력 시간을 던는 데 필요한 필드이다.)
도해6.9 여러 시간 함수들의 관계
두 함수 localtime과 gmtime은 달력 시간을 소위 '분할된 시간(broken-down time)'으로 변환한다. 분할된 시간은 다음과 같은 구조체 tm으로 표현된다.
struct tm { /* 분할된 시간 */ int tm_sec; /* 분 단위 미만 초: [0 - 60] */ int tm_min; /* 시 단위 미만 분: [0 - 59] */ int tm_hour; /* 자정 이후의 시: [0 - 23] */ int tm_mday; /* 한 달의 일: [1 - 31] */ int tm_mon; /* 1년 안의 달: [0 - 11] */ int tm_year; /* 1900 이후의 연도 */ int tm_wday; /* 요일 번호(일요일이 0): [0 - 6] */ int tm_yday; /* 1월 1일로부터의 일 수: [0 - 365] */ int tm_isdst; /* 일광절약시간 플래그: <0, 0, >0 */ };
localtime 함수와 gmtime 함수의 차이는, 전자는 달력 시간을 지역 시간대와 일광절약시간 플래그를 고려해서 지역 시간으로 변환하는 반면 후자는 달력 시간을 UTC 기준의 분할된 시간으로 변환한다는 것이다.
도해6.11 strftime 함수의 사용법을 보여주는 프로그램
#include#include #include int main(void) { time_t t; struct tm *tmp; char buf1[16]; char buf2[64]; time(&t); tmp = localtime(&t); if(strftime(buf1, 16, "time and date: %r, %a %b %d, %Y", tmp) == 0) { printf("buffer length 16 is too small\n"); } else { printf("%s\n", buf1); } if(strftime(buf2, 64, "time and date: %r, %a %b %d, %Y", tmp) == 0) { printf("buffer length 64 is too small\n"); } else { printf("%s\n", buf2); } exit(0); }
6.11 요약
이번 장에서는 대부분의 시스템들에서 패스워드 파일 및 그룹파일 이외의 여러 시스템 관련 자료 파일들에 접근하는 데 쓰이는 비슷한 함수들을 살펴보았다.
'프로그래밍 > UNIX 고급 프로그래밍' 카테고리의 다른 글
제9장 프로세스 관계 (0) | 2019.12.02 |
---|---|
제8장 프로세스 제어 (0) | 2019.11.18 |
제7장 프로세스 환경 (0) | 2017.11.02 |
제5장 표준 입출력 라이브러리 (0) | 2017.10.18 |
제4장 파일과 디렉터리 (0) | 2017.09.20 |
댓글