| /* |
| * Copyright (C) Tildeslash Ltd. All rights reserved. |
| * Copyright (c) 1994,1995,1996,1997 by David R. Hanson. |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Affero General Public License version 3. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Affero General Public License for more details. |
| * |
| * You should have received a copy of the GNU Affero General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * |
| * In addition, as a special exception, the copyright holders give |
| * permission to link the code of portions of this program with the |
| * OpenSSL library under certain conditions as described in each |
| * individual source file, and distribute linked combinations |
| * including the two. |
| * |
| * You must obey the GNU Affero General Public License in all respects |
| * for all of the code used other than OpenSSL. |
| */ |
| |
| |
| #include "Config.h" |
| |
| #include <stdio.h> |
| #include <errno.h> |
| #include <stdarg.h> |
| #include <float.h> |
| #include <limits.h> |
| #include <ctype.h> |
| #ifdef OPENBSD |
| #include <sys/uio.h> |
| #endif |
| |
| #include "system/Net.h" |
| #include "OutputStream.h" |
| |
| |
| /** |
| * Implementation of the OutputStream interface. The printf implementation is |
| * based on "Fmt" from David Hanson's excellent CII library. |
| * |
| * @see http://www.mmonit.com/ |
| * @file |
| */ |
| |
| |
| /* ----------------------------------------------------------- Definitions */ |
| |
| |
| // One TCP frame data size |
| #define BUFFER_SIZE 1500 |
| |
| #define T OutputStream_T |
| struct T { |
| int fd; |
| time_t timeout; |
| uchar_t *limit; |
| uchar_t *length; |
| boolean_t isclosed; |
| int sessionWritten; |
| long long int bytesWritten; |
| uchar_t buffer[BUFFER_SIZE + 1]; |
| }; |
| typedef struct va_list_box {va_list ap;} va_list_box; |
| typedef void (*fmt_t)(T S, int code, va_list_box *box, unsigned char flags[256], int width, int precision); |
| |
| |
| /* --------------------------------------------------------------- Private */ |
| |
| |
| /* Write the output buffer to the underlying file descriptor */ |
| static int flush(T S) { |
| if (S->isclosed) |
| return -1; |
| errno = 0; |
| int n = (int)Net_write(S->fd, S->buffer, S->length - S->buffer, S->timeout); |
| if (n > 0) { |
| S->bytesWritten += n; |
| if ((S->buffer + n) < S->length) { // Did not write all, shift remaining to the front of buffer |
| // TODO: Instead of shifting, use buffer as a circular buffer |
| memmove(S->buffer, S->buffer + n, S->length - (S->buffer + n)); |
| } |
| S->length -= n; |
| } else if (n < 0) { |
| n = -1; |
| S->isclosed = true; |
| } else if (! (errno == EAGAIN || errno == EWOULDBLOCK)) // peer closed connection |
| n = -1; |
| return n; |
| } |
| |
| |
| /* Write a single byte. The byte is written as an int in the range 0 to 255. |
| Returns the byte written, or -1 if a write error occurred */ |
| static inline int write_byte(T S, uchar_t byte) { |
| if (S->length == S->limit) { |
| if (flush(S) <= 0) |
| return -1; |
| } |
| *S->length++ = byte; |
| S->sessionWritten++; |
| return byte; |
| } |
| |
| |
| /* ------------------------------------------------------- Format handlers */ |
| |
| |
| #define pad(n,c) do { int nn = (n); while (nn-- > 0) write_byte(S, (c)); } while (0) |
| static void putd(T S, const char *str, int len, unsigned char flags[], int width, int precision) { |
| int sign; |
| assert(str); |
| assert(len >= 0); |
| assert(flags); |
| if (width == INT_MIN) |
| width = 0; |
| if (width < 0) { |
| flags['-'] = 1; |
| width = -width; |
| } |
| if (precision >= 0) |
| flags['0'] = 0; |
| if (len > 0 && (*str == '-' || *str == '+')) { |
| sign = *str++; |
| len--; |
| } else if (flags['+']) |
| sign = '+'; |
| else if (flags[' ']) |
| sign = ' '; |
| else |
| sign = 0; |
| { int n; |
| if (precision < 0) |
| precision = 1; |
| if (len < precision) |
| n = precision; |
| else if (precision == 0 && len == 1 && str[0] == '0') |
| n = 0; |
| else |
| n = len; |
| if (sign) |
| n++; |
| if (flags['-']) { |
| if (sign) |
| write_byte(S, sign); |
| } else if (flags['0']) { |
| if (sign) |
| write_byte(S, sign); |
| pad(width - n, '0'); |
| } else { |
| pad(width - n, ' '); |
| if (sign) |
| write_byte(S, sign); |
| } |
| pad(precision - len, '0'); |
| for (int i = 0; i < len; i++) |
| write_byte(S, (uchar_t)*str++); |
| if (flags['-']) |
| pad(width - n, ' '); |
| } |
| } |
| |
| |
| static void cvt_s(T S, int code, va_list_box *box, unsigned char flags[], int width, int precision) { |
| uchar_t *str = va_arg(box->ap, uchar_t *); |
| assert(str); |
| int len = (int)strlen((char*)str); |
| assert(len >= 0); |
| assert(flags); |
| if (width == INT_MIN) |
| width = 0; |
| if (width < 0) { |
| flags['-'] = 1; |
| width = -width; |
| } |
| if (precision >= 0) |
| flags['0'] = 0; |
| if (precision >= 0 && precision < len) |
| len = precision; |
| if (!flags['-']) |
| pad(width - len, ' '); |
| for (int i = 0; i < len; i++) |
| write_byte(S, *str++); |
| if (flags['-']) |
| pad(width - len, ' '); |
| } |
| |
| |
| static void cvt_d(T S, int code, va_list_box *box, unsigned char flags[], int width, int precision) { |
| int val = va_arg(box->ap, int); |
| unsigned int m; |
| char buf[43]; |
| char *p = buf + sizeof buf; |
| if (val == INT_MIN) |
| m = INT_MAX + 1UL; |
| else if (val < 0) |
| m = -val; |
| else |
| m = val; |
| do |
| *--p = m%10 + '0'; |
| while ((m /= 10) > 0); |
| if (val < 0) |
| *--p = '-'; |
| putd(S, p, (int)((buf + sizeof buf) - p), flags, width, precision); |
| } |
| |
| |
| static void cvt_l(T S, int code, va_list_box *box, unsigned char flags[], int width, int precision) { |
| long val = va_arg(box->ap, long); |
| unsigned long m; |
| char buf[43]; |
| char *p = buf + sizeof buf; |
| if (val == LONG_MIN) |
| m = LONG_MAX + 1UL; |
| else if (val < 0) |
| m = -val; |
| else |
| m = val; |
| do |
| *--p = m%10 + '0'; |
| while ((m /= 10) > 0); |
| if (val < 0) |
| *--p = '-'; |
| putd(S, p, (int)((buf + sizeof buf) - p), flags, width, precision); |
| } |
| |
| |
| static void cvt_u(T S, int code, va_list_box *box, unsigned char flags[], int width, int precision) { |
| unsigned long m = va_arg(box->ap, unsigned long); |
| char buf[43]; |
| char *p = buf + sizeof buf; |
| do |
| *--p = m%10 + '0'; |
| while ((m /= 10) > 0); |
| putd(S, p, (int)((buf + sizeof buf) - p), flags, width, precision); |
| } |
| |
| |
| static void cvt_o(T S, int code, va_list_box *box, unsigned char flags[], int width, int precision) { |
| unsigned long m = va_arg(box->ap, unsigned long); |
| char buf[43]; |
| char *p = buf + sizeof buf; |
| do |
| *--p = (m&0x7) + '0'; |
| while ((m>>= 3) != 0); |
| putd(S, p, (int)((buf + sizeof buf) - p), flags, width, precision); |
| } |
| |
| |
| static void cvt_x(T S, int code, va_list_box *box, unsigned char flags[], int width, int precision) { |
| unsigned long m = va_arg(box->ap, unsigned long); |
| char buf[43]; |
| char *p = buf + sizeof buf; |
| do |
| *--p = "0123456789abcdef"[m&0xf]; |
| while ((m>>= 4) != 0); |
| putd(S, p, (int)((buf + sizeof buf) - p), flags, width, precision); |
| } |
| |
| |
| static void cvt_p(T S, int code, va_list_box *box, unsigned char flags[], int width, int precision) { |
| unsigned long m = (unsigned long)va_arg(box->ap, void*); |
| char buf[43]; |
| char *p = buf + sizeof buf; |
| precision = INT_MIN; |
| do |
| *--p = "0123456789abcdef"[m&0xf]; |
| while ((m>>= 4) != 0); |
| putd(S, p, (int)((buf + sizeof buf) - p), flags, width, precision); |
| } |
| |
| |
| static void cvt_c(T S, int code, va_list_box *box, unsigned char flags[], int width, int precision) { |
| if (width == INT_MIN) |
| width = 0; |
| if (width < 0) { |
| flags['-'] = 1; |
| width = -width; |
| } |
| if (!flags['-']) |
| pad(width - 1, ' '); |
| write_byte(S, va_arg(box->ap, int)); |
| if (flags['-']) |
| pad(width - 1, ' '); |
| } |
| |
| |
| static void cvt_f(T S, int code, va_list_box *box, unsigned char flags[], int width, int precision) { |
| char buf[DBL_MAX_10_EXP+1+1+99+1]; |
| if (precision < 0) |
| precision = 6; |
| if (code == 'g' && precision == 0) |
| precision = 1; |
| { |
| char fmt[] = "%.dd?"; |
| assert(precision <= 99); |
| fmt[4] = code; |
| fmt[3] = precision%10 + '0'; |
| fmt[2] = (precision/10)%10 + '0'; |
| sprintf(buf, fmt, va_arg(box->ap, double)); |
| } |
| putd(S, buf, (int)strlen(buf), flags, width, precision); |
| } |
| |
| |
| static char *Fmt_flags = "-+ 0"; |
| static fmt_t cvt[256] = { |
| /* 0- 7 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| /* 8- 15 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| /* 16- 23 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| /* 24- 31 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| /* 32- 39 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| /* 40- 47 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| /* 48- 55 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| /* 56- 63 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| /* 64- 71 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| /* 72- 79 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| /* 80- 87 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| /* 88- 95 */ 0, 0, 0, 0, 0, 0, 0, 0, |
| /* 96-103 */ 0, 0, 0, cvt_c, cvt_d, cvt_f, cvt_f, cvt_f, |
| /* 104-111 */ 0, cvt_d, 0, 0, cvt_l, 0, 0, cvt_o, |
| /* 112-119 */ cvt_p, 0, 0, cvt_s, 0, cvt_u, 0, 0, |
| /* 120-127 */ cvt_x, 0, 0, 0, 0, 0, 0, 0 |
| }; |
| |
| |
| /* ---------------------------------------------------------------- Public */ |
| |
| |
| T OutputStream_new(int descriptor) { |
| T S; |
| NEW(S); |
| S->fd = descriptor; |
| S->timeout = NET_WRITE_TIMEOUT; |
| S->length = S->buffer; |
| S->limit = S->buffer + BUFFER_SIZE; |
| return S; |
| } |
| |
| |
| void OutputStream_free(T *S) { |
| assert(S && *S); |
| OutputStream_flush(*S); |
| FREE(*S); |
| } |
| |
| |
| /* ------------------------------------------------------------ Properties */ |
| |
| |
| int OutputStream_getDescriptor(T S) { |
| assert(S); |
| return S->fd; |
| } |
| |
| |
| int OutputStream_buffered(T S) { |
| assert(S); |
| return (int)(S->length - S->buffer); |
| } |
| |
| |
| void OutputStream_setTimeout(T S, time_t timeout) { |
| assert(S); |
| assert(timeout >= 0); |
| S->timeout = timeout; |
| } |
| |
| |
| time_t OutputStream_getTimeout(T S) { |
| assert(S); |
| return S->timeout; |
| } |
| |
| |
| int OutputStream_isClosed(T S) { |
| assert(S); |
| return S->isclosed; |
| } |
| |
| |
| long long int OutputStream_getBytesWritten(T S) { |
| assert(S); |
| return S->bytesWritten; |
| } |
| |
| |
| /* ---------------------------------------------------------------- Public */ |
| |
| |
| int OutputStream_print(T S, const char *s, ...) { |
| assert(S); |
| assert(s); |
| va_list ap; |
| va_start(ap, s); |
| int n = OutputStream_vprint(S, s, ap); |
| va_end(ap); |
| return n; |
| } |
| |
| |
| int OutputStream_vprint(T S, const char *fmt, va_list ap) { |
| assert(S); |
| assert(fmt); |
| va_list_box box; |
| va_copy(box.ap, ap); |
| S->sessionWritten = 0; |
| while (*fmt) { |
| if (*fmt != '%' || *++fmt == '%') |
| write_byte(S, *fmt++); |
| else { |
| uchar_t c, flags[256] = {0}; |
| int width = INT_MIN, precision = INT_MIN; |
| for (c = *fmt; c && strchr(Fmt_flags, c); c = *++fmt) { |
| assert(flags[c] < 255); |
| flags[c]++; |
| } |
| if (*fmt == '*' || isdigit(*fmt)) { |
| int n; |
| if (*fmt == '*') { |
| n = va_arg(box.ap, int); |
| assert(n != INT_MIN); |
| fmt++; |
| } else |
| for (n = 0; isdigit(*fmt); fmt++) { |
| int d = *fmt - '0'; |
| assert(n <= (INT_MAX - d)/10); |
| n = 10*n + d; |
| } |
| width = n; |
| } |
| if (*fmt == '.' && (*++fmt == '*' || isdigit(*fmt))) { |
| int n; |
| if (*fmt == '*') { |
| n = va_arg(box.ap, int); |
| assert(n != INT_MIN); |
| fmt++; |
| } else |
| for (n = 0; isdigit(*fmt); fmt++) { |
| int d = *fmt - '0'; |
| assert(n <= (INT_MAX - d)/10); |
| n = 10*n + d; |
| } |
| precision = n; |
| } |
| c = *fmt++; |
| if (c == 'l') { |
| c = *fmt++; |
| if (c == 'd' || c == 'i') |
| c = 'l'; |
| } |
| assert(cvt[c]); |
| cvt[c](S, c, &box, flags, width, precision); |
| } |
| } |
| va_end(box.ap); |
| return S->isclosed ? -1 : S->sessionWritten; |
| } |
| |
| |
| int OutputStream_write(T S, const void *b, int size) { |
| assert(S); |
| assert(b); |
| S->sessionWritten = 0; |
| uchar_t *t = (uchar_t*)b; |
| while ((size-- > 0) && (write_byte(S, *t++) != -1)); |
| return S->isclosed ? -1 : S->sessionWritten; |
| } |
| |
| |
| int OutputStream_flush(T S) { |
| assert(S); |
| if (S->length > S->buffer) |
| return flush(S); |
| return 0; |
| } |
| |
| |
| void OutputStream_clear(T S) { |
| assert(S); |
| S->length = S->buffer; |
| } |
| |