메뉴 건너뛰기


Developer > Open Source

APR 5장. APR의 간단한 함수들

2013.11.12 22:05

푸우 조회 수:7583

이번에는 머리도 식힐겸 APR에서 제공하는 아주 간단한(?) 몇개의 함수를 설명하도록 하겠습니다.
이번에 설명할 함수들은 APR 함수이기는 하지만 초기화 같은 것은 필요없습니다.
즉, 함수의 기능을 이해하고 그냥 사용하면됩니다.
 
1) 화면에서 암호를 입력받자. 
 
C에서 제공하는 표준라이브러리에는 암호를 입력받는 함수가 없습니다.
여기서 암호를 입력받는 함수란 화면에 타이핑하는 글자가 보이지 않으면서 버퍼에 타이핑한 글자가 담기는 뭐 그런 함수를 이야기 합니다.
APR에서는 이런 기능의 함수를 제공하고 있습니다.
 
apr_status_t apr_password_get(const char *prompt, char *pwbuf, apr_size_t *bufsize)
 
[파라메터]
prompt: 암호를 입력받기 위해 화면에 표시하는 글(일반적으로 "암호를 입력하세요"와 같은...)을 입력합니다.
pwbuf: 타이핑한 암호를 저장할 버퍼입니다.
bufsize: pwbuf의 크기를 입력하여 줍니다.
 
리턴값으로는 암호를 입력받기를 성공하면 APR_SUCCESS를 리턴합니다. 지정한 버퍼의 크기를 초가한 경우 APR_ENAMETOOLONG 값을 리턴합니다.
여기서 주의할 점은 bufsize가 apr_size_t의 포인터 형이라는 겁니다. 그렇다고 apr_password_get()함수내에서 이값을 바꾸는 것도 아닙니다. APR의 설명에는 이렇게 만든것은 "아무 이유없어"라고 나와있습니다. 그냥 주의만 하랍니다. ㅎㅎ
 
다음은 사용예입니다.
 
#include "apr_lib.h" int main(int argc, char *argv[]) { apr_status_t s; unsigned char buf[100]; unsigned int size=10; while(1){ s=apr_password_get("Password: ", buf, &size); if(s==APR_ENAMETOOLONG){ printf("Password is too long.\n"); }else break; } printf("Your password is \"%s\".\n", buf); return 0; }
 
위의 코드의 실행 결과는 다음과 같습니다.
 
Password: ************
Password is too long.
Password: *****
Your password is "12345".
 
 
맨처음 입력시에는 암호로 12자를 입력해서 APR_ENAMETOOLONG 값을 리턴받아구요. 두번째는 12345 라는 5자의 암호를 입력하여 정상적으로 처리된 경우입니다. 
 
 
2) 주어진 파일의 전체 경로에서 파일이름만 구별하기. 
 
뭐 제목 그대롭니다. 이런 함수를 만드는 것 자체는 뭐 그리 어려운 일은 아니죠.
그런데 이걸 라이브러리화 해 놓지 않으면 매번 짜야하는게 좀 불편하죠. 그래서 APR에서는 만들어 놓았나 봅니다. 뭐 긴말 할 것 없이 함수 원형과 사용예를 보이겠습니다. 간단하니깐... 설명도 생략하겠습니다.
 
원형:
const char *apr_filepath_name_get(const char *pathname)
 
사용예:
#include "apr_lib.h" int main(int argc, char *argv[]) { printf("\"/abc/def.hwp\" [%s]\n", apr_filepath_name_get("/abc/def.hwp")); printf("\"/foo/bar/gum\" [%s]\n", apr_filepath_name_get("/foo/bar/gum")); printf("\"/foo/bar/gum/\" [%s]\n", apr_filepath_name_get("/foo/bar/gum/")); printf("\"gum\" [%s]\n", apr_filepath_name_get("gum")); printf("\"bs\\path\\stuff\" [%s]\n", apr_filepath_name_get("bs\\path\\stuff")); return 0; }
 
실행결과
 
"/abc/def.hwp" [def.hwp]
"/foo/bar/gum" [gum]
"/foo/bar/gum/" []
"gum" [gum]
"bs\path\stuff" [stuff]
 
 
 
3) 나만의 printf()함수 만들기(?).
 
이번에는 좀 설명하기 복잡한 APR함수 하나를 소개합니다.
사실 이 함수를 제가 테스트 하기 위해 코딩을 해서 돌려보고도 왜 이런 함수를 만들었는지 의문점이 많았습니다. 몇일을 고민하다가 우연하게 왜 만들었는지 알게 되어서 썼던 글을 다시 수정하여 씁니다.
미리 말씀드리는데 함수 하나를 설명하긴 할텐데... 좀 복잡하기도 하고 해야될 이야기가 많습니다.
느긋한 마음으로 글을 읽어 주세요.
 
실제 APR함수를 설명하기 전에 C언어의 기초만 공부하신 분들은 앞으로 하는 이야기가 도통 뭔 소린지 잘 못 알아 들으실 것 같아서 잠깐 C언어에 대해 이야기 하도록 하겠습니다.
뭐 다 아는 내용인지도 모르지만 앞으로의 이야기를 이해하려면 C언어에서 가변인자를 처리하는 방법에 대해서 이해하고 있어야 합니다. 간단히 설명하자면 가변인자란 printf()함수와 같이 파라메터(인자)의 갯수가 정해지지 않고 여러개 올 수 있는 함수를 이야기 합니다.
C언어에서는 이런 함수를 만들기 위해 함수 원형에 "..."이라는 특수한 문자를 사용하여 가변인자를 사용한다고 지정할 수 있게 하고 있으며 va_start()와 va_end() 그리고 va_arg()라는 함수와 va_list라는 데이터형을 사용하여 그 수나 데이터형이 정해지지 않은 여러개의 파라메터를 처리하도록 하고 있습니다.
한번쯤은 보셨겠지만 printf 함수의 원형은
int printf(const char *format, ...);
입니다. 자세한 사항은 여러분이 갖고 있는 중급 이상의 C언어 책을 참고하세요.
 
이제 다시 본론으로 돌아와서...
이번에 소개할 APR함수는 printf함수와 유사하게 가변인자들을 받아 특정 형태의 문자열로 만들어서 지정한 버퍼에 담아주는 함수입니다.
원형은 다음과 같습니다.
 
int apr_vformatter(int (*flush_func)(apr_vformatter_buff_t *), apr_vformatter_buff_t *vbuff, const char *fmt, va_list ap)
 
좀 길죠? ^^;
여기서 맨 마지막 파라메터는 앞에서 설명한 가변인자를 처리하기 위한 파라메터입니다.
즉, 가변인자를 처리하기 위한 C언어의 함수 중 va_arg()함수를 사용하지 않고 대신에 apr_vformatter()를 사용하여 인자들을 처리하게 하면 됩니다.
 
[파라메터]
flush_func: 제공된 버퍼가 모두 사용되었을 경우 자동으로 호출되는 Callback함수 지정 (데이터의 형이 좀 이상한가요? 함수에 대한 데이터 형이 이런식으로 생겼습니다. 자세한 내용은 C언어 책을 참고하세요.)
vbuff: 형식화된 문자열을 만들어 담아올 버퍼(주의, 좀 있다 설명하겠지만 버퍼 자체를 의미하지는 않음)
fmt: 결과 문자열을 생성하기 위해 형식을 지정하는 문자열(printf함수의 format과 유사함)
ap: 가변 인자를 처리하기 위해 넘겨주는 변수
 
[리턴값]
일반적으로 버퍼에 담은 문자열의 길이를 리턴. 특수한 경우 -1을 리턴
 
뭐 위의 설명만 보고는 도통 뭘 하자는 건지 잘 모르시겠죠?
우선 파라메터 중 vbuff에 대해서 좀 더 알아보겠습니다.
 
vbuff는 구조체 apr_vformatter_buff_t의 포인터 형입니다.
apr_vformatter_buff_t 구조체는 다음과 같이 생겼습니다.
 
struct apr_vformatter_buff_t {
  char *curpos;
  char *endpos;
};
 
즉 apr_vformatter()함수에서 형식화 되어 새롭게 생성 될 문자열을 위해 버퍼를 제공해야 하는데 버퍼 자체를 파라메터로 주는게 아니고 버퍼의 시작 주소와 끝주소를 apr_vformatter_buff_t 구조체에 담아 전달하게 됩니다. 예를 들면 다음과 같습니다.
 
apr_vformatter_buff_t vbuff;
char *buf=malloc(10);
 
vbuff.curpos=buf;
vbuff.endpos=buf+9;
 
이런 식으로 만들어서 apr_vformatter()함수의 파라메터로 입력하면 apr_vformatter()함수에서는 형식화된 문자열을 만들어서 vbuff.curpos의 주소에 문자열 담고 vbuff.curpos 가 가르키는 주소를 담은 크기 만큼 증가시킵니다. 이렇게 vbuff.curpos에 값을 저장하다가 vbuff.endpos와 같게되면 즉, 할당된 버퍼를 다 사용하게 되면 첫번째 파라메터로 지정한 flush_func()함수를 자동으로 호출하게 됩니다.
 
flush_func()함수가 -1을 apr_vformatter()함수에 리턴하면 apr_vformatter()함수는 남은 작업이 있더라도 바로 -1을 리턴하면서 함수가 종료됩니다. flush_func()함수가 -1 외의 값을 리턴하면 apr_vformatter()함수는 남은 작업을 계속해서 진행하게 되고 버퍼가 작업이 끝날때까지 충분하다면 새로 생성된 문자열의 길이를 리턴합니다.
 
fmt는 printf()함수의 format과 같이 %s, %d, %f, %%와 같은 printf에서 사용되는 형식인자를 사용할 수 있습니다. 거기에 추가로 다음의 형식 인자를 더 지원합니다.
 
추가된 형식인자설명

%pA

struct in_addr *형의 인자를 받아 123.123.123.123형식의 IP주소를 출력한다.

%pI

apr_sockaddr_t *형의 인자를 받아 123.123.123.123:port  혹은 [ipv6주소]:port 형식으로 출력한다.

%pT

apr_os_thread_t *형의 인자를 받아 thread번호를 출력한다.

%pt

apr_os_thread_t *형의 인자를 받아 thread번호를 16진수로 출력한다.

%pp

void *형의 인자를 받아 가지고 있는 값을 16진수로 출력한다.
 
예를 들면 fmt에 "[%5s][%010d][%f][%pp]"와 같은 형식을 지정하고 가변인자로 "abc", 1, 2.0, (void*)&"123" 을 순서대로 지정하였다면 아래와 유사한 값을 버퍼에 담아 줍니다.
 
[  abc][0000000001][2.000000][426040]
 
사용하는 예제로 APR에서 vpr_vformatter()를 이용해서 만들어진 apr_snprintf()함수를 보여드리도록 하겠습니다.
 
#include "apr_lib.h" static int snprintf_flush(apr_vformatter_buff_t *vbuff) { return -1; } APR_DECLARE_NONSTD(int) apr_snprintf(char *buf, apr_size_t len, const char *format, ...) { int cc; va_list ap; apr_vformatter_buff_t vbuff; if (len == 0) { vbuff.curpos = NULL; vbuff.endpos = NULL; } else { vbuff.curpos = buf; vbuff.endpos = buf + len - 1; } va_start(ap, format); cc = apr_vformatter(snprintf_flush, &vbuff, format, ap); va_end(ap); if (len != 0) { *vbuff.curpos = '\0'; } return (cc == -1) ? (int)len - 1 : cc; } int main(int argc, char *argv[]) { char buf[100]; apr_snprintf(buf, 100, "[%5s][%010d][%2.5f][%pp]\n", "abc", 1, 2.0, (void*)"123"); printf("%s", buf); return 0; }
 
 
실행결과
 
[  abc][0000000001][2.00000][427054]
 
 
이제 어떻게 저렇게 실행되었는지는 이해가 가시죠?
 
쩝 그런데 여기서 부터 저의 고민이 시작되었습니다.
 
위의 예제에서 snprintf_flush()함수는 무조건 -1을 리턴하고 있습니다. 즉, 제공된 버퍼를 모두 사용하게 되면 snprintf_flush()함수가 호출되게 되고 호출된 snprintf_flush()함수는 무조건 -1을 리턴하여 더이상 버퍼에 기록을 못하게 하는 기능을 하고 있긴 한데...
이럴 거라면 굳이 callback함수를 사용해서 복잡하게 만들 필요가 있을까? 라는 생각이 들었습니다.
뭐 위의 buf를 동적으로 할당하였다면 realloc()이라도 했으면 좋겠는데... snprintf_flush()함수에 넘어오는 vbuff변수의 curpos의 값은 이미 buf의 제일끝 번지를 가르킨 상태로 왔기 때문에 realloc()을 할 수가 없더군요.
callback함수의 이름을 ~flush()라고 하는 걸로 봐서는 버퍼가 꽉차면 그걸 비우고 다시 사용하게 해야 할 것 같은데... 도대체 무슨 방법으로 하라는 건지...
 
여러분은 어떻게 생각하세요? 저만 바보같은 생각을 한건가요? (나만 바본거야. 그런거야~)
 
이런 생각으로 수일을 고민하다가 드디어 활용방안을 알아냈습니다. 답은 APR의 다른 소스에 있더군요.
 
우선 flush함수의 용도는 조금 전에 말한대로 버퍼를 다 사용한 경우 휴지통 비우듯(변기에 물내리 듯)  뭔가를 처리하여 계속해서 진행시키거나 동작을 그많 하거나 하는 것입니다.
 
한가지 예로 다음과 같은 상황을 생각해 보죠.
프로그래밍 할 당시에 몇바이트가 될지 모르는 형식화를 시켜야 할 문자열을 네트워크를 통해 다른 쪽 컴퓨터로 보내야 되는 상황이라면 일반적으로 sprintf()함수를 사용하여 특정 버퍼에 형식화된 문자열을 담고 이를 send()로 보내겠죠. 이때 사용할 버퍼를 코딩단계에서는 얼마나 될지 모르니깐 충분히 크게 잡는 것이 보통입니다. 이런 경우 뭐 대부분은 정상적으로 동작하겠지만... 생각지도 못한 긴 길이의 문자열 변수가 들어 온다면 문제가 생길 소지가 있죠. 이런 경우 적당한 버퍼만을 만들어 형식화 시키면서 버퍼가 다 차면 send()로 보내고 다시 그 버퍼의 처음부터 사용하여 나머지 부분을 또 형식화 해서 또 버퍼를 다 쓰면 send()로 보내고 하는 그런 프로그램을 만들면 좋겠죠. 이게 아마도 flush()함수의 원래 목적이었을 것입니다.
 
여기까지 읽어도 한가지 문제가 있습니다.
flush 함수는 callback 함수이므로 개발자가 임의로 호출하거나 함수의 원형을 변경할 수 없습니다. flush함수는 탁 하나의 파라메터로 apr_vformetter_t형의 변수를 받습니다. 위의 시나리오 대로 하려면 적어도 socket 번호와 할당한 buf의 시작주소라도 파라메터로 전달 받아야 할 겁니다. 그렇다고 전역변수로 이들 값들을 사용하자니 쓰레드에 문제가 생길 수 있고...
이런 고민 중에 APR의 다른 곳에서 apr_vformatter()함수를 사용하는 곳이 없나 싶어 뒤져봤더니 의문이 풀리더군요.
 
위의 시나리오 대로 프로그램하려면 배포다 배꼽이 더 커지니깐 약간 바꿔서 버퍼가 가득차면 특정 파일에 이를 기록하는 것으로 소스를 만들어 보겠습니다.
 
 
#include "apr_lib.h" typedef struct _my_buf{ apr_vformatter_buff_t vbuff; FILE *fp; char *buf; int size; } my_buf; static int my_flush(apr_vformatter_buff_t *vbuff) { my_buf *mbuf; mbuf=(my_buf*)vbuff; *(mbuf->vbuff.curpos) = '\0'; (mbuf->size)+=strlen(mbuf->vbuff.curpos); fputs(mbuf->buf, mbuf->fp); mbuf->vbuff.curpos=mbuf->buf; return 0; } int my_fprintf(FILE *fp, const char *format, ...) { int cc; va_list ap; char buf[100]; my_buf mbuf; mbuf.vbuff.curpos=buf; mbuf.vbuff.endpos=buf+100-1; mbuf.buf=buf; mbuf.fp=fp; mbuf.size=0; va_start(ap, format); cc = apr_vformatter(my_flush, &(mbuf.vbuff), format, ap); va_end(ap); if (cc > 0) { *(mbuf.vbuff.curpos) = '\0'; mbuf.size+=cc; fputs(mbuf.buf, fp); } return mbuf.size; } int main(int argc, char *argv[]) { int len; len=my_fprintf(stdout, "[%5s][%01000d][%2.5f][%pp]\n", "abc", 1, 2.0, (void*)"123"); printf("Total Size: %d\n", len); return 0; }
 
소스를 보시면 아시겠지만할당한 버퍼는 탁 100byte입니다. main()에서 my_fprintf()함수를 사용하면서 첫번째 인자로 표준출력에 출력해라고 주고 두번째 인자의 "[%01000d]" 구문에 의해 생성되는 형식화된 문자열은 자연스럽게 1000byte를 넘어서게 됩니다. 아래의 실행결과를 아시겠지만 전혀 오류가 생기지 않고 100byte의 버퍼만으로 모든 문자열을 출력하였습니다.
 
 
실행결과
 
[  abc][000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000001][2.00000][42602c]
Total Size: 1027
 
 
여기서 flush()함수에 외부에서 제공된 fp나 buf의 시작주소와 같은 것을 전달하는 방법에 주목해 주세요. 이 문제의 해법은 바로 "struct _my_buf"에 있었습니다.
_my_buf 구조체의 제일 첫 변수인 vbuff의 주소는 _my_buf의 주소도 되는 것이죠.
그래서 apr_vformatter()함수 호출시는 vbuff만을 넘기지만 my_flush()함수에서는 vbuff를 _my_buf구조체로 형변환해서 사용해도 무방한 것입니다.
 
이해가 안가신다면... 질문해 주세요.
 
우여곡절 끝에 어떻게 사용해야 하는지 까지는 알았지만...
내가 APR을 만드는 사람이라면 flush로 void *형의 파라미터를 하나 더 넘기게 만들 것 같군요.
 
이번 내용은 머리를 식힌다고 시작했는데 의외로 넘 복잡한 이야기가 되었네요.
 
Creative Commons License
Creative Commons License이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-동일조건변경허락 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
Copyright 조희창(Nicholas Jo). Some rights reserved. http://bbs.nicklib.com