저장된 사용자 ID - Saved UID

Saved UID를 이해하기 위해서는 실제 사용자 ID(Real UID)와 유효 사용자 ID(Effective UID)에 대한 이해가 깔려있어야합니다. 아직 개념이 안잡혀있다면 아래의 포스팅을 먼저 보고 오시면 좋겠습니다. 

https://reakwon.tistory.com/228

 

[리눅스] 코드로 알아보는 uid(real uid, effective uid, saved uid) 관계

아래 포스팅보다 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요. https://reakwon.tistory.com/233 리눅스 프로그래밍 note 배포 티스토리에 리눅

reakwon.tistory.com

 

Saved UID가 없다고 생각하고 Real UID와 Effective UID만 있다고 가정 해보세요. 그리고 setresuid 역시 suid를 설정하는 함수이니 없다고 가정해보세요. 권한이 확장된 프로그램에서의 euid를 권한이 확장된 euid라고 부르겠습니다. 다른 사용자의 권한을 얻은 상태에서 다시 권한을 축소 시켜야할때, 그러니까 실사용자 ruid로 돌려야할 상황이 생길 때 단순히 ruid로만 돌리면 나중에 다시 유효 사용자 권한이 필요할 때 돌아올 방법이 없습니다. 그러니까 euid, ruid를 왔다리 갔다리 유도리있게 스위칭할수가 없다는 뜻입니다.

  • setuid를 통해서 권한이 확장된 euid로 다시 바꿀 수 있을까요? 현재 ruid=euid입니다. suid는 없다고 가정했으니, ruid로만 변경될 수 있는데, 이는 권한이 확장된 euid가 아니잖아요. setuid로는 바꿀 수 없습니다.
  • seteuid 역시 마찬가지인데요. ruid 혹은 suid로 돌아갈 수 있는데 지금은 suid가 없다고 가정했으니 역시 setuid의 결과와 같습니다. 바꿀 수 없죠.  
  • setreuid도 마찬가지입니다. ruid가 현재 euid이기 때문이죠. setreuid는 현재의 ruid 혹은 euid로만 변경되는데, 지금 상황은 ruid가 euid와 같죠.

즉, 저장된 사용자 ID가 없이 ruid와 euid만 존재하면 현재 ruid = euid가 생길 때 다시 확장된 권한의 euid로 돌아갈 방법이 없다는 뜻입니다. 그래서 따로 그러한 확장된 권한의 euid를 저장해 놓아야하는데, 이를 위한 uid가 바로 saved-user id입니다.

그래서 이전 지난 포스팅에 suid가 초기의 euid와 같다는 점을 주시하라고 한겁니다. 저의 큰 그림 아시겠어요? 이제 saved라는 의미가 왜 붙었는지 아시겠죠?

예를 들어, root 권한의 프로그램에서 잠시 root 권한을 뺄 때를 생각해봅시다. 위와 같은 상황은 발생하겠죠. 아무리 root가 프로그램 사용권한을 자신의 것으로 쓰게 끔 허락했어도 특정 파일에 대한 접근 권한을 막을 상황이 생길겁니다. 이런 경우 seteuid를 사용자의 ruid로 돌려줍니다. 현재 ruid = 실 사용자 id, 현재 euid = 실 사용자 id 그러다가 그 후 다시 root 권한으로 작업해야할 때는 seteuid를 다시 root로 돌려야하겠죠. 그런데 현재 상황에서 ruid는 실 사용자의 id, 그리고 euid도 실 사용자 id인데, 어떻게 돌릴 수 있죠? suid는 아까 봤듯이 euid와 같았죠. 네, suid 덕분에 다시 유효사용자 id를 root로 돌릴 수 있게 됩니다.  

아래의 코드는 root.txt라는 root만든 텍스트 파일을 euid를 변경해가며 읽는 소스 코드입니다. 실행하면서 어떤 현상이 발생하는지 관찰해보면 suid가 왜 쓰이는지 알 수 있을 겁니다.

//savedUID.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

void readfile(){
        int n;
        char buf[32]={0,};
        int fd = open("root.txt", O_RDWR);
        if(fd < 0){
                printf("open error(%s)\n", strerror(errno));
                return;
        }


        n = read(fd, buf, 32);
        if(n < 0){
                printf("read error(%s)\n", strerror(errno));
                close(fd);
                return;
        }
        printf("%s", buf);
        close(fd);


}
void pr_resuid(){
        int ruid, euid, suid;
        if(getresuid(&ruid, &euid, &suid) < 0){
                printf("setresuid error\n");
                exit(0);
        }
        printf("ruid:%d, euid:%d, suid:%d\n",
                ruid, euid, suid);
}
int main(){

        printf("초기 uid\n");
        pr_resuid();

        printf("file 읽기 시도 > ");
        readfile();
        printf("\n");

        printf("euid를 %d로 전환\n", getuid());
        if(seteuid(getuid()) < 0){
                printf("seteuid error\n");
                return 1;
        }
        pr_resuid();

        printf("file 읽기 시도 > ");
        readfile();
        printf("\n");

        printf("euid를 root로 전환\n");
        seteuid(0);

        printf("file 읽기 시도 > ");
        readfile();
        pr_resuid();
}

우선 root의 유효사용자 id를 root로 돌려야하기 때문에 권한을 줘야겠군요. gcc 경고에 대한 문구는 가볍게 쌩까도록 합시다. 아참, 그리고 root권한의 파일을 하나 만들어야겠네요. root.txt를 읽어야하잖아요.

root# gcc savedUID.c 
savedUID.c: In function ‘pr_resuid’:
savedUID.c:32:12: warning: implicit declaration of function ‘getresuid’; did you mean ‘setreuid’? [-Wimplicit-function-declaration]
   32 |         if(getresuid(&ruid, &euid, &suid) < 0){
      |            ^~~~~~~~~
      |            setreuid
root# chmod u+s a.out
root# echo "this is a file made by root" > root.txt

 

계정을 ubuntu로 전환하고 파일을 실행하면 어떤 결과가 나올까요?

root# su ubuntu
ubuntu$ ./a.out 
초기 uid
ruid:1000, euid:0, suid:0
file 읽기 시도 > this is a file made by root

euid를 1000로 전환
ruid:1000, euid:1000, suid:0
file 읽기 시도 > open error(Permission denied)

euid를 root로 전환
file 읽기 시도 > this is a file made by root
ruid:1000, euid:0, suid:0

 

euid를 잠시 ubuntu로 전환하고 파일을 읽을 땐 파일을 읽을 수 없습니다. 여기서 알 수 있는 것은 permission denied로 권한이 축소되었음을 알 수 있습니다. 그 후에는 euid를 root로 전환하여 파일을 읽을 수 있게 되었습니다. euid를 0으로 되돌릴 수 있는 이유는 suid가 0이기 때문이고 만약 suid 마저도 다른 uid로 변경되었다면 seteuid는 에러를 발생시킵니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

리눅스 시스템의 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

환경 변수

컴퓨터 시스템에서 사용되는 동적인 값을 저장하는 변수, 프로그램들이 시스템 설정과 상호작용하는 데 사용됩니다. 환경변수는 텍스트 형식으로 저장되며, 특정 이름과 그에 해당하는 값으로 구성됩니다.

이름 =  

시스템 전체에서 공유되거나 특정 프로세스 내에서만 유효한 변수들이 있을 수 있습니다. 환경변수는 주로 다음과 같은 용도로 사용됩니다.

  1. 시스템 설정 및 구성 정보 저장 시스템의 설정 정보를 저장하여 다른 프로그램들이 해당 정보를 활용할 수 있게 합니다. 예를 들어, 시스템의 언어, 시간대, 기본 디렉토리 등을 환경변수로 설정하여 시스템 전반에 적용할 수 있습니다. 
  2. 프로그램 실행 시 동작 옵션 설정 프로그램이 동작할 때 필요한 옵션을 환경변수로 설정하여 해당 프로그램이 이를 인식하고 적용하게 할 수 있습니다. 이를 통해 특정 프로그램의 동작을 조정하거나 사용자 맞춤 설정을 지원할 수 있습니다.
  3. 실행 환경 설정 프로그램이 실행되는 환경을 설정하는 데 사용됩니다. 예를 들어, 실행 파일들을 찾는 경로를 설정하거나 라이브러리의 위치를 지정하는데 환경변수를 활용할 수 있습니다.

 우리는 현재 쉘에서 환경 변수를 설정한적은 없죠. 그런데 로그인 할 때 기본적으로 설정되어 있는 환경 변수들이 있습니다(여기서는 bash 쉘만 보겠습니다.) printenv 명령(혹은 env)을 통해서 현재 쉘에 어떤 환경변수가 설정되어있는지 확인해볼까요?

# printenv
SHELL=/bin/bash
PWD=/root
LOGNAME=ubuntu
//...
HOME=/root
LANG=en_US.UTF-8
//...
USER=ubuntu
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
MAIL=/var/mail/root
SSH_TTY=/dev/pts/0

제가 있는 현재 bash 쉘의 환경변수는 이렇게 설정되어있습니다. 환경 변수도 변수이기 때문에 그 값을 변경하거나 없애거나 할 수 있습니다. 그런데 보통 기본적으로 설정되어있는 환경 변수는 건드리지 않는 편이 좋습니다. 예를 들어 PATH를 건드리게 되면 명령어 실행을 못할 수도 있습니다. 여기서는 알아두면 좋을 환경 변수들을 설명하고 환경 변수를 설정하는 방법과 프로그램에서 어떻게 환경 변수를 읽어오고 설정하는지 확인해보도록 하겠습니다.

1. 일반적인 환경 변수들

아래의 환경 변수들은 기본적으로 현재 쉘에 설정되어있을 수 있는 환경 변수들입니다.

환경변수  설명
USER 현재 로그인한 사용자입니다. UID(숫자)가 아닌 계정명으로 나타납니다.
HOME 현재 사용자의 홈 디렉토리입니다. 로그인하면 이 디렉토리로 자동으로 위치가 됩니다.
EDITER 사용할 기본 파일 편집기입니다. 터미널에 edit를 입력할 때 사용할 편집기입니다.
LANG 현재 로컬 언어 설정을 보여줍니다.
TERM 현재 터미널 에뮬레이션입니다.
MAIL 현재 사용자의 메일이 저장되는 위치입니다.
PATH  명령어를 실행할 때 검색할 디렉토리들입니다. 여러 디렉토리가 있을 수 있기 때문에 :(콜론)으로 구분이 됩니다.
여러분이 명령어를 아무데서나 칠 수 있는 이유는 바로 이 PATH변수 때문입니다. 이 변수가 없다면 ls 명령을 칠 때 전체 경로를 넣어서 /bin/ls를 쳐야하지만 PATH/bin 디렉토리가 있기 때문에 ls만 쳐서 명령을 칠 수 있는 것입니다.
SHELL 현재 사용자의 로그인 쉘입니다.
PWD 현재 사용자가 있는 디렉토리입니다

 

2.  환경 변수 설정

우선 일반적으로 쉘에서 단순 변수는 아래와 같이 설정할 수 있습니다.

변수명= 

그 변수를 참조할 때는 변수명 앞에 $를 사용하여 확인할 수 있습니다. 며칠전에 달라로 500만원을 환전했는데 개떡락을 하더군요 --;;

# VAL="hello, world"
# echo $VAL
hello, world

그런데 이건 이 쉘에서만 유효한 변수일 뿐이지 환경 변수는 아닙니다. printenvenv 명령을 통해서 현재 쉘에 설정된 환경 변수확인해보세요. 환경변수는 아니라는 것을 알 수 있습니다.

# printenv | grep VAL
아무 결과도 나오지 않음

대신에 현재 쉘 환경의 환경 변수를 포함한 모든 변수, 함수 등을 출력하는 set 명령을 통해 확인하면 변수가 설정되어있는 것은 알 수 있습니다.

# set | grep VAL
VAL='hello world'

그렇다면 환경 변수를 설정하려면 어떻게 해야 할까요?

 export 명령

export 명령, 일명 내보내기 명령을 통해서 현재 쉘의 환경 변수로 변수를 등록할 수 있습니다. 이때 변수명 앞 $는 생략해야합니다.

export 변수명

# VAL="hello world"
# export VAL
# printenv | grep VAL
VAL=hello world

혹은 한번에 값을 지정해서 내보낼 수도 있습니다.

export 변수명=

# export APPLE="Mac Book"
# printenv | grep APPLE
APPLE=Mac Book

이제 환경 변수로 등록되었다는 것을 알 수 있습니다.  

환경 변수나 변수를 해제하려면 unset 명령어를 통해서 해제할 수 있습니다.

unset 변수명

# printenv |grep VAL
VAL=hello world
# unset VAL
# printenv | grep VAL
# printenv | grep APPLE
APPLE=Mac Book
# unset APPLE
# printenv | grep APPLE

이런 환경 변수는 현재 수행되는 쉘이 유지되는 동안만 유효(사실 더 정확하게는 환경변수를 설정한 프로세스가 유지되는 동안 유효)합니다. 무슨 말이냐구요?

3. 환경변수의 특성

계속 주구장창 현재 쉘, 현재 쉘이라는 말을 썼는데요. 여러분들이 놓치실까봐 초록색으로 표시했습니다. 그림으로 보면 더욱 이해가 빠르실텐데요.  

처음 로그인 쉘인 (1) /bin/bash에서 export를 이용해서 HELLO를 환경 변수로 등록하고 sh로 새로운 쉘을 시작합니다. 이때 (2)/bin/sh로 실행되는 환경이 바뀌게 됩니다. 여기서도 export를 이용해서 WORLD를 환경 변수로 등록하고 다시 새로운 (3)bash 쉘을 실행시켜서 실행 환경을 바꿉니다. 여기서 BYE라는 환경 변수를 등록합니다.

그러면 맨 오른쪽 (3)bash 쉘을 사용하고 있을때 HELLO, WORLD, BYE라는 환경변수는 다 살아있을까요? , 살아있습니다. 맨처음 (1)/bin/bash, 그 다음 (2)/bin/sh도 실행중이니까요. 이제 exit으로 (3)bash를 끝내게 되어도 세 환경 변수 HELLO, WORLD, BYE는 살아있을까요? BYE(3)bashexit을 종료했기 때문에 더 이상 존재하지 않습니다. 그래서 HELLO, WORLD만 남아있게 됩니다. (2)/bin/sh를 끝냈을때, 이때 (1)/bin/bash만 수행되는 상태겠죠. 그러면 HELLO만 환경 변수로 유효하게 되는 겁니다.

아래는 테스트 결과입니다. 헷갈리지 않도록 앞에 프롬프트를 추가했습니다. 참고로 전 쉘에서 설정한 변수를 다음 쉘에서 이용하려면 export를 이용해 환경변수로 등록이 되어야 가능합니다. 그러니까 (1)/bin/bash에서 설정한 HELLO(2)/bin/shecho로 출력하려면 export되어있어야 된다는 겁니다.

(1)/bin/bash# export HELLO="hello!"
(1)/bin/bash# sh
(2)/bin/sh# export WORLD="world!"   
(2)/bin/sh# bash
(3)/bin/bash# export BYE="GOOD BYE~"
(3)/bin/bash# echo $HELLO $WORLD $BYE
hello! world! GOOD BYE~
(3)/bin/bash# exit
exit
(2)/bin/sh# echo $HELLO $WORLD $BYE
hello! world!
(2)/bin/sh# exit
(1)/bin/bash# echo $HELLO $WORLD $BYE
hello!

이런 특성을 모른다면 이런 실수 많이들 하실겁니다.  아래는 간단한 bash 쉘 스크립트를 작성한겁니다.

#!/bin/bash

#현재 프로세스 ID 출력 
echo $$

export HELLO="hello "
export WORLD="world!"
export BYE="good bye!"

echo $HELLO $WORLD $BYE

스크립트를 몰라도 맨위의 첫줄은 bash 스크립트를 알려주는 한줄이니까 그냥 그렇게 쓰는구나 묻지도 따지지지도 말고 흡수하시면 됩니다.

#!/bin/bash

그 다음 줄을 좀 눈여겨 봐야하는데, $$는 현재 프로세스의 ID를 의미합니다. 그래서 echo를 통해서 출력하는 것이구요. 그냥 간단히 현재 실행되는 프로그램의 ID라고 생각하세요.

#현재 프로세스 ID 출력 
echo $$

그리고 우리가 배운 export와 명령을 출력하는 echo가 다입니다.  위 스크립트의 의도는 $HELLO, $WORLD, $BYE를 환경 변수로 설정해서 편하게 쓰려는 의도입니다.  실행권한을 주고 실행하면 쉘 스크립트는 잘 실행되지만 스크립트가 끝난 이후 환경 변수를 불러오고자 한다면 이렇게 빈 공백만 출력이 됩니다.

# chmod 777 setmyenv.sh
# ./setmyenv.sh 
6968
hello world! good bye!
# echo $$
3196
# echo $HELLO

# echo $WORLD

# echo $BYE

그런데 제가 명령어를 수행하는 프로그램의 ID3196이고, , ./setmyenv.sh라는 쉘 스크립트가 실행되는 프로그램 ID6968로 다르네요. 결국 이런 상황입니다.

방금 전 언급한 그 상황이 발생했습니다. 그러면 새로운 실행을 낳지 않고, 현재 있는 쉘에서 그대로 스크립트를 수행하면 될 것 같습니다. 그런 목적을 달성할 명령이 바로 source 명령입니다.

 위의 스크립트를 source를 이용해서 실행해봅시다.

# source setmyenv.sh 
3196
hello world! good bye!
# echo $HELLO
hello
# echo $WORLD
world!
r# echo $BYE
good bye!
# echo $$
3196

보세요. 쉘 스크립트가 그대로 3196에서 실행이 되었죠? 그리고 환경 변수가 설정되어서 현재 쉘에서 사용할 수도 있습니다. 우리가 원하는 목적을 달성할 수 있었습니다. source는 쉘 스크립트를 현재 쉘에서 실행하게 해주는 명령어입니다.

4. 환경 변수 함수

이젠 리눅스에서 프로그램이 어떻게 환경 변수를 다룰 수 있는 지 볼까요? 환경 변수를 설정하는 함수는 setenv, putenv가 있으며 환경 변수를 해제하려면 unsetenv함수를 사용할 수 있습니다. 환경 변수의 어떤 값을 읽어올 경우에는 getenv 함수를 이용하면 됩니다.

4.1 setenv, unsetenv

#include <stdlib.h>
int setenv(const char *name, const char *value, int overwrite);
int unsetenv(const char *name);

setenv 함수는 name에 해당하는 value를 갖는 환경 변수를 설정해줍니다. overwrite1이라면 기존에 환경 변수값이 있을 때 현재 값으로 덮어쓰고, 0이라면 덮어쓰지 않습니다.

unsetenv 함수는 name에 해당하는 환경 변수를 해제합니다.

성공시 0, 실패시 -1을 반환합니다.

4.2 putenv

#include <stdlib.h>
int putenv(char *string);

putenv이름=형식으로 넘겨줌으로써 환경 변수를 설정할 수 있습니다. 이때 무조건 덮어쓰기가 되니 주의하세요.

성공시 0, 실패시 0이 아닌 값을 반환합니다.

4.3 getenv

#include <stdlib.h>
char *getenv(const char *name);

getenv함수는 name에 해당하는 환경변수를 읽어옵니다. 그래서 그 값을 반환하죠. 만약에 해당하는 환경 변수가 없다면 NULL을 반환합니다.

4.4 환경 변수 함수 이용의 예

네 함수(setenv, putenv, getenv unsetenv)를 다 사용하는 예제를 한번 볼까요.

//setenv.c
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]){

        char *name = NULL;
        char *value = NULL;
        char name_val_pair[128]={0,};
        if(argc != 3){
                printf("Usage %s NAME VALUE\n", argv[0]);
                return 1;
        }

        name = argv[1];
        value = argv[2];

        //overwrite 허용
        //setenv(name, value, 1);
        //overwrite 불가
        //setenv(name, value, 0);
        sprintf(name_val_pair, "%s=%s",name,value);
        putenv(name_val_pair);

        printf("%s=%s\n",name,getenv(name));

        unsetenv(name);
}

putenv를 이용해서 환경 변수를 설정해보았습니다. 만약 여러분이  setenv함수를 이용해서 덮어쓸지 말지를 결정할 수도 있습니다. 여기서는 putenv를 통해서 덮어쓰기가 가능한 환경 변수 설정을 하는 것으로 합시다. 다음은 환경 변수 USER를 바꾸는 실행화면입니다. 처음에는 ubuntu로 설정되어있고, 프로그램을 실행하니까 kali로 바뀌었습니다.

그런데 프로그램이 끝난 이후 USER를 확인해보니 ubuntu로 돌아왔네요. 왜 그렇죠!? 용(3 환경변수의 특성)을 잊지 않으셨죠?

# echo $USER
ubuntu
# ./a.out USER kali
USER=kali
# echo $USER
ubuntu

만약 USER가 바껴지는 것을 확인하고 싶다면 system함수로 다른 shell을 실행시켜셔 확인해보세요. system 함수는 다른 명령어를 실행하는 함수로 이해하시면 됩니다.

//setenv.c
//… 생략 … //

int main(int argc, char *argv[]){
	//… 생략 … //
	putenv(name_val_pair);

        printf("%s=%s\n",name,getenv(name));
        system("/bin/sh");

        unsetenv(name);
}
# ./a.out USER kali
USER=kali
# printenv | grep USER    sh쉘로 전환
USER=kali

 

로그인시 환경변수 자동 셋팅(.bashrc), 전체 사용자에 대한 환경 변수 설정(/etc/profile)에 대한 설정 방법은 아주 훌륭하신 블로거님들이 정리를 잘 해놓았으니, 여기서는 설명하지 않겠습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

리눅스의 전반적인 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

한계

리눅스에서 프로그램을 다른 리눅스에서 실행할 수 있습니다. 혹은 다른 유닉스 계열(BSD나 솔라리스, MAC OS X 등)의 시스템에서도 동작이 될 수가 있지요. 그런데 이렇게 시스템마다 지원하는 한계라는 것이 다 제 각각이거든요. 예를 들면 어떤 시스템에서는 한 프로세스 당 열 수 있는 파일 디스크립터의 갯수가 256개를 지원하는데, 다른 시스템에서는 1024개까지 지원할 수 있는 등 시스템마다 지원할 수 있는 한계가 존재합니다. 그렇다면 어떤 시스템에서 사용자가 생성할 수 있는 자식 프로세스의 수라던가, 최대 파일 경로의 이름이라던가,  로그인이 가능한 이름의 최대 길이라던가를 알아내면 그 프로그램이 효율적으로 동작할 수 있을 것 같은데요. 과연 어떻게 알아낼 수 있을까요?

한계는 세가지로 구분할 수가 있습니다. 

  • 컴파일 시점에서 한계(헤더 파일에 명시) : 이러한 한계들은 limits.h에 명시되어 있습니다. 시스템마다 불변하는 한계를 의미하게 됩니다. 예를 들어 POSIX를 준수하는 시스템에서는 int 자료형에서 지원가능한 값은 2,147,483,647이 적어도 만족이 되어야합니다. INT_MAX 상수로 확인이 가능합니다. 여기서는 설명하지 않겠습니다. 
  • 파일이나 디렉토리와 연관되지 않은 실행 시점의 한계(sysconf 함수) : 파일과 연관이 없는 시스템에서 실제 지원할 수가 있는 한계를 확인하려면 sysconf함수를 이용해서 확인할 수 있습니다. 함수의 원형을 볼까요?
#include <unistd.h>

long sysconf(int name);

   sysconf의 인자인 name을 전달하게 되면 그에 따른 값이 나옵니다. name은 앞에 _SC_로 시작하는 매크로 상수입니다. SC는 SysConf의 약자입니다. 아래의 소개한 매크로보다 더 다양한 name이 있습니다. 여기서는 요만큼만 설명합니다.

매크로 상수 설명
_SC_ARG_MAX exec 함수의 인수 길이 최대값
_SC_CHILD_MAX uid 당 동시에 실행할 수 있는 프로세스의 최대값
_SC_HOST_NAME_MAX hostname의 최대 값, gethostname으로 구할 수 있습니다
_SC_LOGIN_NAMX_MAX 로그인 이름의 최대 길이 
_SC_OPEN_MAX 프로세스가 열 수 있는 파일의 최대 갯수
_SC_PAGE_SIZE
_SC_PAGESIZE
페이지의 크기
_SC_STREAM_MAX 프로세스가 열 수 있는 파일 스트림의 최대 갯수
_SC_TTY_NAME_MAX 터미널 디바이스 이름의 최대 길이
_SC_TZNAME_MAX 타임 존 이름의 최대 길이 
_SC_LINE_MAX 유틸리티 프로그램에서 입력 줄로 받을 수 있는 최대 길이(stdin으로든 file으로 든)
_SC_SIGQUEUE_MAX 한 프로세스에 신호 큐에 담을 수 있는 신호의 최대 갯수
_SC_SEM_VALUE_MAX 세마포어의 최대값
_SC_SEM_NSEMS_MAX 한 프로세스가 동시에 사용할 수 있는 세마포어의 최대 개수
_SC_CLK_TCK 1초 클록 틱 개수

 

  • 파일이나 디렉토리와 연관된 실행 시점의 한계(pathconf 함수 혹은 fpathconf 함수) : 파일과 관련된 한계를 알아낼 때는 pathconffpathconf 함수를 사용할 수 있습니다. 예를 들어 어떤 터미널에 대한 한계를 알고 싶다면 /dev/ 하위의 터미널 파일을 입력으로 주어 확인할 수 있습니다. 
#include <unistd.h>

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

 이 둘의 동작은 같습니다. 단지 파일을 알려주는 첫 인자를 파일 디스크립터로 전달하느냐(fd), 파일의 경로와 이름을 사용하여 전달하느냐(path)에 따른 것만 다르죠. name은 확인하려는 한계의 이름을 넣어주면 됩니다. 앞에 _PC_로 시작합니다. Path Config의 약자겠죠? 

매크로 상수 설명
_PC_LINK_MAX 파일에 최대 링크 갯수를 의미하며, 만약 파일이라면 파일에 대한 최대 링크 갯수를 가져오고, 디렉토리를 지정하면 디렉토리의 링크 최대 갯수를 가져옵니다.
_PC_MAX_CANON 터미널의 서식화된 입력줄의 최대 길이로 파일을 반드시 터미널 파일을 지정해야합니다.
_PC_MAX_INPUT 터미널의 입력줄의 최대 길이로 반드시 터미널 파일을 지정해야합니다.
_PC_NAME_MAX 지정한 디렉토리에서 파일 입력의 최대 길이로 인자를 디렉토리로 주어야합니다.
_PC_PIPE_BUF 하나의 파이프에 원자적으로 쓸 수 있는 최대 바이트 수로 pipe나 fifo 타입의 파일, 혹은 디렉토리를 주어야합니다. 디렉토리를 주었을 때는 디렉토리 안에 생성된 임의의 fifo에 대한 한계를 가져옵니다.
_PC_PATH_MAX path나 fd가 현재 작업 디렉토리일 경우 상대 경로의 최대길이

 

위 세 함수(sysconf, pathconf, fpathconf)는 실패일 경우 -1이 반환되지만 1) 실제 지원하지 않는 name이라서 실패한 경우2)확정할 수 없는 한계에 의한 실패가 있습니다. 지원하지 않는 실패의 경우 errno가 EINVAL로 설정되구요. 확정할 수 없는 한계에 의한 실패는 errno가 변하지 않습니다.

한계를 가져오는 소스 코드

//print_conf.c
#include <stdio.h>
#include <unistd.h>
#include <limits.h>
#include <errno.h>

static void print_sysconf(char *str, int name){
        long val;
        errno = 0;
        printf("%s ", str);

        if((val = sysconf(name)) < 0){ 
                if(errno != 0){
                        if(errno == EINVAL)
                                printf(" (not supported)\n");
                        else printf("syconf error\n");
                }else
                        printf(" (no limit)\n");

        }else 
                printf(" %ld\n", val);
}

static void print_pathconf(char *str, char *path, int name){
        long val;
        printf("%s ", str);
        errno = 0;
        if((val = pathconf(path,name)) < 0){ 
                if(errno != 0){
                        if(errno == EINVAL)
                                printf(" (not supported)\n");
                        else printf("pathcon error(%s)\n", path);
                }else
                        printf(" (no limit)\n");
        }else
                printf(" %ld\n", val);

}
int main(int argc, char *argv[]){
        if(argc != 2){
                printf("Usage : %s <filename> \n", argv[0]);
                return 1;
        }


        printf("======== sysconf ==========\n");
#ifdef _SC_ARG_MAX
        print_sysconf("ARG_MAX :", _SC_ARG_MAX);
#endif
#ifdef _SC_CHILD_MAX
        print_sysconf("CHILD_MAX : ", _SC_CHILD_MAX);
#endif
#ifdef _SC_HOST_NAME_MAX
        print_sysconf("HOST_NAME_MAX : ", _SC_HOST_NAME_MAX);
#endif
#ifdef _SC_LOGIN_NAMX_MAX
        print_sysconf("LOGIN_NAMX_MAX : ", _SC_LOGIN_NAMX_MAX);
#endif
#ifdef _SC_OPEN_MAX
        print_sysconf("OPEN_MAX : ", _SC_OPEN_MAX);
#endif
#ifdef _SC_PAGESIZE
        print_sysconf("PAGESIZE : ", _SC_PAGESIZE);
#endif
#ifdef _SC_STREAM_MAX
        print_sysconf("STREAM_MAX : ", _SC_STREAM_MAX);
#endif
#ifdef _SC_TTY_NAME_MAX
        print_sysconf("TTY_NAME_MAX : ", _SC_TTY_NAME_MAX);
#endif
#ifdef _SC_TZNAME_MAX
        print_sysconf("TZNAME_MAX : ", _SC_TZNAME_MAX);
#endif
#ifdef _SC_LINE_MAX
        print_sysconf("LINE_MAX : ", _SC_LINE_MAX);
#endif
#ifdef _SC_SIGQUEUE_MAX
        print_sysconf("SIGQUEUE_MAX : ", _SC_SIGQUEUE_MAX);
#endif
#ifdef _SC_SEM_VALUE_MAX
        print_sysconf("SEM_VALUE_MAX : ", _SC_SEM_VALUE_MAX);
#endif
#ifdef _SC_SEM_NSEMS_MAX
        print_sysconf("SEM_NSEMS_MAX : ", _SC_SEM_NSEMS_MAX);
#endif
#ifdef _SC_CLK_TCK
        print_sysconf("CLK_TCK : ", _SC_CLK_TCK);
#endif

        printf("======== pathconf ==========\n");
#ifdef _PC_LINK_MAX
        print_pathconf("LINK_MAX : ", argv[1], _PC_LINK_MAX);
#endif
#ifdef _PC_MAX_CANON
        print_pathconf("MAX_CANON : ", argv[1], _PC_MAX_CANON);
#endif
#ifdef _PC_MAX_INPUT
        print_pathconf("MAX_INPUT : ", argv[1], _PC_MAX_INPUT);
#endif
#ifdef _PC_NAME_MAX
        print_pathconf("NAME_MAX : ", argv[1], _PC_NAME_MAX);
#endif
#ifdef _PC_PIPE_BUF
        print_pathconf("PIPE_BUF : ", argv[1], _PC_PIPE_BUF);
#endif
#ifdef _PC_PATH_MAX
        print_pathconf("PATH_MAX : ", argv[1], _PC_PATH_MAX);
#endif

}

 

위의 소스코드는 sysconf와 pathconf 함수를 사용해서 한계를 출력해주는 프로그램입니다. 다른 시스템에서는 정의되지 않은 name이 존재할 수 있으므로 #ifdef로 정의되어있는지 파악하여 사용합니다. 

실패할 경우에는 errno이 EINVAL이면 지원하지 않는 한계 이름입니다. 그런데 errno가 0으로 변하지 않았다면 불확정인 한계로 볼 수 있습니다. 

errno = 0;
//...
if((val = sysconf(name)) < 0){ 
    if(errno != 0){
            if(errno == EINVAL)
                    printf(" (not supported)\n");
            else printf("syconf error\n");
    }else
            printf(" (no limit)\n");

 

아래는 실행 결과입니다.

# ./a.out /etc
======== sysconf ==========
ARG_MAX :  2097152
CHILD_MAX :   15044
HOST_NAME_MAX :   64
OPEN_MAX :   1024
PAGESIZE :   4096
STREAM_MAX :   16
TTY_NAME_MAX :   32
TZNAME_MAX :   (no limit)
LINE_MAX :   2048
SIGQUEUE_MAX :   15044
SEM_VALUE_MAX :   2147483647
SEM_NSEMS_MAX :   (no limit)
CLK_TCK :   100
======== pathconf ==========
LINK_MAX :   65000
MAX_CANON :   255
MAX_INPUT :   255
NAME_MAX :   255
PIPE_BUF :   4096
PATH_MAX :   4096

 

반응형
블로그 이미지

REAKWON

와나진짜

,