diff --git a/libc/Makefile b/libc/Makefile index cfc355ff..42c62ebe 100644 --- a/libc/Makefile +++ b/libc/Makefile @@ -200,6 +200,7 @@ openat.o \ open.o \ pipe.o \ poll.o \ +popen.o \ ppoll.o \ print.o \ putc.o \ diff --git a/libc/include/stdio.h b/libc/include/stdio.h index 82a0eb6d..a9439820 100644 --- a/libc/include/stdio.h +++ b/libc/include/stdio.h @@ -1,6 +1,6 @@ /******************************************************************************* - Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2013. This file is part of the Sortix C Library. @@ -97,7 +97,9 @@ extern int getc(FILE* stream); extern int getchar(void); extern ssize_t getdelim(char** restrict lineptr, size_t* restrict n, int delimiter, FILE* restrict stream); extern ssize_t getline(char** restrict lineptr, size_t* restrict n, FILE* restrict stream); +extern int pclose(FILE* steam); extern void perror(const char* s); +extern FILE* popen(const char* command, const char* mode); extern int printf(const char* restrict format, ...); extern int putc(int c, FILE* stream); extern int putchar(int c); @@ -128,7 +130,6 @@ extern int vsscanf(const char* restrict s, const char* restrict format, __gnuc_v extern char* ctermid(char* s); extern FILE *fmemopen(void* restrict buf, size_t size, const char* restrict mode); extern FILE* open_memstream(char** bufp, size_t* sizep); -extern FILE* popen(const char* command, const char* mode); extern FILE* tmpfile(void); extern int dprintf(int fildes, const char* restrict format, ...); extern int fgetpos(FILE* restrict stream, fpos_t* restrict pos); @@ -136,7 +137,6 @@ extern int fsetpos(FILE* stream, const fpos_t* pos); extern int ftrylockfile(FILE* file); extern int getchar_unlocked(void); extern int getc_unlocked(FILE* stream); -extern int pclose(FILE* steam); extern int putchar_unlocked(int c); extern int putc_unlocked(int c, FILE* steam); extern int setvbuf(FILE* restrict stream, char* restrict buf, int type, size_t size); diff --git a/libc/popen.cpp b/libc/popen.cpp new file mode 100644 index 00000000..04f7b6ce --- /dev/null +++ b/libc/popen.cpp @@ -0,0 +1,231 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + This file is part of the Sortix C Library. + + The Sortix C Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + The Sortix C Library 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 Lesser General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with the Sortix C Library. If not, see . + + popen.cpp + Pipe stream to or from a process. + +*******************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include + +const int POPEN_WRITING = 1 << 0; +const int POPEN_READING = 1 << 1; +const int POPEN_CLOEXEC = 1 << 2; +const int POPEN_ERROR = 1 << 3; +const int POPEN_EOF = 1 << 4; + +typedef struct popen_struct +{ + int flags; + int fd; + pid_t pid; +} popen_t; + +static size_t popen_read(void* ptr, size_t size, size_t nmemb, void* user) +{ + uint8_t* buf = (uint8_t*) ptr; + popen_t* info = (popen_t*) user; + if ( !(info->flags & POPEN_READING) ) + return errno = EBADF, 0; + size_t sofar = 0; + size_t total = size * nmemb; + while ( sofar < total ) + { + ssize_t numbytes = read(info->fd, buf + sofar, total - sofar); + if ( numbytes < 0 ) { info->flags |= POPEN_ERROR; break; } + if ( numbytes == 0 ) { info->flags |= POPEN_EOF; break; } + return numbytes / size; + sofar += numbytes; + } + return sofar / size; +} + +static size_t popen_write(const void* ptr, size_t size, size_t nmemb, void* user) +{ + const uint8_t* buf = (const uint8_t*) ptr; + popen_t* info = (popen_t*) user; + if ( !(info->flags & POPEN_WRITING) ) + return errno = EBADF, 0; + size_t sofar = 0; + size_t total = size * nmemb; + while ( sofar < total ) + { + ssize_t numbytes = write(info->fd, buf + sofar, total - sofar); + if ( numbytes < 0 ) { info->flags |= POPEN_ERROR; break; } + if ( numbytes == 0 ) { info->flags |= POPEN_EOF; break; } + sofar += numbytes; + } + return sofar / size; +} + +static int popen_seek(void* /*user*/, off_t /*offset*/, int /*whence*/) +{ + return errno = ESPIPE, -1; +} + +static off_t popen_tell(void* /*user*/) +{ + return errno = ESPIPE, -1; +} + +static void popen_seterr(void* user) +{ + popen_t* info = (popen_t*) user; + info->flags |= POPEN_ERROR; +} + +static void popen_clearerr(void* user) +{ + popen_t* info = (popen_t*) user; + info->flags &= ~POPEN_ERROR; +} + +static int popen_eof(void* user) +{ + popen_t* info = (popen_t*) user; + return info->flags & POPEN_EOF; +} + +static int popen_error(void* user) +{ + popen_t* info = (popen_t*) user; + return info->flags & POPEN_ERROR; +} + +static int popen_fileno(void* user) +{ + popen_t* info = (popen_t*) user; + return info->fd; +} + +static int popen_close(void* user) +{ + popen_t* info = (popen_t*) user; + if ( close(info->fd) ) + { + /* TODO: Should this return value be discarded? */; + } + int status; + if ( waitpid(info->pid, &status, 0) < 0 ) + status = -1; + free(info); + return status; +} + +static int parse_popen_type(const char* type) +{ + int ret = 0; + switch ( *type++ ) + { + case 'r': ret = POPEN_READING; break; + case 'w': ret = POPEN_WRITING; break; + default: return errno = -EINVAL, -1; + } + while ( char c = *type++ ) + switch ( c ) + { + case 'e': ret |= POPEN_CLOEXEC; break; + default: return errno = -EINVAL, -1; + } + return ret; +} + +void popen_install(FILE* fp, popen_t* info) +{ + fp->user = info; + fp->read_func = popen_read; + fp->write_func = popen_write; + fp->seek_func = popen_seek; + fp->tell_func = popen_tell; + fp->seterr_func = popen_seterr; + fp->clearerr_func = popen_clearerr; + fp->eof_func = popen_eof; + fp->error_func = popen_error; + fp->fileno_func = popen_fileno; + fp->close_func = popen_close; + fp->flags |= _FILE_STREAM; +} + +extern "C" FILE* popen(const char* command, const char* type) +{ + int flags, used_end, unused_end, redirect_what; + int pipefds[2]; + popen_t* info; + FILE* fp; + pid_t childpid; + + if ( (flags = parse_popen_type(type)) < 0 ) + goto cleanup_out; + if ( !(info = (popen_t*) calloc(1, sizeof(popen_t))) ) + goto cleanup_out; + if ( !(fp = fnewfile()) ) + goto cleanup_info; + if ( pipe(pipefds) ) + goto cleanup_fp; + if ( (childpid = fork()) < 0 ) + goto cleanup_pipes; + + if ( childpid ) + { + used_end = flags & POPEN_WRITING ? 1 /*writing*/ : 0 /*reading*/; + unused_end = flags & POPEN_WRITING ? 0 /*reading*/ : 1 /*writing*/; + close(pipefds[unused_end]); + if ( flags & POPEN_CLOEXEC ) + fcntl(pipefds[used_end], F_SETFL, FD_CLOEXEC); + info->fd = pipefds[used_end]; + info->flags = flags; + info->pid = childpid; + popen_install(fp, info); + return fp; + } + + redirect_what = flags & POPEN_WRITING ? 0 /*stdin*/ : 1 /*stdout*/; + used_end = flags & POPEN_WRITING ? 0 /*reading*/ : 1 /*writing*/; + unused_end = flags & POPEN_WRITING ? 1 /*writing*/ : 0 /*reading*/; + if ( dup2(pipefds[used_end], redirect_what) < 0 || + close(pipefds[used_end]) || + close(pipefds[unused_end]) ) + _exit(127); + + execlp("sh", "sh", "-c", command, NULL); + _exit(127); + +cleanup_pipes: + close(pipefds[0]); + close(pipefds[1]); +cleanup_fp: + fclose(fp); +cleanup_info: + free(info); +cleanup_out: + return NULL; +} + +extern "C" int pclose(FILE* fp) +{ + return fclose(fp); +}