35 #include <sys/uio.h> |
35 #include <sys/uio.h> |
36 #include <sys/uio.h> |
36 #include <sys/uio.h> |
37 #ifndef BSD |
37 #ifndef BSD |
38 #include <sys/sendfile.h> |
38 #include <sys/sendfile.h> |
39 #endif |
39 #endif |
40 #include <limits.h> /* asprintf */ |
|
41 |
40 |
42 #include "../daemon/vfs.h" |
41 #include "../daemon/vfs.h" |
43 #include "io.h" |
42 #include "io.h" |
44 #include "pool.h" |
43 #include "pool.h" |
|
44 #include "ucx/utils.h" |
45 |
45 |
46 IOStream native_io_funcs = { |
46 IOStream native_io_funcs = { |
47 system_write, |
47 (io_write_f)net_sys_write, |
48 system_writev, |
48 (io_writev_f)net_sys_writev, |
49 system_read, |
49 (io_read_f)net_sys_read, |
50 NULL, |
50 (io_sendfile_f)net_sys_sendfile, |
51 NULL, |
51 (io_close_f)net_sys_close, |
52 NULL |
52 NULL |
53 }; |
53 }; |
54 |
54 |
55 IOStream net_io_funcs = { |
55 IOStream http_io_funcs = { |
56 (io_write_f)net_stream_write, |
56 (io_write_f)net_http_write, |
57 (io_writev_f)net_stream_writev, |
57 (io_writev_f)net_http_writev, |
58 (io_read_f)net_stream_read, |
58 (io_read_f)net_http_read, |
59 (io_sendfile_f)net_stream_sendfile, |
59 (io_sendfile_f)net_http_sendfile, |
60 (io_close_f)net_stream_close, |
60 (io_close_f)net_http_close, |
61 (io_finish_f)net_stream_finish |
61 (io_finish_f)net_http_finish |
62 }; |
62 }; |
63 |
63 |
64 IOStream ssl_io_funcs = { |
64 IOStream ssl_io_funcs = { |
65 (io_write_f)net_ssl_write, |
65 (io_write_f)net_ssl_write, |
66 (io_writev_f)net_ssl_writev, |
66 (io_writev_f)net_ssl_writev, |
69 (io_close_f)net_ssl_close, |
69 (io_close_f)net_ssl_close, |
70 (io_finish_f)net_ssl_finish |
70 (io_finish_f)net_ssl_finish |
71 }; |
71 }; |
72 |
72 |
73 |
73 |
74 IOStream* stream_new_from_fd(pool_handle_t *pool, int fd) { |
74 /* |
75 SystemIOStream *st = pool_malloc(pool, sizeof(SystemIOStream)); |
75 * SysStream implementation |
|
76 */ |
|
77 |
|
78 IOStream* sysstream_new(pool_handle_t *pool, int fd) { |
|
79 SysStream *st = pool_malloc(pool, sizeof(SysStream)); |
76 st->st = native_io_funcs; |
80 st->st = native_io_funcs; |
77 st->fd = fd; |
81 st->fd = fd; |
78 return (IOStream*)st; |
82 return (IOStream*)st; |
79 } |
83 } |
80 |
84 |
81 ssize_t system_write(IOStream *st, void *buf, size_t nbytes) { |
85 #ifdef XP_UNIX |
82 return write(((SystemIOStream*)st)->fd, buf, nbytes); |
86 ssize_t net_sys_write(SysStream *st, void *buf, size_t nbytes) { |
83 } |
87 return write(st->fd, buf, nbytes); |
84 |
88 } |
85 ssize_t system_writev(IOStream *st, struct iovec *iovec, int iovcnt) { |
89 |
86 return writev(((SystemIOStream*)st)->fd, iovec, iovcnt); |
90 ssize_t net_sys_writev(SysStream *st, struct iovec *iovec, int iovcnt) { |
87 } |
91 return writev(st->fd, iovec, iovcnt); |
88 |
92 } |
89 ssize_t system_read(IOStream *st, void *buf, size_t nbytes) { |
93 |
90 return read(((SystemIOStream*)st)->fd, buf, nbytes); |
94 ssize_t net_sys_read(SysStream *st, void *buf, size_t nbytes) { |
91 } |
95 return read(st->fd, buf, nbytes); |
92 |
96 } |
93 |
97 |
94 IOStream* net_stream_from_fd(pool_handle_t *pool, int fd) { |
98 ssize_t net_sys_sendfile(SysStream *st, sendfiledata *sfd) { |
95 NetIOStream *st = pool_malloc(pool, sizeof(NetIOStream)); |
|
96 st->st = net_io_funcs; |
|
97 st->fd = fd; |
|
98 st->max_read = 0; |
|
99 st->read = 0; |
|
100 st->chunked_enc = 0; |
|
101 st->buffered = 0; |
|
102 return (IOStream*)st; |
|
103 } |
|
104 |
|
105 ssize_t net_stream_write(NetIOStream *st, void *buf, size_t nbytes) { |
|
106 if(st->chunked_enc) { |
|
107 // TODO: on some plattforms iov_len is smaller than size_t |
|
108 struct iovec io[2]; |
|
109 char chunk_len[16]; |
|
110 io[0].iov_base = chunk_len; |
|
111 io[0].iov_len = snprintf(chunk_len, 16, "\r\n%zx\r\n", nbytes); |
|
112 io[1].iov_base = buf; |
|
113 io[1].iov_len = nbytes; |
|
114 ssize_t r = writev(st->fd, io, 2); |
|
115 return r - io[0].iov_len; |
|
116 } else { |
|
117 return write(st->fd, buf, nbytes); |
|
118 } |
|
119 } |
|
120 |
|
121 ssize_t net_stream_writev(NetIOStream *st, struct iovec *iovec, int iovcnt) { |
|
122 if(st->chunked_enc) { |
|
123 struct iovec *io = calloc(iovcnt + 1, sizeof(struct iovec)); |
|
124 char chunk_len[16]; |
|
125 io[0].iov_base = chunk_len; |
|
126 size_t len = 0; |
|
127 for(int i=0;i<iovcnt;i++) { |
|
128 len += iovec[i].iov_len; |
|
129 } |
|
130 io[0].iov_len = snprintf(chunk_len, 16, "\r\n%zx\r\n", len); |
|
131 memcpy(io + 1, iovec, iovcnt * sizeof(struct iovec)); |
|
132 ssize_t r = writev(st->fd, io, iovcnt + 1); |
|
133 return r - io[0].iov_len; |
|
134 } else { |
|
135 return writev(st->fd, iovec, iovcnt); |
|
136 } |
|
137 } |
|
138 |
|
139 ssize_t net_stream_read(NetIOStream *st, void *buf, size_t nbytes) { |
|
140 if(st->max_read != 0 && st->read >= st->max_read) { |
|
141 return 0; |
|
142 } |
|
143 ssize_t r = read(st->fd, buf, nbytes); |
|
144 st->read += r; |
|
145 return r; |
|
146 } |
|
147 |
|
148 ssize_t net_stream_sendfile(NetIOStream *st, sendfiledata *sfd) { |
|
149 ssize_t ret = 0; |
99 ssize_t ret = 0; |
150 off_t fileoffset = sfd->offset; |
100 off_t fileoffset = sfd->offset; |
151 if(sfd->fd->fd != -1) { |
101 if(sfd->fd->fd != -1) { |
152 #ifdef BSD |
102 #ifdef BSD |
153 struct iovec hdvec; |
103 struct iovec hdvec; |
190 } |
140 } |
191 |
141 |
192 return ret; |
142 return ret; |
193 } |
143 } |
194 |
144 |
195 void net_stream_close(NetIOStream *st) { |
145 void net_sys_close(SysStream *st) { |
196 close(st->fd); |
146 close(st->fd); |
197 } |
147 } |
198 |
148 |
199 void net_stream_finish(NetIOStream *st) { |
149 #elif defined(XP_WIN32) |
|
150 |
|
151 ssize_t net_sys_write(SysStream *st, void *buf, size_t nbytes) { |
|
152 int ret = send(st->socket, buf, nbytes, 0); |
|
153 if(ret == SOCKET_ERROR) { |
|
154 return IO_ERROR; |
|
155 } |
|
156 return ret; |
|
157 } |
|
158 |
|
159 ssize_t net_sys_writev(SysStream *st, struct iovec *iovec, int iovcnt) { |
|
160 // TODO |
|
161 } |
|
162 |
|
163 ssize_t net_sys_read(SysStream *st, void *buf, size_t nbytes) { |
|
164 int ret = recv(st->socket, buf, nbytes, 0); |
|
165 if(ret == SOCKET_ERROR) { |
|
166 return IO_ERROR; |
|
167 } |
|
168 return ret; |
|
169 } |
|
170 |
|
171 ssize_t net_sys_sendfile(SysStream *st, sendfiledata *sfd) { |
|
172 // TODO |
|
173 } |
|
174 |
|
175 void net_sys_close(SysStream *st) { |
|
176 closesocket(st->socket); |
|
177 } |
|
178 |
|
179 #endif |
|
180 |
|
181 |
|
182 /* |
|
183 * HttpStream implementation |
|
184 */ |
|
185 |
|
186 IOStream* httpstream_new(pool_handle_t *pool, IOStream *fd) { |
|
187 HttpStream *st = pool_malloc(pool, sizeof(HttpStream)); |
|
188 st->st = http_io_funcs; |
|
189 st->fd = fd; |
|
190 st->max_read = 0; |
|
191 st->read = 0; |
|
192 st->chunked_enc = WS_FALSE; |
|
193 st->buffered = WS_FALSE; |
|
194 return (IOStream*)st; |
|
195 } |
|
196 |
|
197 ssize_t net_http_write(HttpStream *st, void *buf, size_t nbytes) { |
|
198 IOStream *fd = st->fd; |
200 if(st->chunked_enc) { |
199 if(st->chunked_enc) { |
201 write(st->fd, "0\r\n\r\n", 5); |
200 // TODO: on some plattforms iov_len is smaller than size_t |
202 } |
201 struct iovec io[2]; |
203 } |
202 char chunk_len[16]; |
204 |
203 io[0].iov_base = chunk_len; |
205 |
204 io[0].iov_len = snprintf(chunk_len, 16, "\r\n%zx\r\n", nbytes); |
206 /* ssl stream */ |
205 io[1].iov_base = buf; |
207 IOStream* net_ssl_stream(pool_handle_t *pool, SSL *ssl) { |
206 io[1].iov_len = nbytes; |
|
207 ssize_t r = fd->writev(fd, io, 2); |
|
208 return r - io[0].iov_len; |
|
209 } else { |
|
210 return fd->write(fd, buf, nbytes); |
|
211 } |
|
212 } |
|
213 |
|
214 ssize_t net_http_writev(HttpStream *st, struct iovec *iovec, int iovcnt) { |
|
215 IOStream *fd = st->fd; |
|
216 if(st->chunked_enc) { |
|
217 struct iovec *io = calloc(iovcnt + 1, sizeof(struct iovec)); |
|
218 char chunk_len[16]; |
|
219 io[0].iov_base = chunk_len; |
|
220 size_t len = 0; |
|
221 for(int i=0;i<iovcnt;i++) { |
|
222 len += iovec[i].iov_len; |
|
223 } |
|
224 io[0].iov_len = snprintf(chunk_len, 16, "\r\n%zx\r\n", len); |
|
225 memcpy(io + 1, iovec, iovcnt * sizeof(struct iovec)); |
|
226 ssize_t r = fd->writev(fd, io, iovcnt + 1); |
|
227 return r - io[0].iov_len; |
|
228 } else { |
|
229 return fd->writev(fd, iovec, iovcnt); |
|
230 } |
|
231 } |
|
232 |
|
233 ssize_t net_http_read(HttpStream *st, void *buf, size_t nbytes) { |
|
234 if(st->max_read != 0 && st->read >= st->max_read) { |
|
235 return 0; |
|
236 } |
|
237 ssize_t r = st->fd->read(st->fd, buf, nbytes); |
|
238 st->read += r; |
|
239 return r; |
|
240 } |
|
241 |
|
242 ssize_t net_http_sendfile(HttpStream *st, sendfiledata *sfd) { |
|
243 ssize_t ret = 0; |
|
244 if(st->fd->sendfile) { |
|
245 ret = st->fd->sendfile(st->fd, sfd); |
|
246 } else { |
|
247 |
|
248 } |
|
249 |
|
250 return ret; |
|
251 } |
|
252 |
|
253 void net_http_close(HttpStream *st) { |
|
254 st->fd->close(st->fd); |
|
255 } |
|
256 |
|
257 void net_http_finish(HttpStream *st) { |
|
258 if(st->chunked_enc) { |
|
259 st->fd->write(st->fd, "0\r\n\r\n", 5); |
|
260 } |
|
261 } |
|
262 |
|
263 |
|
264 /* |
|
265 * SSLStream implementation |
|
266 */ |
|
267 |
|
268 IOStream* sslstream_new(pool_handle_t *pool, SSL *ssl) { |
208 SSLStream *st = pool_malloc(pool, sizeof(SSLStream)); |
269 SSLStream *st = pool_malloc(pool, sizeof(SSLStream)); |
209 st->st = ssl_io_funcs; |
270 st->st = ssl_io_funcs; |
210 st->ssl = ssl; |
271 st->ssl = ssl; |
211 return (IOStream*)st; |
272 return (IOStream*)st; |
212 } |
273 } |
266 } |
329 } |
267 |
330 |
268 ssize_t net_printf(SYS_NETFD fd, char *format, ...) { |
331 ssize_t net_printf(SYS_NETFD fd, char *format, ...) { |
269 va_list arg; |
332 va_list arg; |
270 va_start(arg, format); |
333 va_start(arg, format); |
271 char *buf; |
334 sstr_t buf = ucx_vasprintf(ucx_default_allocator(), format, arg); |
272 ssize_t len = vasprintf(&buf, format, arg); |
335 ssize_t r = net_write(fd, buf.ptr, buf.length); |
273 ssize_t r = net_write(fd, buf, len); |
336 free(buf.ptr); |
274 free(buf); |
|
275 return r; |
337 return r; |
276 } |
338 } |
277 |
339 |
278 ssize_t net_sendfile(SYS_NETFD fd, sendfiledata *sfd) { |
340 ssize_t net_sendfile(SYS_NETFD fd, sendfiledata *sfd) { |
279 NetIOStream *out = fd; |
341 IOStream *out = fd; |
280 if(out->st.sendfile && sfd->fd && sfd->fd->fd != -1) { |
342 if(out->sendfile && sfd->fd && sfd->fd->fd != -1) { |
281 ssize_t r = ((IOStream*)fd)->sendfile(fd, sfd); |
343 ssize_t r = out->sendfile(fd, sfd); |
282 if(r < 0) { |
344 if(r < 0) { |
283 return IO_ERROR; |
345 return IO_ERROR; |
284 } |
346 } |
285 } else { |
347 } else { |
286 // stream/file does not support sendfile |
348 // stream/file does not support sendfile |
287 // do regular copy |
349 // do regular copy |
288 char *buf = malloc(4096); |
350 return net_copy_file(out, sfd); |
289 if(!buf) { |
351 } |
290 // TODO: out of memory error |
352 return IO_ERROR; |
291 return IO_ERROR; |
353 } |
292 } |
354 |
293 char *header = (char*)sfd->header; |
355 // private |
294 int hlen = sfd->hlen; |
356 ssize_t net_copy_file(IOStream *fd, sendfiledata *sfd) { |
295 char *trailer = (char*)sfd->trailer; |
357 char *buf = malloc(4096); |
296 int tlen = sfd->tlen; |
358 if(!buf) { |
297 if(header == NULL) { |
359 // TODO: out of memory error |
298 hlen = 0; |
360 return IO_ERROR; |
299 } |
361 } |
300 if(trailer == NULL) { |
362 char *header = (char*)sfd->header; |
301 tlen = 0; |
363 int hlen = sfd->hlen; |
302 } |
364 char *trailer = (char*)sfd->trailer; |
303 |
365 int tlen = sfd->tlen; |
304 ssize_t r; |
366 if(header == NULL) { |
305 while(hlen > 0) { |
367 hlen = 0; |
306 r = out->st.write(fd, header, hlen); |
368 } |
307 header += r; |
369 if(trailer == NULL) { |
308 hlen -= r; |
370 tlen = 0; |
309 if(r <= 0) { |
371 } |
310 free(buf); |
372 |
311 return IO_ERROR; |
373 ssize_t r; |
312 } |
374 while(hlen > 0) { |
313 } |
375 r = fd->write(fd, header, hlen); |
314 |
376 header += r; |
315 if(system_lseek(sfd->fd, sfd->offset, SEEK_SET) == -1) { |
377 hlen -= r; |
|
378 if(r <= 0) { |
316 free(buf); |
379 free(buf); |
317 return IO_ERROR; |
380 return IO_ERROR; |
318 } |
381 } |
319 |
382 } |
320 size_t length = sfd->len; |
383 |
321 while(length > 0) { |
384 if(system_lseek(sfd->fd, sfd->offset, SEEK_SET) == -1) { |
322 if((r = system_fread(sfd->fd, buf, 4096)) <= 0) { |
|
323 break; |
|
324 } |
|
325 char *write_buf = buf; |
|
326 while(r > 0) { |
|
327 ssize_t w = out->st.write(fd, write_buf, r); |
|
328 r -= w; |
|
329 length -= w; |
|
330 write_buf += w; |
|
331 } |
|
332 } |
|
333 free(buf); |
385 free(buf); |
334 if(length > 0) { |
386 return IO_ERROR; |
|
387 } |
|
388 |
|
389 size_t length = sfd->len; |
|
390 while(length > 0) { |
|
391 if((r = system_fread(sfd->fd, buf, 4096)) <= 0) { |
|
392 break; |
|
393 } |
|
394 char *write_buf = buf; |
|
395 while(r > 0) { |
|
396 ssize_t w = fd->write(fd, write_buf, r); |
|
397 r -= w; |
|
398 length -= w; |
|
399 write_buf += w; |
|
400 } |
|
401 } |
|
402 free(buf); |
|
403 if(length > 0) { |
|
404 return IO_ERROR; |
|
405 } |
|
406 |
|
407 while(tlen > 0) { |
|
408 r = fd->write(fd, trailer, tlen); |
|
409 trailer += r; |
|
410 tlen -= r; |
|
411 if(r <= 0) { |
335 return IO_ERROR; |
412 return IO_ERROR; |
336 } |
413 } |
337 |
414 } |
338 while(tlen > 0) { |
415 |
339 r = out->st.write(fd, trailer, tlen); |
416 return sfd->hlen + sfd->len + sfd->tlen; |
340 trailer += r; |
|
341 tlen -= r; |
|
342 if(r <= 0) { |
|
343 return IO_ERROR; |
|
344 } |
|
345 } |
|
346 |
|
347 return sfd->hlen + sfd->len + sfd->tlen; |
|
348 } |
|
349 return IO_ERROR; |
|
350 } |
417 } |
351 |
418 |
352 int net_flush(SYS_NETFD sd) { |
419 int net_flush(SYS_NETFD sd) { |
353 // TODO: implement |
420 // TODO: implement |
354 return 0; |
421 return 0; |
356 |
423 |
357 void net_close(SYS_NETFD fd) { |
424 void net_close(SYS_NETFD fd) { |
358 ((IOStream*)fd)->close(fd); |
425 ((IOStream*)fd)->close(fd); |
359 } |
426 } |
360 |
427 |
|
428 // private |
361 void net_finish(SYS_NETFD fd) { |
429 void net_finish(SYS_NETFD fd) { |
362 ((IOStream*)fd)->finish(fd); |
430 ((IOStream*)fd)->finish(fd); |
363 } |
431 } |
364 |
|
365 |
|
366 /* iovec buffer */ |
|
367 iovec_buf_t *iovec_buf_create(pool_handle_t *pool) { |
|
368 iovec_buf_t *buf = pool_malloc(pool, sizeof(iovec_buf_t)); |
|
369 |
|
370 buf->pool = pool; |
|
371 buf->iov = pool_calloc(pool, 32, sizeof(struct iovec)); |
|
372 buf->maxiovec = 32; |
|
373 buf->iovctn = 0; |
|
374 |
|
375 return buf; |
|
376 } |
|
377 |
|
378 void iovec_buf_write(iovec_buf_t *io, void *buf, size_t nbyte) { |
|
379 if(io->iovctn >= io->maxiovec) { |
|
380 io->iov = pool_realloc( |
|
381 io->pool, |
|
382 io->iov, |
|
383 (io->maxiovec + 16) * sizeof(struct iovec)); |
|
384 } |
|
385 |
|
386 io->iov[io->iovctn].iov_base = buf; |
|
387 io->iov[io->iovctn].iov_len = nbyte; |
|
388 io->iovctn++; |
|
389 } |
|
390 |
|
391 ssize_t iovec_buf_flush(iovec_buf_t *io, int fd) { |
|
392 return writev(fd, io->iov, io->iovctn); |
|
393 } |
|
394 |
|
395 |
|
396 /* TODO: add asprintf to new file */ |
|
397 |
|
398 /* |
|
399 * asprintf implementation for Solaris 10 |
|
400 * source from OpenSolaris |
|
401 * file: /onnv/onnv-gate/usr/src/lib/libc/port/print/asprintf.c |
|
402 */ |
|
403 |
|
404 /* |
|
405 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. |
|
406 * Use is subject to license terms. |
|
407 */ |
|
408 |
|
409 /* |
|
410 * Copyright (c) 2004 Darren Tucker. |
|
411 * |
|
412 * Based originally on asprintf.c from OpenBSD: |
|
413 * Copyright (c) 1997 Todd C. Miller <Todd.Miller@courtesan.com> |
|
414 * |
|
415 * Permission to use, copy, modify, and distribute this software for any |
|
416 * purpose with or without fee is hereby granted, provided that the above |
|
417 * copyright notice and this permission notice appear in all copies. |
|
418 * |
|
419 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
|
420 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
|
421 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
|
422 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
|
423 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
|
424 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
|
425 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
426 */ |
|
427 |
|
428 #ifdef __SunOS_5_10 |
|
429 |
|
430 #define INIT_SZ 128 |
|
431 |
|
432 /* VARARGS2 */ |
|
433 int |
|
434 vasprintf(char **str, const char *format, va_list ap) |
|
435 { |
|
436 char string[INIT_SZ]; |
|
437 char *newstr; |
|
438 int ret; |
|
439 size_t len; |
|
440 |
|
441 *str = NULL; |
|
442 ret = vsnprintf(string, INIT_SZ, format, ap); |
|
443 if (ret < 0) /* retain the value of errno from vsnprintf() */ |
|
444 return (-1); |
|
445 if (ret < INIT_SZ) { |
|
446 len = ret + 1; |
|
447 if ((newstr = malloc(len)) == NULL) |
|
448 return (-1); /* retain errno from malloc() */ |
|
449 (void) strlcpy(newstr, string, len); |
|
450 *str = newstr; |
|
451 return (ret); |
|
452 } |
|
453 /* |
|
454 * We will perform this loop more than once only if some other |
|
455 * thread modifies one of the vasprintf() arguments after our |
|
456 * previous call to vsnprintf(). |
|
457 */ |
|
458 for (;;) { |
|
459 if (ret == INT_MAX) { /* Bad length */ |
|
460 errno = ENOMEM; |
|
461 return (-1); |
|
462 } |
|
463 len = ret + 1; |
|
464 if ((newstr = malloc(len)) == NULL) |
|
465 return (-1); /* retain errno from malloc() */ |
|
466 ret = vsnprintf(newstr, len, format, ap); |
|
467 if (ret < 0) { /* retain errno from vsnprintf() */ |
|
468 free(newstr); |
|
469 return (-1); |
|
470 } |
|
471 if (ret < len) { |
|
472 *str = newstr; |
|
473 return (ret); |
|
474 } |
|
475 free(newstr); |
|
476 } |
|
477 } |
|
478 |
|
479 int |
|
480 asprintf(char **str, const char *format, ...) |
|
481 { |
|
482 va_list ap; |
|
483 int ret; |
|
484 |
|
485 *str = NULL; |
|
486 va_start(ap, format); |
|
487 ret = vasprintf(str, format, ap); |
|
488 va_end(ap); |
|
489 |
|
490 return (ret); |
|
491 } |
|
492 |
|
493 #endif |
|