stunnel/fix-ucontext-handling.patch

786 lines
22 KiB
Diff

--- src/network.c 2011-05-01 23:28:59.000000000 +0200
+++ src/network.c 2011-05-08 09:45:04.000000000 +0200
@@ -119,10 +119,10 @@
int retval, retry;
CONTEXT *context, *prev;
int min_timeout;
- int nfds, i;
+ unsigned int nfds, i;
time_t now;
short *signal_revents;
- static int max_nfds=0;
+ static unsigned int max_nfds=0;
static struct pollfd *ufds=NULL;
time(&now);
@@ -216,51 +216,56 @@
}
int s_poll_wait(s_poll_set *fds, int sec, int msec) {
- /* FIXME: msec parameter is currently ignored with UCONTEXT threads */
CONTEXT *context; /* current context */
static CONTEXT *to_free=NULL; /* delayed memory deallocation */
+ /* FIXME: msec parameter is currently ignored with UCONTEXT threads */
+ (void)msec; /* skip warning about unused parameter */
+
/* remove the current context from ready queue */
context=ready_head;
ready_head=ready_head->next;
if(!ready_head) /* the queue is empty */
ready_tail=NULL;
+ /* it it safe to s_log() after new ready_head is set */
+
+ /* it's illegal to deallocate the stack of the current context */
+ if(to_free) { /* a delayed deallocation is scheduled */
+ s_log(LOG_DEBUG, "Releasing context %ld", to_free->id);
+ free(to_free->stack);
+ free(to_free);
+ to_free=NULL;
+ }
+ /* manage the current thread */
if(fds) { /* something to wait for -> swap the context */
context->fds=fds; /* set file descriptors to wait for */
context->finish=sec<0 ? -1 : time(NULL)+sec;
- /* move (append) the current context to the waiting queue */
+
+ /* append the current context to the waiting queue */
context->next=NULL;
if(waiting_tail)
waiting_tail->next=context;
waiting_tail=context;
if(!waiting_head)
waiting_head=context;
- while(!ready_head) /* no context ready */
- scan_waiting_queue();
+ } else { /* nothing to wait for -> drop the context */
+ to_free=context; /* schedule for delayed deallocation */
+ }
+
+ while(!ready_head) /* wait until there is a thread to switch to */
+ scan_waiting_queue();
+
+ /* switch threads */
+ if(fds) { /* swap the current context */
if(context->id!=ready_head->id) {
s_log(LOG_DEBUG, "Context swap: %ld -> %ld",
context->id, ready_head->id);
swapcontext(&context->context, &ready_head->context);
s_log(LOG_DEBUG, "Current context: %ld", ready_head->id);
- if(to_free) {
- s_log(LOG_DEBUG, "Releasing context %ld", to_free->id);
- free(to_free->stack);
- free(to_free);
- to_free=NULL;
- }
}
return ready_head->ready;
- } else { /* nothing to wait for -> drop the context */
- /* it's illegal to deallocate the stack of the current context */
- if(to_free) {
- s_log(LOG_DEBUG, "Releasing context %ld", to_free->id);
- free(to_free->stack);
- free(to_free);
- }
- to_free=context;
- while(!ready_head) /* no context ready */
- scan_waiting_queue();
+ } else { /* drop the current context */
s_log(LOG_DEBUG, "Context set: %ld (dropped) -> %ld",
context->id, ready_head->id);
setcontext(&ready_head->context);
@@ -701,6 +706,10 @@
longjmp(c->err, 1); /* error */
}
line=str_realloc(line, ptr+1);
+ if(!line) {
+ s_log(LOG_CRIT, "Memory allocation failed");
+ longjmp(c->err, 1); /* error */
+ }
switch(readsocket(fd, line+ptr, 1)) {
case -1: /* error */
sockerror("readsocket (fdgetline)");
--- src/sthreads.c 2011-05-02 23:58:45.000000000 +0200
+++ src/sthreads.c 2011-05-10 11:45:18.000000000 +0200
@@ -47,10 +47,12 @@
/* no need for critical sections */
void enter_critical_section(SECTION_CODE i) {
+ (void)i; /* skip warning about unused parameter */
/* empty */
}
void leave_critical_section(SECTION_CODE i) {
+ (void)i; /* skip warning about unused parameter */
/* empty */
}
@@ -76,7 +78,6 @@
/* first context on the ready list is the active context */
CONTEXT *ready_head=NULL, *ready_tail=NULL; /* ready to execute */
CONTEXT *waiting_head=NULL, *waiting_tail=NULL; /* waiting on poll() */
-int next_id=1;
unsigned long stunnel_process_id(void) {
return (unsigned long)getpid();
@@ -86,63 +87,48 @@
return ready_head ? ready_head->id : 0;
}
-static CONTEXT *new_context(int stack_size) {
+static CONTEXT *new_context(void) {
+ static int next_id=1;
CONTEXT *context;
/* allocate and fill the CONTEXT structure */
- context=str_alloc(sizeof(CONTEXT));
+ context=calloc(1, sizeof(CONTEXT));
if(!context) {
s_log(LOG_ERR, "Unable to allocate CONTEXT structure");
return NULL;
}
- context->stack=str_alloc(stack_size);
- if(!context->stack) {
- s_log(LOG_ERR, "Unable to allocate CONTEXT stack");
- return NULL;
- }
context->id=next_id++;
context->fds=NULL;
context->ready=0;
- /* some manuals claim that initialization of context structure is required */
- if(getcontext(&context->context)<0) {
- str_free(context->stack);
- str_free(context);
- ioerror("getcontext");
- return NULL;
- }
- context->context.uc_link=NULL; /* it should never happen */
-#if defined(__sgi) || ARGC==2 /* obsolete ss_sp semantics */
- context->context.uc_stack.ss_sp=context->stack+stack_size-8;
-#else
- context->context.uc_stack.ss_sp=context->stack;
-#endif
- context->context.uc_stack.ss_size=stack_size;
- context->context.uc_stack.ss_flags=0;
- /* attach to the tail of the ready queue */
+ /* append to the tail of the ready queue */
context->next=NULL;
if(ready_tail)
ready_tail->next=context;
ready_tail=context;
if(!ready_head)
ready_head=context;
+
return context;
}
void sthreads_init(void) {
/* create the first (listening) context and put it in the running queue */
- if(!new_context(DEFAULT_STACK_SIZE)) {
+ if(!new_context()) {
s_log(LOG_ERR, "Unable create the listening context");
die(1);
}
+ /* no need to initialize ucontext_t structure here
+ it will be initialied with swapcontext() call */
}
int create_client(int ls, int s, CLI *arg, void *(*cli)(void *)) {
CONTEXT *context;
(void)ls; /* this parameter is only used with USE_FORK */
+
s_log(LOG_DEBUG, "Creating a new context");
- context=new_context(arg->opt->stack_size);
+ context=new_context();
if(!context) {
if(arg)
free(arg);
@@ -150,6 +136,38 @@
closesocket(s);
return -1;
}
+
+ /* initialize context_t structure */
+ if(getcontext(&context->context)<0) {
+ free(context);
+ if(arg)
+ free(arg);
+ if(s>=0)
+ closesocket(s);
+ ioerror("getcontext");
+ return -1;
+ }
+ context->context.uc_link=NULL; /* stunnel does not use uc_link */
+
+ /* create stack */
+ context->stack=calloc(1, arg->opt->stack_size);
+ if(!context->stack) {
+ free(context);
+ if(arg)
+ free(arg);
+ if(s>=0)
+ closesocket(s);
+ s_log(LOG_ERR, "Unable to allocate stack");
+ return -1;
+ }
+#if defined(__sgi) || ARGC==2 /* obsolete ss_sp semantics */
+ context->context.uc_stack.ss_sp=context->stack+arg->opt->stack_size-8;
+#else
+ context->context.uc_stack.ss_sp=context->stack;
+#endif
+ context->context.uc_stack.ss_size=arg->opt->stack_size;
+ context->context.uc_stack.ss_flags=0;
+
s_log(LOG_DEBUG, "Context %ld created", context->id);
makecontext(&context->context, (void(*)(void))cli, ARGC, arg);
return 0;
@@ -298,7 +316,7 @@
/* Disabled on OS X due to strange problems on Mac OS X 10.5
it seems to restore signal mask somewhere (I couldn't find where)
effectively blocking signals after first accepted connection */
- sigset_t sigset;
+ sigset_t new_set, old_set;
#endif /* HAVE_PTHREAD_SIGMASK && !__APPLE__*/
(void)ls; /* this parameter is only used with USE_FORK */
@@ -306,9 +324,8 @@
#if defined(HAVE_PTHREAD_SIGMASK) && !defined(__APPLE__)
/* the idea is that only the main thread handles all the signals with
* posix threads; signals are blocked for any other thread */
- sigfillset(&sigset);
- pthread_sigmask(SIG_SETMASK, &sigset, NULL); /* block signals */
- sigemptyset(&sigset); /* prepare for unblocking */
+ sigfillset(&new_set);
+ pthread_sigmask(SIG_SETMASK, &new_set, &old_set); /* block signals */
#endif /* HAVE_PTHREAD_SIGMASK && !__APPLE__*/
pthread_attr_init(&pth_attr);
pthread_attr_setdetachstate(&pth_attr, PTHREAD_CREATE_DETACHED);
@@ -316,7 +333,7 @@
error=pthread_create(&thread, &pth_attr, cli, arg);
pthread_attr_destroy(&pth_attr);
#if defined(HAVE_PTHREAD_SIGMASK) && !defined(__APPLE__)
- pthread_sigmask(SIG_SETMASK, &sigset, NULL); /* unblock signals */
+ pthread_sigmask(SIG_SETMASK, &old_set, NULL); /* unblock signals */
#endif /* HAVE_PTHREAD_SIGMASK && !__APPLE__*/
if(error) {
--- src/client.c 2011-05-02 18:12:53.000000000 +0200
+++ src/client.c 2011-06-17 15:19:44.000000000 +0200
@@ -106,10 +106,8 @@
if(!c->opt->option.retry)
break;
sleep(1); /* FIXME: not a good idea in ucontext threading */
-#ifndef USE_UCONTEXT
str_stats();
str_cleanup();
-#endif
}
} else
run_client(c);
@@ -119,18 +117,17 @@
#ifdef DEBUG_STACK_SIZE
stack_info(0); /* display computed value */
#endif
+#ifdef USE_UCONTEXT
+ s_log(LOG_DEBUG, "Context %ld closed", ready_head->id);
+#endif
str_stats();
-#if defined(USE_WIN32) && !defined(_WIN32_WCE)
str_cleanup();
+ /* s_log() is not allowed after str_cleanup() */
+#if defined(USE_WIN32) && !defined(_WIN32_WCE)
_endthread();
#endif
#ifdef USE_UCONTEXT
- s_log(LOG_DEBUG, "Context %ld closed", ready_head->id);
- /* no str_cleanup() here, as all contexts share the same CPU thread */
s_poll_wait(NULL, 0, 0); /* wait on poll() */
- s_log(LOG_ERR, "INTERNAL ERROR: failed to drop context");
-#else
- str_cleanup();
#endif
return NULL;
}
@@ -283,6 +280,15 @@
SSL_set_session_id_context(c->ssl, (unsigned char *)sid_ctx,
strlen(sid_ctx));
if(c->opt->option.client) {
+#ifndef OPENSSL_NO_TLSEXT
+ if(c->opt->host_name) {
+ s_log(LOG_DEBUG, "SNI host name: %s", c->opt->host_name);
+ if(!SSL_set_tlsext_host_name(c->ssl, c->opt->host_name)) {
+ sslerror("SSL_set_tlsext_host_name");
+ longjmp(c->err, 1);
+ }
+ }
+#endif
if(c->opt->session) {
enter_critical_section(CRIT_SESSION);
SSL_set_session(c->ssl, c->opt->session);
@@ -314,9 +320,9 @@
while(1) {
#if OPENSSL_VERSION_NUMBER<0x1000002f
- /* this critical section is a crude workaround for CVE-2010-3864 */
- /* see http://www.securityfocus.com/bid/44884 for details */
- /* NOTE: this critical section also covers callbacks (e.g. OCSP) */
+ /* this critical section is a crude workaround for CVE-2010-3864 *
+ * see http://www.securityfocus.com/bid/44884 for details *
+ * NOTE: this critical section also covers callbacks (e.g. OCSP) */
enter_critical_section(CRIT_SSL);
#endif /* OpenSSL version < 1.0.0b */
if(c->opt->option.client)
@@ -821,22 +827,26 @@
type=strchr(line, ':');
if(!type) {
s_log(LOG_ERR, "Malformed IDENT response");
+ str_free(line);
longjmp(c->err, 1);
}
*type++='\0';
system=strchr(type, ':');
if(!system) {
s_log(LOG_ERR, "Malformed IDENT response");
+ str_free(line);
longjmp(c->err, 1);
}
*system++='\0';
if(strcmp(type, " USERID ")) {
s_log(LOG_ERR, "Incorrect INETD response type");
+ str_free(line);
longjmp(c->err, 1);
}
user=strchr(system, ':');
if(!user) {
s_log(LOG_ERR, "Malformed IDENT response");
+ str_free(line);
longjmp(c->err, 1);
}
*user++='\0';
@@ -846,9 +856,11 @@
safestring(user);
s_log(LOG_WARNING, "Connection from %s REFUSED by IDENT (user %s)",
c->accepted_address, user);
+ str_free(line);
longjmp(c->err, 1);
}
s_log(LOG_INFO, "IDENT authentication passed");
+ str_free(line);
}
#if defined(_WIN32_WCE) || defined(__vms)
@@ -878,8 +890,8 @@
execname_l=str2tstr(c->opt->execname);
execargs_l=str2tstr(c->opt->execargs);
CreateProcess(execname_l, execargs_l, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
- free(execname_l);
- free(execargs_l);
+ str_free(execname_l);
+ str_free(execargs_l);
closesocket(fd[1]);
CloseHandle(pi.hProcess);
@@ -916,6 +928,7 @@
longjmp(c->err, 1);
case 0: /* child */
closesocket(fd[0]);
+ set_nonblock(fd[1], 0); /* switch back to blocking mode */
/* dup2() does not copy FD_CLOEXEC flag */
dup2(fd[1], 0);
dup2(fd[1], 1);
@@ -969,9 +982,10 @@
s=s_socket(AF_INET, SOCK_STREAM, 0, 1, "socket#1");
if(s<0)
longjmp(c->err, 1);
- fd[1]=s_socket(AF_INET, SOCK_STREAM, 0, 1, "socket#2");
- if(fd[1]<0)
+ c->fd=s_socket(AF_INET, SOCK_STREAM, 0, 1, "socket#2");
+ if(c->fd<0)
longjmp(c->err, 1);
+
addrlen=sizeof addr;
memset(&addr, 0, addrlen);
addr.in.sin_family=AF_INET;
@@ -979,22 +993,30 @@
addr.in.sin_port=htons(0); /* dynamic port allocation */
if(bind(s, &addr.sa, addrlen))
log_error(LOG_DEBUG, get_last_socket_error(), "bind#1");
- if(bind(fd[1], &addr.sa, addrlen))
+ if(bind(c->fd, &addr.sa, addrlen))
log_error(LOG_DEBUG, get_last_socket_error(), "bind#2");
+
if(listen(s, 1)) {
+ closesocket(s);
sockerror("listen");
longjmp(c->err, 1);
}
if(getsockname(s, &addr.sa, &addrlen)) {
+ closesocket(s);
sockerror("getsockname");
longjmp(c->err, 1);
}
- if(connect(fd[1], &addr.sa, addrlen)) {
- sockerror("connect");
+ if(connect_blocking(c, &addr, addr_len(addr))) {
+ closesocket(s);
longjmp(c->err, 1);
}
- if((fd[0]=s_accept(s, &addr.sa, &addrlen, 1, "accept"))<0)
+ fd[0]=s_accept(s, &addr.sa, &addrlen, 1, "accept");
+ if(fd[0]<0) {
+ closesocket(s);
longjmp(c->err, 1);
+ }
+ fd[1]=c->fd;
+ c->fd=-1;
closesocket(s); /* don't care about the result */
#else
if(s_socketpair(AF_UNIX, SOCK_STREAM, 0, fd, 1, "socketpair"))
--- src/str.c 2011-05-02 19:14:53.000000000 +0200
+++ src/str.c 2011-05-08 08:37:57.000000000 +0200
@@ -38,17 +38,128 @@
#include "common.h"
#include "prototypes.h"
-typedef struct str_struct {
- struct str_struct *prev, *next;
+#ifndef va_copy
+#ifdef __va_copy
+#define va_copy(dst, src) __va_copy((dst), (src))
+#else /* __va_copy */
+#define va_copy(dst, src) memcpy(&(dst), &(src), sizeof(va_list))
+#endif /* __va_copy */
+#endif /* va_copy */
+
+typedef struct alloc_list {
+ struct alloc_list *prev, *next;
size_t size;
unsigned int magic;
-} STR;
-static void str_set(STR *);
-static STR *str_get();
+} ALLOC_LIST;
+
+static void set_alloc_head(ALLOC_LIST *);
+static ALLOC_LIST *get_alloc_head();
+
+char *str_dup(const char *str) {
+ char *retval;
+
+ retval=str_alloc(strlen(str)+1);
+ if(retval)
+ strcpy(retval, str);
+ return retval;
+}
+
+char *str_printf(const char *format, ...) {
+ char *txt;
+ va_list arglist;
+
+ va_start(arglist, format);
+ txt=str_vprintf(format, arglist);
+ va_end(arglist);
+ return txt;
+}
+
+char *str_vprintf(const char *format, va_list start_ap) {
+ int n, size=64;
+ char *p, *np;
+ va_list ap;
+
+ p=str_alloc(size);
+ if(!p)
+ return NULL;
+ for(;;) {
+ va_copy(ap, start_ap);
+ n=vsnprintf(p, size, format, ap);
+ if(n>-1 && n<size)
+ return p;
+ if(n>-1) /* glibc 2.1 */
+ size=n+1; /* precisely what is needed */
+ else /* glibc 2.0, WIN32, etc. */
+ size*=2; /* twice the old size */
+ np=str_realloc(p, size);
+ if(!np) {
+ str_free(p);
+ return NULL;
+ }
+ p=np; /* LOL */
+ }
+}
+
+#ifdef USE_UCONTEXT
+
+static ALLOC_LIST *alloc_tls=NULL;
+
+void str_init() {
+}
+
+static void set_alloc_head(ALLOC_LIST *alloc_head) {
+ if(ready_head)
+ ready_head->tls=alloc_head;
+ else /* ucontext threads not initialized */
+ alloc_tls=alloc_head;
+}
+
+static ALLOC_LIST *get_alloc_head() {
+ if(ready_head)
+ return ready_head->tls;
+ else /* ucontext threads not initialized */
+ return alloc_tls;
+}
+
+#endif /* USE_UCONTEXT */
+
+#ifdef USE_FORK
+
+static ALLOC_LIST *alloc_tls=NULL;
+
+void str_init() {
+}
+
+static void set_alloc_head(ALLOC_LIST *alloc_head) {
+ alloc_tls=alloc_head;
+}
+
+static ALLOC_LIST *get_alloc_head() {
+ return alloc_tls;
+}
+
+#endif /* USE_FORK */
+
+#ifdef USE_PTHREAD
+
+static pthread_key_t pthread_key;
+
+void str_init() {
+ pthread_key_create(&pthread_key, NULL);
+}
+
+static void set_alloc_head(ALLOC_LIST *alloc_head) {
+ pthread_setspecific(pthread_key, alloc_head);
+}
+
+static ALLOC_LIST *get_alloc_head() {
+ return pthread_getspecific(pthread_key);
+}
+
+#endif /* USE_PTHREAD */
#ifdef USE_WIN32
-/* __thread does not work in mingw32 due to a bug in GCC */
static DWORD tls_index;
void str_init() {
@@ -59,97 +170,81 @@
}
}
-static void str_set(STR *str) {
- if(!TlsSetValue(tls_index, str)) {
+static void set_alloc_head(ALLOC_LIST *alloc_head) {
+ if(!TlsSetValue(tls_index, alloc_head)) {
s_log(LOG_ERR, "TlsSetValue failed");
die(1);
}
}
-static STR *str_get() {
- STR *str;
+static ALLOC_LIST *get_alloc_head() {
+ ALLOC_LIST *alloc_head;
- str=TlsGetValue(tls_index);
- if(!str && GetLastError()!=ERROR_SUCCESS) {
+ alloc_head=TlsGetValue(tls_index);
+ if(!alloc_head && GetLastError()!=ERROR_SUCCESS) {
s_log(LOG_ERR, "TlsGetValue failed");
die(1);
}
- return str;
-}
-
-#else
-
-/* gcc Thread-Local Storage */
-static __thread STR *root_str=NULL;
-
-void str_init() {
- if(root_str)
- s_log(LOG_WARNING, "str_init: Non-empty allocation list");
+ return alloc_head;
}
-static void str_set(STR *str) {
- root_str=str;
-}
-
-static STR *str_get() {
- return root_str;
-}
-
-#endif
+#endif /* USE_WIN32 */
void str_cleanup() {
- STR *str, *tmp;
+ ALLOC_LIST *alloc_head, *tmp;
- str=str_get();
- while(str) {
- tmp=str;
- str=tmp->next;
+ alloc_head=get_alloc_head();
+ while(alloc_head) {
+ tmp=alloc_head;
+ alloc_head=tmp->next;
free(tmp);
}
- str_set(NULL);
+ set_alloc_head(NULL);
}
void str_stats() {
- STR *tmp;
+ ALLOC_LIST *tmp;
int blocks=0, bytes=0;
- for(tmp=str_get(); tmp; tmp=tmp->next) {
+ for(tmp=get_alloc_head(); tmp; tmp=tmp->next) {
++blocks;
bytes+=tmp->size;
}
- s_log(LOG_DEBUG, "str_stats: %d blocks, %d bytes", blocks, bytes);
+ s_log(LOG_DEBUG, "str_stats: %d block(s), %d byte(s)", blocks, bytes);
}
void *str_alloc(size_t size) {
- STR *str, *tmp;
+ ALLOC_LIST *alloc_head, *tmp;
if(size>=1024*1024) /* huge allocations are not allowed */
return NULL;
- tmp=calloc(1, sizeof(STR)+size);
+ tmp=calloc(1, sizeof(ALLOC_LIST)+size);
if(!tmp)
return NULL;
- str=str_get();
+ alloc_head=get_alloc_head();
tmp->prev=NULL;
- tmp->next=str;
+ tmp->next=alloc_head;
tmp->size=size;
tmp->magic=0xdeadbeef;
- if(str)
- str->prev=tmp;
- str_set(tmp);
+ if(alloc_head)
+ alloc_head->prev=tmp;
+ set_alloc_head(tmp);
return tmp+1;
}
void *str_realloc(void *ptr, size_t size) {
- STR *oldtmp, *tmp;
+ ALLOC_LIST *old_tmp, *tmp;
if(!ptr)
return str_alloc(size);
- oldtmp=(STR *)ptr-1;
- if(oldtmp->magic!=0xdeadbeef) { /* not allocated by str_alloc() */
+ old_tmp=(ALLOC_LIST *)ptr-1;
+ if(old_tmp->magic!=0xdeadbeef) { /* not allocated by str_alloc() */
s_log(LOG_CRIT, "INTERNAL ERROR: str_realloc: Bad magic");
die(1);
}
- tmp=realloc(oldtmp, sizeof(STR)+size);
+ if(size>=1024*1024) /* huge allocations are not allowed */
+ return NULL;
+ tmp=realloc(old_tmp, sizeof(ALLOC_LIST)+size);
if(!tmp)
return NULL;
/* refresh all possibly invalidated pointers */
@@ -158,17 +253,17 @@
if(tmp->prev)
tmp->prev->next=tmp;
tmp->size=size;
- if(str_get()==oldtmp)
- str_set(tmp);
+ if(get_alloc_head()==old_tmp)
+ set_alloc_head(tmp);
return tmp+1;
}
void str_free(void *ptr) {
- STR *tmp;
+ ALLOC_LIST *tmp;
if(!ptr) /* do not attempt to free null pointers */
return;
- tmp=(STR *)ptr-1;
+ tmp=(ALLOC_LIST *)ptr-1;
if(tmp->magic!=0xdeadbeef) { /* not allocated by str_alloc() */
s_log(LOG_CRIT, "INTERNAL ERROR: str_free: Bad magic");
die(1);
@@ -178,54 +273,9 @@
tmp->next->prev=tmp->prev;
if(tmp->prev)
tmp->prev->next=tmp->next;
- if(str_get()==tmp)
- str_set(tmp->next);
+ if(get_alloc_head()==tmp)
+ set_alloc_head(tmp->next);
free(tmp);
}
-char *str_dup(const char *str) {
- char *retval;
-
- retval=str_alloc(strlen(str)+1);
- if(retval)
- strcpy(retval, str);
- return retval;
-}
-
-char *str_vprintf(const char *format, va_list start_ap) {
- int n, size=64;
- char *p, *np;
- va_list ap;
-
- p=str_alloc(size);
- if(!p)
- return NULL;
- for(;;) {
- va_copy(ap, start_ap);
- n=vsnprintf(p, size, format, ap);
- if(n>-1 && n<size)
- return p;
- if(n>-1) /* glibc 2.1 */
- size=n+1; /* precisely what is needed */
- else /* glibc 2.0 */
- size*=2; /* twice the old size */
- np=str_realloc(p, size);
- if(!np) {
- str_free(p);
- return NULL;
- }
- p=np; /* LOL */
- }
-}
-
-char *str_printf(const char *format, ...) {
- char *txt;
- va_list arglist;
-
- va_start(arglist, format);
- txt=str_vprintf(format, arglist);
- va_end(arglist);
- return txt;
-}
-
/* end of str.c */