/******************************************************************************* 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 . stdio/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); }