/* $Id$ */ /* Copyright (c) 2004-2020 Pierre Pronchery */ /* This file is part of DeforaOS System libc */ /* All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "fcntl.h" #include "unistd.h" #include "stdarg.h" #include "stdint.h" #include "stdlib.h" #include "string.h" #include "ctype.h" #include "limits.h" #include "syscalls.h" #include "errno.h" #include "stdio.h" #include "start.h" #ifndef min # define min(a, b) ((a) > (b) ? (b) : (a)) #endif /* private */ /* types */ typedef enum _FILEDirection { FD_READ = 0, FD_WRITE = 1 } FILEDirection; struct _FILE { int fd; int flags; unsigned char _buf[BUFSIZ]; unsigned char * buf; unsigned char * mbuf; size_t bufsiz; size_t len; size_t pos; char eof; char error; int fbuf; FILEDirection dir; }; typedef int (*print_func)(void * dest, size_t size, const char * buf); typedef char (*scan_func)(char const * buf, size_t * i); /* prototypes */ static int _fopen_mode(char const * mode); static FILE * _fopen_new(int fd, int flags, int fbuf); static int _vprintf(print_func func, void * dest, size_t size, char const * format, va_list arg); static int _vscanf(scan_func func, char const * string, char const * format, va_list arg); /* public */ /* variables */ static FILE _stdin = { STDIN_FILENO, O_RDONLY, { 0 }, _stdin._buf, NULL, sizeof(_stdin._buf), 0, 0, 0, 0, _IOFBF, FD_READ }; FILE * stdin = &_stdin; static FILE _stdout = { STDOUT_FILENO, O_WRONLY, { 0 }, _stdout._buf, NULL, sizeof(_stdout._buf), 0, 0, 0, 0, _IOLBF, FD_WRITE }; FILE * stdout = &_stdout; static FILE _stderr = { STDERR_FILENO, O_WRONLY, { 0 }, _stderr._buf, NULL, sizeof(_stderr._buf), 0, 0, 0, 0, _IONBF, FD_WRITE }; FILE * stderr = &_stderr; /* functions */ /* clearerr */ void clearerr(FILE * file) { file->eof = 0; file->error = 0; } /* ctermid */ char * ctermid(char * buf) { static char const devtty[] = "/dev/tty"; static char tty[L_ctermid]; if(buf == NULL) buf = tty; /* XXX may overflow */ if(snprintf(buf, L_ctermid, "%s", devtty) >= L_ctermid) return NULL; return buf; } /* fclose */ int fclose(FILE * file) { int ret; ret = fflush(file); if(file->fd >= 0) if(close(file->fd) != 0) ret = -1; free(file->mbuf); free(file); return ret; } /* fdopen */ FILE * fdopen(int fd, char const * mode) { return _fopen_new(fd, _fopen_mode(mode), isatty(fd) ? _IONBF : _IOFBF); } /* feof */ int feof(FILE * file) { return file->eof; } /* ferror */ int ferror(FILE * file) { return file->error; } /* fflush */ int fflush(FILE * file) { ssize_t w; if(file == NULL) { errno = ENOSYS; /* FIXME implement */ return EOF; } if(file->dir == FD_WRITE) { for(; file->pos < file->len; file->pos += w) if((w = write(file->fd, &file->buf[file->pos], file->len - file->pos)) < 0) { file->error = 1; return EOF; } } else if(file->dir != FD_READ) return EOF; file->pos = 0; file->len = 0; return 0; } /* fgetc */ int fgetc(FILE * file) { unsigned char c; if(fread(&c, sizeof(c), 1, file) != 1) return EOF; return c; } /* fgets */ char * fgets(char * str, int size, FILE * file) { int i; int c; if(size < 0) { errno = EINVAL; return NULL; } for(i = 0; i + 1 < size; i++) { if((c = fgetc(file)) == EOF) { if(i == 0 || !feof(file)) return NULL; break; } str[i] = c; if(c == '\n') break; } if(++i >= size) return NULL; str[i] = '\0'; return str; } /* fileno */ int fileno(FILE * file) { if(file == NULL) { errno = EBADF; return -1; } return file->fd; } /* flock */ void flockfile(FILE * file) { struct flock fl; fl.l_start = 0; fl.l_len = 0; fl.l_pid = 0; fl.l_type = F_RDLCK | F_WRLCK; fl.l_whence = SEEK_SET; fcntl(file->fd, F_SETLKW, &fl); } /* fmemopen */ FILE * fmemopen(void * buffer, size_t size, char const * mode) { FILE * file; int flags; /* FIXME subsequent file operations untested */ if((flags = _fopen_mode(mode)) == -1) return NULL; if((file = _fopen_new(-1, flags, _IONBF)) == NULL) return NULL; if(buffer != NULL) file->buf = buffer; else { if(size == 0) file->buf = NULL; else if((file->mbuf = calloc(1, size)) == NULL) { fclose(file); return NULL; } else file->buf = file->mbuf; } file->bufsiz = size; file->len = size; return file; } /* fopen */ FILE * fopen(char const * path, char const * mode) { FILE * file; int flags; int fd; if((flags = _fopen_mode(mode)) == -1 || (fd = open(path, flags, 0777)) < 0) return NULL; if((file = _fopen_new(fd, flags, isatty(fd) ? _IONBF : _IOFBF)) == NULL) { close(fd); return NULL; } return file; } /* fprintf */ int fprintf(FILE * file, char const * format, ...) { int ret; va_list arg; va_start(arg, format); ret = vfprintf(file, format, arg); va_end(arg); return ret; } /* fputc */ int fputc(int c, FILE * file) { unsigned char p = c; if(fwrite(&p, sizeof(char), 1, file) != 1) return EOF; return p; } /* fputs */ int fputs(char const * str, FILE * file) { size_t len; if((len = strlen(str)) == 0) return 0; return fwrite(str, sizeof(char), len, file) == len ? 0 : EOF; } /* fread */ size_t fread(void * ptr, size_t size, size_t nb, FILE * file) { char * p = (char *)ptr; size_t i; size_t j; size_t len; ssize_t r; if(file->dir != FD_READ) { if(fflush(file) != 0) return 0; else file->dir = FD_READ; } for(i = 0; i < nb; i++) for(j = 0; j < size; j += len) { if(file->pos == file->len) { if((r = read(file->fd, file->buf, file->bufsiz)) < 0) { file->error = 1; return i; } else if(r == 0) { file->eof = 1; return i; } file->pos = 0; file->len = r; } len = min(file->len - file->pos, size - j); memcpy(p, &file->buf[file->pos], len); file->pos += len; p += len; } return i; } /* freopen */ FILE * freopen(char const * path, char const * mode, FILE * file) { int flags; fflush(file); if(path == NULL) { if((flags = _fopen_mode(mode)) == -1) { if(fcntl(file->fd, F_SETFL, flags) == -1) return NULL; file->flags = flags; file->dir = flags & O_WRONLY ? FD_WRITE : FD_READ; return file; } } close(file->fd); clearerr(file); if((flags = _fopen_mode(mode)) == -1 || (file->fd = open(path, file->flags, 0777)) < 0) return NULL; file->flags = flags; file->dir = flags & O_WRONLY ? FD_WRITE : FD_READ; file->fbuf = isatty(file->fd) ? _IONBF : _IOFBF; return file; } /* fscanf */ int fscanf(FILE * file, char const * format, ...) { int ret; va_list arg; va_start(arg, format); ret = vfscanf(file, format, arg); va_end(arg); return ret; } /* fseek */ int fseek(FILE * file, long offset, int whence) { return fseeko(file, offset, whence); } /* fseeko */ int fseeko(FILE * file, off_t offset, int whence) { if(whence != SEEK_CUR && whence != SEEK_END && whence != SEEK_SET) { errno = EINVAL; return -1; } if(fflush(file) != 0) return -1; if(lseek(file->fd, offset, whence) == -1) return -1; /* reset the status */ file->eof = (whence == SEEK_END && offset == 0) ? 1 : 0; file->error = 0; return 0; } /* ftell */ long ftell(FILE * file) { long ret; if((ret = lseek(file->fd, SEEK_CUR, 0)) < 0) return -1; /* FIXME not tested */ return ret - file->len + file->pos; } /* ftrylock */ int ftrylock(FILE * file) { struct flock fl; fl.l_start = 0; fl.l_len = 0; fl.l_pid = 0; fl.l_type = F_RDLCK | F_WRLCK; fl.l_whence = SEEK_SET; return fcntl(file->fd, F_SETLK, &fl); } /* funlockfile */ void funlockfile(FILE * file) { struct flock fl; fl.l_start = 0; fl.l_len = 0; fl.l_pid = 0; fl.l_type = F_UNLCK; fl.l_whence = SEEK_SET; fcntl(file->fd, F_SETLKW, &fl); } /* fwrite */ size_t fwrite(void const * ptr, size_t size, size_t nb, FILE * file) { char const * p = (char const *)ptr; size_t i; size_t j; size_t len; ssize_t w; size_t x; if(file->dir != FD_WRITE) { if(fflush(file) != 0) return 0; file->dir = FD_WRITE; } for(i = 0; i < nb; i++) for(j = 0; j < size; j+=len) { len = min(file->bufsiz - file->len, size - j); memcpy(&file->buf[file->len], p, len); p += len; file->len += len; if(file->len != file->bufsiz) /* buffer is not full */ continue; x = file->bufsiz - file->pos; if((w = write(file->fd, &file->buf[file->pos], x)) < 0) { file->error = 1; return i; } if((size_t)w != x) /* XXX cast */ file->pos = w; /* buffer is not empty */ else /* buffer is empty */ { file->pos = 0; file->len = 0; } } if(file->fbuf == _IOFBF) /* fully buffered */ return nb; if(file->fbuf == _IOLBF) /* line buffered */ for(j = file->len; j > file->pos; j--) { if(file->buf[j - 1] == '\n') break; } else /* unbuffered */ j = file->len; for(; file->pos < j; file->pos += w) /* empty buffer if necessary */ if((w = write(file->fd, &file->buf[file->pos], j - file->pos)) < 0) { file->error = 1; break; /* XXX should we otherwise report the error? */ } return nb; } /* getc */ int getc(FILE * file) { return fgetc(file); } /* getc_unlocked */ int getc_unlocked(FILE * file) { return getc(file); } /* getchar */ int getchar(void) { return fgetc(stdin); } /* getchar_unlocked */ int getchar_unlocked(void) { return getchar(); } /* pclose */ int pclose(FILE * stream) { return fclose(stream); } /* perror */ void perror(char const * s) { if(s != NULL && *s != '\0') fprintf(stderr, "%s%s", s, ": "); /* strerror() never returns NULL */ fputs(strerror(errno), stderr); fputc('\n', stderr); } /* popen */ static void _popen_child(char const * command, int flags, int * fd); FILE * popen(char const * command, char const * mode) { int flags; pid_t pid; int fd[2]; if((flags = _fopen_mode(mode)) == -1) return NULL; if(pipe(fd) != 0) return NULL; if((pid = fork()) == -1) { close(fd[0]); close(fd[1]); return NULL; } if(pid == 0) /* child */ _popen_child(command, flags, fd); if(flags & O_WRONLY) close(fd[0]); else close(fd[1]); return _fopen_new(flags & O_WRONLY ? fd[1] : fd[0], flags, _IONBF); } static void _popen_child(char const * command, int flags, int * fd) { if(flags & O_WRONLY) { close(fd[1]); if(dup2(fd[0], 0) == -1) exit(127); } else { close(fd[0]); if(dup2(fd[1], 1) == -1) exit(127); } execlp("/bin/sh", "sh", "-c", command, NULL); exit(127); } /* printf */ int printf(char const * format, ...) { int ret; va_list arg; va_start(arg, format); ret = vfprintf(stdout, format, arg); va_end(arg); return ret; } /* putc */ int putc(int c, FILE * file) { return fputc(c, file); } /* putc_unlocked */ int putc_unlocked(int c, FILE * file) { return putc(c, file); } /* putchar */ int putchar(int c) { return fputc(c, stdout); } /* putchar_unlocked */ int putchar_unlocked(int c) { return putchar(c); } /* puts */ int puts(char const * str) { size_t i; if(str == NULL) /* XXX be nice and do not crash */ str = "(null)"; i = strlen(str); if(fwrite(str, sizeof(char), i, stdout) != i) return EOF; fputc('\n', stdout); return i <= INT_MAX ? i : INT_MAX; } /* remove */ int remove(char const * path) { if(unlink(path) == 0) return 0; if(errno == EISDIR && rmdir(path) == 0) return 0; return -1; } /* rename */ #ifndef SYS_rename # warning Unsupported platform: rename() is missing int rename(char const * from, char const * to) { errno = ENOSYS; return -1; } #endif /* renameat */ #ifndef SYS_renameat # warning Unsupported platform: renameat() is missing int renameat(int fromfd, char const * from, int tofd, char const * to) { errno = ENOSYS; return -1; } #endif /* rewind */ void rewind(FILE * file) { fseeko(file, 0, SEEK_SET); } /* scanf */ int scanf(char const * format, ...) { int ret; va_list arg; va_start(arg, format); ret = vfscanf(stdin, format, arg); va_end(arg); return ret; } /* setbuf */ void setbuf(FILE * file, char * buf) { setvbuf(file, buf, (buf != NULL) ? _IOFBF : _IONBF, BUFSIZ); } /* setvbuf */ int setvbuf(FILE * file, char * buf, int type, size_t size) { if(file->fd < 0 || file->len > 0) { errno = EBADF; return -1; } file->buf = (unsigned char *)buf; file->fbuf = type; file->bufsiz = size; return 0; } /* snprintf */ int snprintf(char * str, size_t size, char const * format, ...) { va_list arg; int ret; va_start(arg, format); ret = vsnprintf(str, size, format, arg); va_end(arg); return ret; } /* sprintf */ int sprintf(char * str, char const * format, ...) { va_list arg; int ret; va_start(arg, format); ret = vsprintf(str, format, arg); va_end(arg); return ret; } /* sscanf */ int sscanf(char const * str, char const * format, ...) { int ret; va_list arg; va_start(arg, format); ret = vsscanf(str, format, arg); va_end(arg); return ret; } /* tempnam */ char * tempnam(char const * dir, char const * prefix) { char const * p; size_t len; char * template; if((p = getenv("TMPDIR")) != NULL) dir = p; else if(dir == NULL) dir = P_tmpdir; if(dir == NULL) dir = "/tmp"; if(prefix == NULL) prefix = "tmp.XXXXXX"; len = strlen(dir) + strlen(prefix) + 2; if((template = malloc(len)) == NULL) return NULL; snprintf(template, len, "%s/%s", dir, prefix); if(mktemp(template) != NULL) return template; free(template); return NULL; } /* tmpfile */ FILE * tmpfile(void) { char template[] = P_tmpdir "/tmp.XXXXXX"; int fd; FILE * file; if((fd = mkstemp(template)) < 0) return NULL; if(unlink(template) != 0 || (file = fdopen(fd, "w+")) == NULL) { close(fd); return NULL; } return file; } /* tmpnam */ char * tmpnam(char * str) { static char template[] = P_tmpdir "/tmp.XXXXXX"; if(str == NULL) return mktemp(template); if(snprintf(str, L_tmpnam, "%s", str) >= L_tmpnam) return NULL; return mktemp(str); } /* ungetc */ int ungetc(int c, FILE * file) { if(c == EOF || c < 0) { errno = EINVAL; return EOF; } if(file->len == sizeof(file->buf)) { errno = ENOBUFS; return EOF; } memmove(&file->buf[file->pos + 1], &file->buf[file->pos], file->len - file->pos); file->buf[file->pos] = c; /* XXX c may be silently truncated */ file->len++; return file->buf[file->pos]; } /* vfprintf */ static int _fprint(void * dest, size_t size, const char * buf); int vfprintf(FILE * file, char const * format, va_list arg) { return _vprintf(_fprint, file, ~0, format, arg); } static int _fprint(void * dest, size_t size, char const buf[]) { FILE * file = dest; return (fwrite(buf, sizeof(char), size, file) == size) ? 0 : -1; } /* vfscanf */ int vfscanf(FILE * file, char const * format, va_list arg) { /* FIXME implement */ errno = ENOSYS; return -1; } /* vprintf */ int vprintf(char const * format, va_list arg) { return _vprintf(_fprint, stdout, ~0, format, arg); } /* vscanf */ int vscanf(char const * format, va_list arg) { return vfscanf(stdin, format, arg); } /* vsnprintf */ static int _sprint(void * dest, size_t size, const char * buf); int vsnprintf(char * str, size_t size, char const * format, va_list arg) { int ret; char * p = str; size_t i; if((ret = _vprintf(_sprint, &p, size, format, arg)) < 0) return ret; i = ret; if(i < size) str[ret] = '\0'; else if(size > 0) str[size - 1] = '\0'; return ret; } static int _sprint(void * dest, size_t size, char const buf[]) { char ** str = dest; if(*str == NULL || size == 0) return 0; strncpy(*str, buf, size); *str += size; return 0; } /* vsprintf */ int vsprintf(char * str, char const * format, va_list arg) { int ret; size_t size = (str != NULL) ? -1 : 0; if((ret = _vprintf(_sprint, &str, size, format, arg)) < 0) return ret; if(str != NULL) str[ret] = '\0'; return ret; } /* vsscanf */ int vsscanf(char const * str, char const * format, va_list arg) { return _vscanf(NULL, str, format, arg); } /* protected */ /* start_stdio */ void __start_stdio(void) { if(getenv("PS1") == NULL) _stdout.flags = _IONBF; } /* private */ /* functions */ /* fopen_mode */ /* PRE * POST * -1 error * else corresponding mode */ static int _fopen_mode(char const * mode) { int flags; if(*mode == 'r') { flags = O_RDONLY; if(*(++mode) == 'b') ++mode; if(*mode == '+' && ++mode) flags = O_RDWR; } else if(*mode == 'w') { flags = O_WRONLY | O_CREAT | O_TRUNC; if(*(++mode) == 'b') ++mode; if(*mode == '+' && ++mode) flags = O_RDWR | O_CREAT | O_TRUNC; } else if(*mode == 'a') { flags = O_WRONLY | O_APPEND | O_CREAT; if(*(++mode) == 'b') ++mode; if(*mode == '+' && ++mode) flags = O_RDWR | O_APPEND | O_CREAT; } else { errno = EINVAL; return -1; } for(;; mode++) if(*mode == 'x') flags |= O_EXCL; else if(*mode != 'b') break; if(*mode != '\0') { errno = EINVAL; return -1; } return flags; } /* fopen_new */ static FILE * _fopen_new(int fd, int flags, int fbuf) { FILE * file; if(flags == -1) return NULL; if((file = malloc(sizeof(*file))) == NULL) return NULL; file->flags = flags; file->buf = file->_buf; file->mbuf = NULL; file->bufsiz = sizeof(file->_buf); file->fd = fd; file->len = 0; file->pos = 0; file->eof = 0; file->error = 0; file->dir = (file->flags & O_WRONLY) ? FD_WRITE : FD_READ; file->fbuf = fbuf; return file; } /* vprintf */ typedef struct _print_args { int ret; int flags; size_t width; size_t precision; int shrt; print_func func; void * dest; size_t * size; } print_args; static int _vfprintf_do(print_args * args, char const * buf, size_t len); static int _vfprintf_do_do(print_args * args, char const * buf, size_t len); #define FLAGS_HASH 0x01 #define FLAGS_MINUS 0x02 #define FLAGS_PLUS 0x04 #define FLAGS_SPACE 0x08 #define FLAGS_ZERO 0x10 static int _vprintf_flags(char const * p, size_t * i); static size_t _vprintf_width(char const * p, size_t * i); static size_t _vprintf_precision(char const * p, size_t * i); static void _format_lutoa(char * dest, unsigned long n, size_t base, int upper); static int _format_c(print_args * args, char const * chrp); static int _format_d(print_args * args, long long * ptr); static int _format_f(print_args * args, double * ptr); static int _format_lf(print_args * args, long double * ptr); static int _format_o(print_args * args, unsigned long long * ptr); static int _format_s(print_args * args, char const * str); static int _format_p(print_args * args, void * ptr); static int _format_u(print_args * args, unsigned long long * ptr); static int _format_x(print_args * args, unsigned long long * ptr, int upper); static int _vprintf(print_func func, void * dest, size_t size, char const * format, va_list arg) { print_args args; char const * p; /* pointer to current format character */ size_t i; int lng; int f = 0; char c; char const * str; double e; long double le; long long int d; unsigned long long int u; void * ptr; args.ret = 0; args.func = func; args.dest = dest; args.size = &size; for(p = format; *p != '\0'; p += i) { args.flags = 0; args.width = 0; args.precision = 0; args.shrt = 0; for(i = 0; p[i] != '\0' && p[i] != '%'; i++); if(i > 0 && _vfprintf_do(&args, p, i) != 0) return -1; if(p[i++] != '%') break; if(p[i] == '%') { if(_vfprintf_do(&args, p++, 1) != 0) return -1; continue; } args.flags = _vprintf_flags(p, &i); args.width = _vprintf_width(p, &i); args.precision = _vprintf_precision(p, &i); for(lng = 0; p[i] != '\0'; i++) { if(p[i] == 'L') { if(lng > 0) { errno = EINVAL; return -1; } lng = -1; } else if(p[i] == 'l') { if(lng < 0 || ++lng > 2) { errno = EINVAL; return -1; } } else if(p[i] == 'c') { c = va_arg(arg, int); f = _format_c(&args, &c); break; } else if(p[i] == 'd') { if(lng > 1) d = va_arg(arg, long long int); else if(lng == 1) d = va_arg(arg, long int); else d = va_arg(arg, int); f = _format_d(&args, &d); break; } else if(p[i] == 'f') { if(lng < 0) { le = va_arg(arg, long double); f = _format_lf(&args, &le); } else { e = va_arg(arg, double); f = _format_f(&args, &e); } break; } else if(p[i] == 'h') { if(++args.shrt > 2) { errno = EINVAL; return -1; } } else if(p[i] == 'o') { if(lng > 1) u = va_arg(arg, unsigned long long int); else if(lng == 1) u = va_arg(arg, unsigned long int); else u = va_arg(arg, unsigned int); f = _format_o(&args, &u); break; } else if(p[i] == 'p') { ptr = va_arg(arg, void *); f = _format_p(&args, ptr); break; } else if(p[i] == 's') { str = va_arg(arg, char const *); f = _format_s(&args, str); break; } else if(p[i] == 'u') { if(lng > 1) u = va_arg(arg, unsigned long long int); else if(lng == 1) u = va_arg(arg, unsigned long int); else u = va_arg(arg, unsigned int); f = _format_u(&args, &u); break; } else if(p[i] == 'x' || p[i] == 'X') { if(lng > 1) u = va_arg(arg, unsigned long long int); else if(lng == 1) u = va_arg(arg, unsigned long int); else u = va_arg(arg, unsigned int); f = _format_x(&args, &u, (p[i] == 'X')); break; } else if(p[i] == 'z') { if(lng != 0) { errno = EINVAL; return -1; } lng = 1; } else { errno = EINVAL; return -1; } } if(f != 0) return -1; i++; } return args.ret; } static int _vfprintf_do(print_args * args, char const * buf, size_t len) { size_t i; char padding = ' '; if(args->flags & FLAGS_MINUS) { _vfprintf_do_do(args, buf, len); for(i = len; i < args->width; i++) if(_vfprintf_do_do(args, &padding, 1) != 0) return -1; return 0; } if(args->flags & FLAGS_ZERO) padding = '0'; for(i = len; i < args->width; i++) if(_vfprintf_do_do(args, &padding, 1) != 0) return -1; return _vfprintf_do_do(args, buf, len); } static int _vfprintf_do_do(print_args * args, char const * buf, size_t len) { size_t s; s = min(*(args->size), len); if(args->func(args->dest, s, buf) != 0) return -1; if((size_t)INT_MAX - len < (size_t)(args->ret)) { errno = ERANGE; return -1; } *(args->size) -= s; args->ret += len; return 0; } static int _vprintf_flags(char const * p, size_t * i) { int flags; for(flags = 0; p[*i] != '\0'; (*i)++) { if(p[*i] == '#') flags |= FLAGS_HASH; else if(p[*i] == '-') flags |= FLAGS_MINUS; else if(p[*i] == '+') flags |= FLAGS_PLUS; else if(p[*i] == ' ') flags |= FLAGS_SPACE; else if(p[*i] == '0') flags |= FLAGS_ZERO; else break; } return flags; } static size_t _vprintf_width(char const * p, size_t * i) { size_t width; for(width = 0; p[*i] != '\0'; (*i)++) { if(p[*i] < '0' || p[*i] > '9') break; width *= 10; width += p[*i] - '0'; } return width; } static size_t _vprintf_precision(char const * p, size_t * i) { if(p[*i] != '.') return 0; (*i)++; return _vprintf_width(p, i); } static int _format_c(print_args * args, char const * chrp) { return _vfprintf_do(args, chrp, sizeof(*chrp)); } static int _format_d(print_args * args, long long * ptr) { unsigned long long uval; char tmp[20] = "-"; if(*ptr >= 0) return _format_u(args, (unsigned long long *)ptr); uval = llabs(*ptr); /* XXX assumes tmp is large enough */ _format_lutoa(&tmp[1], uval, 10, 0); return _vfprintf_do(args, tmp, strlen(tmp)); } static int _format_f(print_args * args, double * ptr) { /* FIXME really implement */ unsigned long long uval; if(*ptr >= 0.0) { uval = *ptr; return _format_u(args, &uval); } uval = -(*ptr); if(_vfprintf_do(args, "-", 1) != 0 || _format_u(args, &uval) != 0) return -1; return 0; } static int _format_lf(print_args * args, long double * ptr) { /* FIXME really implement */ unsigned long long uval; if(*ptr >= 0.0) { uval = *ptr; return _format_u(args, &uval); } uval = -(*ptr); if(_vfprintf_do(args, "-", 1) != 0 || _format_u(args, &uval) != 0) return -1; return 0; } static int _format_o(print_args * args, unsigned long long * ptr) { char tmp[22] = ""; _format_lutoa(tmp, *ptr, 8, 0); /* XXX assumes tmp is large enough */ return _vfprintf_do(args, tmp, strlen(tmp)); } static int _format_s(print_args * args, char const * str) { if(str == NULL) /* XXX be nice and do not crash */ str = "(null)"; return _vfprintf_do(args, str, strlen(str)); } static int _format_p(print_args * args, void * ptr) { char tmp[3 + sizeof(void*) + sizeof(void*)] = "0x"; _format_lutoa(&tmp[2], (long)ptr, 16, 0); /* XXX cast */ return _vfprintf_do(args, tmp, strlen(tmp)); } static int _format_u(print_args * args, unsigned long long * ptr) { char tmp[19] = ""; _format_lutoa(tmp, *ptr, 10, 0); /* XXX assumes tmp is large enough */ return _vfprintf_do(args, tmp, strlen(tmp)); } static int _format_x(print_args * args, unsigned long long * ptr, int upper) { unsigned long u = *ptr; char tmp[sizeof(long) + sizeof(long) + 1] = ""; if(args->shrt == 1) u = u & 0xffff; else if(args->shrt == 2) u = u & 0xff; _format_lutoa(tmp, u, 16, upper); /* XXX assumes tmp large enough */ if((args->flags & FLAGS_HASH) == FLAGS_HASH) if(_vfprintf_do_do(args, "0x", 2) != 0) return -1; return _vfprintf_do(args, tmp, strlen(tmp)); } /* PRE dest is long enough * POST 2 <= base <= 36 dest is the ascii representation of n * else dest is an empty string */ static void _format_lutoa(char * dest, unsigned long n, size_t base, int upper) { static char const convl[] = "0123456789abcdefghijklmnopqrstuvwxyz"; static char const convu[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; char const * conv = (upper != 0) ? convu : convl; size_t len = 0; unsigned long p; size_t i; if(base < 2 || base >= sizeof(convl)) { dest[0] = '\0'; return; } if(n == 0) { strcpy(dest, "0"); return; } /* XXX performing twice the divisions is not optimal */ for(p = n; p > 0; p /= base) len++; for(i = len; n > 0; n /= base) { p = n % base; dest[--i] = conv[p]; n -= p; } dest[len] = '\0'; } /* vscanf */ typedef struct _scan_args { int ret; int discard; int width; } scan_args; static size_t _vscanf_do(scan_args * args, char const * string, char const * format, va_list arg); static size_t _vscanf_do_bracket(scan_args * args, char const * string, char const * format, va_list arg); static size_t _vscanf_do_char(char const * string, char const * format, va_list arg); static size_t _vscanf_do_chars(scan_args * args, char const * string, va_list arg); static size_t _vscanf_do_double(char const * string, char const * format, va_list arg); static size_t _vscanf_do_long(char const * string, char const * format, va_list arg); static size_t _vscanf_do_max(char const * string, char const * format, va_list arg); static size_t _vscanf_do_short(char const * string, char const * format, va_list arg); static size_t _vscanf_do_size(char const * string, char const * format, va_list arg); static size_t _vscanf_do_string(scan_args * args, char const * string, va_list arg); static int _vscanf(scan_func func, char const * string, char const * format, va_list arg) { scan_args args; char const * s = string; char const * f = format; size_t i; char * p; args.ret = 0; for(i = 0; *f != '\0' && *s != '\0'; f += i) { switch(*f) { case ' ': case '\t': if(!isspace(*s)) break; /* skip spaces in the format string */ for(i = 1; isspace((unsigned int)f[i]); i++); /* skip spaces in the original string */ for(; isspace((unsigned int)*s); s++); break; case '%': if((++f)[0] == '*') { /* FIXME really implement */ args.discard = 1; f++; } else args.discard = 0; /* maximum field width */ if((args.width = strtol(f, &p, 10)) == 0) args.width = -1; else f = p; if(*f == 'h') { if(*(++f) == 'h') i = _vscanf_do_char(s, ++f, arg); else i = _vscanf_do_short(s, f, arg); } else if(*f == 'j') i = _vscanf_do_max(s, ++f, arg); else if(*f == 'L') i = _vscanf_do_double(s, ++f, arg); else if(*f == 'l') i = _vscanf_do_long(s, ++f, arg); else if(*f == 'z') i = _vscanf_do_size(s, ++f, arg); else i = _vscanf_do(&args, s, f, arg); if(i == 0) break; s += i; /* FIXME hardcoded */ i = 1; args.ret++; break; default: i = (*(s++) == f[0]) ? 1 : 0; break; } if(i == 0) { args.ret = -1; break; } } return args.ret; } static size_t _vscanf_do(scan_args * args, char const * string, char const * format, va_list arg) { int * d; float * f; unsigned int * u; char * s; void ** v; switch(format[0]) { case 'A': case 'a': case 'E': case 'e': case 'F': case 'f': case 'G': case 'g': f = va_arg(arg, float *); return ((*f = strtof(string, &s)) != 0 && string != s) ? *f : 0; case 'c': return _vscanf_do_chars(args, string, arg); case 'd': d = va_arg(arg, int *); *d = strtol(string, &s, 10); return s - string; case 'i': d = va_arg(arg, int *); *d = strtol(string, &s, 0); return s - string; case 'o': u = va_arg(arg, unsigned int *); *u = strtol(string, &s, 8); return s - string; case 'p': v = va_arg(arg, void **); *v = (void *)strtol(string, &s, 16); return s - string; case 's': return _vscanf_do_string(args, string, arg); case 'u': u = va_arg(arg, unsigned int *); *u = strtoul(string, &s, 10); return s - string; case 'X': case 'x': u = va_arg(arg, unsigned int *); *u = strtoul(string, &s, 16); return s - string; case '[': return _vscanf_do_bracket(args, string, format, arg); case '%': return (*string == '%') ? 1 : 0; case 'C': return _vscanf_do_long(string, "c", arg); case 'S': return _vscanf_do_long(string, "S", arg); } errno = EINVAL; return 0; } static size_t _vscanf_do_bracket(scan_args * args, char const * string, char const * format, va_list arg) { int ret = 1; int i; char * s; if(format[ret] == '^') /* FIXME really implement */ ret++; if(format[ret] == ']') ret++; /* FIXME really implement */ for(; format[ret] != ']'; ret++); s = va_arg(arg, char *); for(i = 0; (args->width <= 0 || i < args->width) && string[i] != '\0'; i++) s[i] = string[i]; /* FIXME check for any off by one */ s[i] = '\0'; return ret; } static size_t _vscanf_do_char(char const * string, char const * format, va_list arg) { char * d; unsigned char * u; char * s; switch(format[0]) { case 'd': d = va_arg(arg, char *); *d = strtol(string, &s, 10); return s - string; case 'i': d = va_arg(arg, char *); *d = strtol(string, &s, 0); return s - string; case 'o': u = va_arg(arg, unsigned char *); *u = strtol(string, &s, 8); return s - string; case 'u': u = va_arg(arg, unsigned char *); *u = strtoul(string, &s, 10); return s - string; case 'X': case 'x': u = va_arg(arg, unsigned char *); *u = strtoul(string, &s, 16); return s - string; } errno = EINVAL; return 0; } static size_t _vscanf_do_chars(scan_args * args, char const * string, va_list arg) { size_t ret; char * s; size_t width = (args->width > 0) ? args->width : 1; s = va_arg(arg, char *); for(ret = 0; ret < width && *string != '\0'; ret++) *s++ = *string++; return ret; } static size_t _vscanf_do_double(char const * string, char const * format, va_list arg) { long double * f; char * s; switch(format[0]) { case 'A': case 'a': case 'E': case 'e': case 'F': case 'f': case 'G': case 'g': f = va_arg(arg, long double *); return ((*f = strtold(string, &s)) != 0 && string != s) ? *f : 0; } errno = EINVAL; return 0; } static size_t _vscanf_do_long(char const * string, char const * format, va_list arg) { long * d; double * f; unsigned long * u; char * s; switch(format[0]) { case 'A': case 'a': case 'E': case 'e': case 'F': case 'f': case 'G': case 'g': f = va_arg(arg, double *); return ((*f = strtod(string, &s)) != 0 && string != s) ? *f : 0; case 'd': d = va_arg(arg, long *); *d = strtol(string, &s, 10); return s - string; case 'i': d = va_arg(arg, long *); *d = strtol(string, &s, 0); return s - string; case 'o': u = va_arg(arg, unsigned long *); *u = strtol(string, &s, 8); return s - string; case 'u': u = va_arg(arg, unsigned long *); *u = strtoul(string, &s, 10); return s - string; case 'X': case 'x': u = va_arg(arg, unsigned long *); *u = strtoul(string, &s, 16); return s - string; case 'c': case 's': case '[': /* FIXME implement */ errno = ENOSYS; return 0; } errno = EINVAL; return 0; } static size_t _vscanf_do_max(char const * string, char const * format, va_list arg) { intmax_t * d; uintmax_t * u; char * s; switch(format[0]) { case 'd': d = va_arg(arg, intmax_t *); *d = strtoll(string, &s, 10); return s - string; case 'i': d = va_arg(arg, intmax_t *); *d = strtoll(string, &s, 0); return s - string; case 'o': u = va_arg(arg, uintmax_t *); *u = strtoll(string, &s, 8); return s - string; case 'u': u = va_arg(arg, uintmax_t *); *u = strtoull(string, &s, 10); return s - string; case 'X': case 'x': u = va_arg(arg, uintmax_t *); *u = strtoull(string, &s, 16); return s - string; } errno = EINVAL; return 0; } static size_t _vscanf_do_short(char const * string, char const * format, va_list arg) { short * d; unsigned short * u; char * s; switch(format[0]) { case 'd': d = va_arg(arg, short *); *d = strtol(string, &s, 10); return s - string; case 'i': d = va_arg(arg, short *); *d = strtol(string, &s, 0); return s - string; case 'o': u = va_arg(arg, unsigned short *); *u = strtol(string, &s, 8); return s - string; case 'u': u = va_arg(arg, unsigned short *); *u = strtoul(string, &s, 10); return s - string; case 'X': case 'x': u = va_arg(arg, unsigned short *); *u = strtoul(string, &s, 16); return s - string; } errno = EINVAL; return 0; } static size_t _vscanf_do_size(char const * string, char const * format, va_list arg) { ssize_t * d; size_t * u; char * s; switch(format[0]) { case 'd': d = va_arg(arg, ssize_t *); *d = strtoll(string, &s, 10); return s - string; case 'i': d = va_arg(arg, ssize_t *); *d = strtoll(string, &s, 0); return s - string; case 'o': u = va_arg(arg, size_t *); *u = strtoll(string, &s, 8); return s - string; case 'u': u = va_arg(arg, size_t *); *u = strtoull(string, &s, 10); return s - string; case 'X': case 'x': u = va_arg(arg, size_t *); *u = strtoull(string, &s, 16); return s - string; } errno = EINVAL; return 0; } static size_t _vscanf_do_string(scan_args * args, char const * string, va_list arg) { int i; char * s; s = va_arg(arg, char *); for(i = 0; (args->width <= 0 || i < args->width) && !isspace((unsigned int)string[i]) && string[i] != '\0'; i++) s[i] = string[i]; /* FIXME check for any off by one */ s[i] = '\0'; return 1; }