Microsoft MVP성태의 닷넷 이야기
Linux: 11. 리눅스의 환경 변수 관련 함수 정리 - putenv, setenv, unsetenv [링크 복사], [링크+제목 복사],
조회: 14625
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 

(시리즈 글이 2개 있습니다.)
Linux: 6. getenv, setenv가 언어/운영체제마다 호환이 안 되는 문제
; https://www.sysnet.pe.kr/2/0/11846

Linux: 11. 리눅스의 환경 변수 관련 함수 정리 - putenv, setenv, unsetenv
; https://www.sysnet.pe.kr/2/0/11915




리눅스의 환경 변수 관련 함수 정리 - putenv, setenv, unsetenv

전에도 한 번 다룬 적이 있는데,

getenv, setenv가 언어/운영체제마다 호환이 안 되는 문제
; https://www.sysnet.pe.kr/2/0/11846

리눅스의 CRT 라이브러리는 운영체제로부터 받은 환경 변수 데이터를 나름의 해석으로 별도 보관을 합니다. 보관 방식은, environ 변수를 확인해 보면 되는데요,

#include <cstdio>

extern char** environ;

int main()
{
    char* ptr = (char *)environ;

    return 0;
}

만약 ptr 포인터 변수가 0x00007fffffffea88 위치를 가리킨다고 하면, 그 위치에는 다음과 같이 (64비트의 경우) 8바이트 포인터 테이블로 환경 변수 목록을 보관합니다.

0x00007FFFFFFFEA88  fb ec ff ff ff 7f 00 00
0x00007FFFFFFFEA90  06 ed ff ff ff 7f 00 00
0x00007FFFFFFFEA98  37 ed ff ff ff 7f 00 00
0x00007FFFFFFFEAA0  59 ed ff ff ff 7f 00 00
0x00007FFFFFFFEAA8  68 ed ff ff ff 7f 00 00
0x00007FFFFFFFEAB0  79 ed ff ff ff 7f 00 00
0x00007FFFFFFFEAB8  8d ed ff ff ff 7f 00 00
0x00007FFFFFFFEAC0  98 ed ff ff ff 7f 00 00
0x00007FFFFFFFEAC8  d3 ed ff ff ff 7f 00 00
0x00007FFFFFFFEAD0  dc ed ff ff ff 7f 00 00
0x00007FFFFFFFEAD8  ed ed ff ff ff 7f 00 00
0x00007FFFFFFFEAE0  0d ee ff ff ff 7f 00 00
0x00007FFFFFFFEAE8  4e ee ff ff ff 7f 00 00
0x00007FFFFFFFEAF0  61 ee ff ff ff 7f 00 00
0x00007FFFFFFFEAF8  6d ee ff ff ff 7f 00 00
0x00007FFFFFFFEB00  82 ee ff ff ff 7f 00 00
0x00007FFFFFFFEB08  92 ee ff ff ff 7f 00 00
0x00007FFFFFFFEB10  9c ee ff ff ff 7f 00 00
0x00007FFFFFFFEB18  a4 ee ff ff ff 7f 00 00
0x00007FFFFFFFEB20  b2 ee ff ff ff 7f 00 00
0x00007FFFFFFFEB28  e8 ee ff ff ff 7f 00 00
0x00007FFFFFFFEB30  07 ef ff ff ff 7f 00 00
0x00007FFFFFFFEB38  89 ef ff ff ff 7f 00 00
0x00007FFFFFFFEB40  00 00 00 00 00 00 00 00 // 마지막임을 가리키는 null 포인터

첫 번째 포인터가 가리키는 위치를 볼까요?

0x00007FFFFFFFEA88  fb ec ff ff ff 7f 00 00

즉 *((long long *)ptr + 0)의 값은 0x00007fffffffecfb이고, 이 위치를 메모리로 확인해 보면 다음과 같습니다.

[0x00007fffffffecfb]
0x00007FFFFFFFECFB  4c 53 5f 43 4f 4c 4f 52 53 3d 00 53 53 48 5f 43 4f 4e 4e 45 43 54 49 4f 4e  LS_COLORS=.SSH_CONNECTION
0x00007FFFFFFFED14  3d 31 39 32 2e 31 36 38 2e 30 2e 31 39 20 36 31 30 36 20 31 39 32 2e 31 36  =192.168.0.19 6106 192.16
0x00007FFFFFFFED2D  38 2e 30 2e 32 31 20 32 32 00 4c 45 53 53 43 4c 4f 53 45 3d 2f 75 73 72 2f  8.0.21 22.LESSCLOSE=/usr/
0x00007FFFFFFFED46  62 69 6e 2f 6c 65 73 73 70 69 70 65 20 25 73 20 25 73 00 5f 3d 2f 75 73 72  bin/lesspipe %s %s._=/usr
0x00007FFFFFFFED5F  2f 62 69 6e 2f 67 64 62 00 4c 41 4e 47 3d 6b 6f 5f 4b 52 2e 55 54 46 2d 38  /bin/gdb.LANG=ko_KR.UTF-8
0x00007FFFFFFFED78  00 58 44 47 5f 53 45 53 53 49 4f 4e 5f 49 44 3d 33 34 31 38 00 55 53 45 52  .XDG_SESSION_ID=3418.USER
0x00007FFFFFFFED91  3d 6b 65 76 69 6e 00 50 57 44 3d 2f 68 6f 6d 65 2f 6b 65 76 69 6e 2f 70 72  =kevin.PWD=/home/kevin/pr
0x00007FFFFFFFEDAA  6f 6a 65 63 74 73 2f 43 6f 6e 73 6f 6c 65 41 70 70 6c 69 63 61 74 69 6f 6e  ojects/ConsoleApplication
0x00007FFFFFFFEDC3  31 2f 62 69 6e 2f 78 36 34 2f 44 65 62 75 67 00 4c 49 4e 45 53 3d 34 38 00  1/bin/x64/Debug.LINES=48.
...[생략]...

보는 바와 같이 '\0'로 끝나는 "LSCOLORS=" 환경 변수를 가리킵니다. 이런 식으로 environ 테이블의 목록은 각각의 환경 변수의 위치를 담은 포인터 변수를 담고 있는 것입니다.

그렇다면 여기서, 기존에 등록되어 있던 LINES 환경 변수의 값을 putenv로 변경해 보겠습니다.

int result = putenv("LINES=45"); // result == 0

이렇게 하면, CRT가 구성해 놓은 환경 변수 문자열에는 아무런 변화가 없습니다.

0x00007FFFFFFFECFB  4c 53 5f 43 4f 4c 4f 52 53 3d 00 53 53 48 5f 43 4f 4e 4e 45 43 54 49 4f 4e  LS_COLORS=.SSH_CONNECTION
0x00007FFFFFFFED14  3d 31 39 32 2e 31 36 38 2e 30 2e 31 39 20 36 31 30 36 20 31 39 32 2e 31 36  =192.168.0.19 6106 192.16
0x00007FFFFFFFED2D  38 2e 30 2e 32 31 20 32 32 00 4c 45 53 53 43 4c 4f 53 45 3d 2f 75 73 72 2f  8.0.21 22.LESSCLOSE=/usr/
0x00007FFFFFFFED46  62 69 6e 2f 6c 65 73 73 70 69 70 65 20 25 73 20 25 73 00 5f 3d 2f 75 73 72  bin/lesspipe %s %s._=/usr
0x00007FFFFFFFED5F  2f 62 69 6e 2f 67 64 62 00 4c 41 4e 47 3d 6b 6f 5f 4b 52 2e 55 54 46 2d 38  /bin/gdb.LANG=ko_KR.UTF-8
0x00007FFFFFFFED78  00 58 44 47 5f 53 45 53 53 49 4f 4e 5f 49 44 3d 33 34 31 38 00 55 53 45 52  .XDG_SESSION_ID=3418.USER
0x00007FFFFFFFED91  3d 6b 65 76 69 6e 00 50 57 44 3d 2f 68 6f 6d 65 2f 6b 65 76 69 6e 2f 70 72  =kevin.PWD=/home/kevin/pr
0x00007FFFFFFFEDAA  6f 6a 65 63 74 73 2f 43 6f 6e 73 6f 6c 65 41 70 70 6c 69 63 61 74 69 6f 6e  ojects/ConsoleApplication
0x00007FFFFFFFEDC3  31 2f 62 69 6e 2f 78 36 34 2f 44 65 62 75 67 00 4c 49 4e 45 53 3d 34 38 00  1/bin/x64/Debug.LINES=48.
...[생략]...

대신, environ 테이블에서 LINES 환경 변수를 가리키던 9번째 항목의 포인터 값이 새로운 위치로 바뀌게 됩니다.

LINES 환경 변수의 위치 값 == *((long long *)ptr + 8) == 0x00007FFFFFFFEAC8

[기존]
0x00007FFFFFFFEAC8  d3 ed ff ff ff 7f 00 00

[신규]
0x00007FFFFFFFEAC8  84 47 55 55 55 55 00 00

즉, putenv("LINES=45") 명령어는 "LINES=45" 문자열을 위한 메모리 공간을 새롭게 malloc으로 할당받고 그 포인터의 위치를 environ 테이블의 9번째 위치에 덮어 쓰는 것입니다.




변경은 그렇고, 삭제 처리는 어떻게 될까요? "LINES" 환경 변수를,

int result = putenv("LINES"); // result == 0

위와 같이 putenv를 호출해 삭제하면 0x00007FFFFFFFEAC8 위치의 하위에 있던 값들을 전부 한 칸씩 당겨서 복사하는 작업을 합니다. 즉, environ 테이블이 다음과 같이 구성됩니다.

0x00007FFFFFFFEA88  fb ec ff ff ff 7f 00 00
0x00007FFFFFFFEA90  06 ed ff ff ff 7f 00 00
0x00007FFFFFFFEA98  37 ed ff ff ff 7f 00 00
0x00007FFFFFFFEAA0  59 ed ff ff ff 7f 00 00
0x00007FFFFFFFEAA8  68 ed ff ff ff 7f 00 00
0x00007FFFFFFFEAB0  79 ed ff ff ff 7f 00 00
0x00007FFFFFFFEAB8  8d ed ff ff ff 7f 00 00
0x00007FFFFFFFEAC0  98 ed ff ff ff 7f 00 00
0x00007FFFFFFFEAC8  dc ed ff ff ff 7f 00 00 // 기존의 84 47 55 55 55 55 00 00 주솟값을 하위 항목의 주솟값으로 덮어쓰고,
0x00007FFFFFFFEAD0  ed ed ff ff ff 7f 00 00 // 이후 이런 식으로 전부 한 칸씩 당겨지는 복사 작업 수행
0x00007FFFFFFFEAD8  0d ee ff ff ff 7f 00 00
0x00007FFFFFFFEAE0  4e ee ff ff ff 7f 00 00
0x00007FFFFFFFEAE8  61 ee ff ff ff 7f 00 00
0x00007FFFFFFFEAF0  6d ee ff ff ff 7f 00 00
0x00007FFFFFFFEAF8  82 ee ff ff ff 7f 00 00
0x00007FFFFFFFEB00  92 ee ff ff ff 7f 00 00
0x00007FFFFFFFEB08  9c ee ff ff ff 7f 00 00
0x00007FFFFFFFEB10  a4 ee ff ff ff 7f 00 00
0x00007FFFFFFFEB18  b2 ee ff ff ff 7f 00 00
0x00007FFFFFFFEB20  e8 ee ff ff ff 7f 00 00
0x00007FFFFFFFEB28  07 ef ff ff ff 7f 00 00
0x00007FFFFFFFEB30  89 ef ff ff ff 7f 00 00
0x00007FFFFFFFEB38  00 00 00 00 00 00 00 00 // 마지막임을 가리키는 null 포인터
0x00007FFFFFFFEB40  00 00 00 00 00 00 00 00




신규로 환경 변수를 추가하는 작업은 어떨까요? 이게 좀 재미있습니다. ^^ 우선, 기존 environ 테이블은 운영체제로부터 전달받은 환경 변수 목록만을 담을 수 있는 크기로 고정되어 있기 때문에 새롭게 환경 변수를 담을 수 없습니다. 따라서 기존 environ (0x00007FFFFFFFEA88) 테이블은 통째로 버리고 신규 환경 변수를 담을 수 있는 크기의 테이블을 새롭게 생성해 기존 테이블의 값 복사와 함께 신규 항목을 포함하는 작업을 거칩니다.

결국 putenv("TEST=1")과 같은 함수 호출로 인해 전역 변수인 environ 포인터의 위치도 함께 바뀌게 됩니다.

이 상태에서 putenv("TEST=2")를 하면 또 어떻게 될까요?

이미 "TEST"라는 환경 변수가 있기 때문에 environ 테이블의 포인터가 갱신이 될까요? 이번엔 아닙니다. 만약 "TEST=1"의 위치가 최초 CRT가 구성한 환경 변수 문자열 묶음 내에 있었다면 오밀조밀하게 모여 있는 탓에 값을 갱신할 수 없어 그때에만 malloc 신규 할당을 받는 것입니다. 이미 malloc으로 할당받은 환경 변수라면 그 위치의 문자열 값이 변경이 됩니다. 왜냐하면, 애당초 환경 변수를 새롭게 할당받을 때 제한된 변경은 수용할 수 있도록 넉넉한 공간을 할당받기 때문입니다. 따라서 어쩌면 putenv("TEST=....................................................")라는 식의 긴 문자열을 지정하면 이럴 때는 environ 테이블 내의 포인터 값이 갱신이 될 수 있습니다.

위와 같은 것을 감안해서 putenv, environ, setenv 등의 함수에 대한 도움말을 다시 읽어보시면 이전에는 그냥 지나쳤던 문구들을 새롭게 이해할 수 있는 계기가 될 것입니다. ^^ 예를 들어, 다음과 같은 글이 이제는 눈에 들어올 것입니다.

ENV31-C. Do not rely on an environment pointer following an operation that may invalidate it 
; https://wiki.sei.cmu.edu/confluence/display/c/ENV31-C.+Do+not+rely+on+an+environment+pointer+following+an+operation+that+may+invalidate+it




마지막으로, 혼란스러운 함수 사용법을 좀 정리해 보겠습니다.

그러니까, putenv("LINES")는 "LINES" 환경 변수의 항목을 삭제하는 것이지만 이와 동일한 용도로 unsetenv 함수를 사용해도 무방합니다.

putenv("LINES"); // == unsetenv("LINES");

그리고, putenv("LINES=1")와 같은 환경 변수 설정은 setenv로도 동일하게 구현할 수 있습니다.

putenv("LINES=1") // == setenv("LINES", "1", 1)

setenv의 경우 3번째 인자가 "replace" 용도인데요, 이미 해당 키(위의 예제에서는 "LINES")가 환경 변수로 등록되어 있는 경우 그 값을 바꾸기 위해서는 3번쨰 인자의 값을 1로 줘야 합니다. 반면 이미 있는 값인 경우 변경하고 싶지 않다면 0으로 줘야 하고.

setenv("QWER", "1", 1); // 기존 환경 변수 "QWER"의 유무에 상관없이 값을 변경

setenv("QWER", "2", 0); // 기본 환경 변수 "QWER"이 있다면 값을 변경하지 않음
                        // 반면 "QWER"이 없다면 새롭게 값을 등록

마지막으로, 환경 변수 관련한 소스 코드도 있으니 참고하시고.

setenv, unsetenv 소스 코드
; http://man7.org/tlpi/code/online/dist/proc/setenv.c.html

int
unsetenv(const char *name)
{
    extern char **environ;
    char **ep, **sp;
    size_t len;

    if (name == NULL || name[0] == '\0' || strchr(name, '=') != NULL) {
        errno = EINVAL;
        return -1;
    }

    len = strlen(name);

    for (ep = environ; *ep != NULL; )
        if (strncmp(*ep, name, len) == 0 && (*ep)[len] == '=') {

            /* Remove found entry by shifting all successive entries
               back one element */

            for (sp = ep; *sp != NULL; sp++)
                *sp = *(sp + 1);

            /* Continue around the loop to further instances of 'name' */

        } else {
            ep++;
        }

    return 0;
}
int
setenv(const char *name, const char *value, int overwrite)
{
    char *es;

    if (name == NULL || name[0] == '\0' || strchr(name, '=') != NULL ||
            value == NULL) {
        errno = EINVAL;
        return -1;
    }

    if (getenv(name) != NULL && overwrite == 0)
        return 0;

    unsetenv(name);             /* Remove all occurrences */

    es = malloc(strlen(name) + strlen(value) + 2);
                                /* +2 for '=' and null terminator */
    if (es == NULL)
        return -1;

    strcpy(es, name);
    strcat(es, "=");
    strcat(es, value);

    return (putenv(es) != 0) ? -1 : 0;
}




물론, 이 글에서 설명한 세부적인 구현 방식은 구현체마다, 시간에 따라 변경될 수 있음을 감안해야 합니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 5/24/2019]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 46  47  48  49  50  51  [52]  53  54  55  56  57  58  59  60  ...
NoWriterDateCnt.TitleFile(s)
12335정성태9/21/202022415Linux: 31. 우분투 20.04 초기 설정 - 고정 IP 및 SSH 설치
12334정성태9/21/20207857오류 유형: 652. windbg - !py 확장 명령어 실행 시 "failed to find python interpreter"
12333정성태9/20/20208295.NET Framework: 941. C# - 전위/후위 증감 연산자에 대한 오버로딩 구현 (2)
12332정성태9/18/202010334.NET Framework: 940. C# - Windows Forms ListView와 DataGridView의 예제 코드파일 다운로드1
12331정성태9/18/20209496오류 유형: 651. repadmin /syncall - 0x80090322 The target principal name is incorrect.
12330정성태9/18/202010559.NET Framework: 939. C# - 전위/후위 증감 연산자에 대한 오버로딩 구현 [2]파일 다운로드1
12329정성태9/16/202012480오류 유형: 650. ASUS 메인보드 관련 소프트웨어 설치 후 ArmouryCrate.UserSessionHelper.exe 프로세스 무한 종료 현상
12328정성태9/16/202012698VS.NET IDE: 150. TFS의 이력에서 "Get This Version"과 같은 기능을 Git으로 처리한다면?
12327정성태9/12/202010267.NET Framework: 938. C# - ICS(Internet Connection Sharing) 제어파일 다운로드1
12326정성태9/12/20209766개발 환경 구성: 516. Azure VM의 Network Adapter를 실수로 비활성화한 경우
12325정성태9/12/20209294개발 환경 구성: 515. OpenVPN - 재부팅 후 ICS(Internet Connection Sharing) 기능이 동작 안하는 문제
12324정성태9/11/202010556개발 환경 구성: 514. smigdeploy.exe를 이용한 Windows Server 2016에서 2019로 마이그레이션 방법
12323정성태9/11/20209416오류 유형: 649. Copy Database Wizard - The job failed. Check the event log on the destination server for details.
12322정성태9/11/202010522개발 환경 구성: 513. Azure VM의 RDP 접속 위치 제한 [1]
12321정성태9/11/20208775오류 유형: 648. netsh http add urlacl - Error: 183 Cannot create a file when that file already exists.
12320정성태9/11/20209999개발 환경 구성: 512. RDP(원격 데스크톱) 접속 시 비밀 번호를 한 번 더 입력해야 하는 경우
12319정성태9/10/20209730오류 유형: 647. smigdeploy.exe를 Windows Server 2016에서 실행할 때 .NET Framework 미설치 오류 발생
12318정성태9/9/20209219오류 유형: 646. OpenVPN - "TAP-Windows Adapter V9" 어댑터의 "Network cable unplugged" 현상
12317정성태9/9/202011543개발 환경 구성: 511. Beats용 Kibana 기본 대시 보드 구성 방법
12316정성태9/8/20209971디버깅 기술: 170. WinDbg Preview 버전부터 닷넷 코어 3.0 이후의 메모리 덤프에 대해 sos.dll 자동 로드
12315정성태9/7/202012264개발 환경 구성: 510. Logstash - FileBeat을 이용한 IIS 로그 처리 [2]
12314정성태9/7/202010834오류 유형: 645. IIS HTTPERR - Timer_MinBytesPerSecond, Timer_ConnectionIdle 로그
12313정성태9/6/202012005개발 환경 구성: 509. Logstash - 사용자 정의 grok 패턴 추가를 이용한 IIS 로그 처리
12312정성태9/5/202015904개발 환경 구성: 508. Logstash 기본 사용법 [2]
12311정성태9/4/202011097.NET Framework: 937. C# - 간단하게 만들어 보는 리눅스의 nc(netcat), json_pp 프로그램 [1]
12310정성태9/3/202010342오류 유형: 644. Windows could not start the Elasticsearch 7.9.0 (elasticsearch-service-x64) service on Local Computer.
... 46  47  48  49  50  51  [52]  53  54  55  56  57  58  59  60  ...