/* portability.c - code to workaround the deficiencies of various platforms. * * Copyright 2012 Rob Landley <rob@landley.net> * Copyright 2012 Georgi Chorbadzhiyski <gf@unixsol.org> */ #include "toys.h" // We can't fork() on nommu systems, and vfork() requires an exec() or exit() // before resuming the parent (because they share a heap until then). And no, // we can't implement our own clone() call that does the equivalent of fork() // because nommu heaps use physical addresses so if we copy the heap all our // pointers are wrong. (You need an mmu in order to map two heaps to the same // address range without interfering with each other.) In the absence of // a portable way to tell malloc() to start a new heap without freeing the old // one, you pretty much need the exec().) // So we exec ourselves (via /proc/self/exe, if anybody knows a way to // re-exec self without depending on the filesystem, I'm all ears), // and use the arguments to signal reentry. #if CFG_TOYBOX_FORK pid_t xfork(void) { pid_t pid = fork(); if (pid < 0) perror_exit("fork"); return pid; } #endif int xgetrandom(void *buf, unsigned buflen, unsigned flags) { int fd; #if CFG_TOYBOX_GETRANDOM if (buflen == getrandom(buf, buflen, flags&~WARN_ONLY)) return 1; if (errno!=ENOSYS && !(flags&WARN_ONLY)) perror_exit("getrandom"); #endif fd = xopen(flags ? "/dev/random" : "/dev/urandom",O_RDONLY|(flags&WARN_ONLY)); if (fd == -1) return 0; xreadall(fd, buf, buflen); close(fd); return 1; } // Get list of mounted filesystems, including stat and statvfs info. // Returns a reversed list, which is good for finding overmounts and such. #if defined(__APPLE__) || defined(__FreeBSD__) #include <sys/mount.h> struct mtab_list *xgetmountlist(char *path) { struct mtab_list *mtlist = 0, *mt; struct statfs *entries; int i, count; if (path) error_exit("xgetmountlist"); if ((count = getmntinfo(&entries, 0)) == 0) perror_exit("getmntinfo"); // The "test" part of the loop is done before the first time through and // again after each "increment", so putting the actual load there avoids // duplicating it. If the load was NULL, the loop stops. for (i = 0; i < count; ++i) { struct statfs *me = &entries[i]; mt = xzalloc(sizeof(struct mtab_list) + strlen(me->f_fstypename) + strlen(me->f_mntonname) + strlen(me->f_mntfromname) + strlen("") + 4); dlist_add_nomalloc((void *)&mtlist, (void *)mt); // Collect details about mounted filesystem. // Don't report errors, just leave data zeroed. stat(me->f_mntonname, &(mt->stat)); statvfs(me->f_mntonname, &(mt->statvfs)); // Remember information from struct statfs. mt->dir = stpcpy(mt->type, me->f_fstypename)+1; mt->device = stpcpy(mt->dir, me->f_mntonname)+1; mt->opts = stpcpy(mt->device, me->f_mntfromname)+1; strcpy(mt->opts, ""); /* TODO: reverse from f_flags? */ } return mtlist; } #else #include <mntent.h> static void octal_deslash(char *s) { char *o = s; while (*s) { if (*s == '\\') { int i, oct = 0; for (i = 1; i < 4; i++) { if (!isdigit(s[i])) break; oct = (oct<<3)+s[i]-'0'; } if (i == 4) { *o++ = oct; s += i; continue; } } *o++ = *s++; } *o = 0; } // Check if this type matches list. // Odd syntax: typelist all yes = if any, typelist all no = if none. int mountlist_istype(struct mtab_list *ml, char *typelist) { int len, skip; char *t; if (!typelist) return 1; skip = strncmp(typelist, "no", 2); for (;;) { if (!(t = comma_iterate(&typelist, &len))) break; if (!skip) { // If one -t starts with "no", the rest must too if (strncmp(t, "no", 2)) error_exit("bad typelist"); if (!strncmp(t+2, ml->type, len-2)) { skip = 1; break; } } else if (!strncmp(t, ml->type, len) && !ml->type[len]) { skip = 0; break; } } return !skip; } struct mtab_list *xgetmountlist(char *path) { struct mtab_list *mtlist = 0, *mt; struct mntent *me; FILE *fp; char *p = path ? path : "/proc/mounts"; if (!(fp = setmntent(p, "r"))) perror_exit("bad %s", p); // The "test" part of the loop is done before the first time through and // again after each "increment", so putting the actual load there avoids // duplicating it. If the load was NULL, the loop stops. while ((me = getmntent(fp))) { mt = xzalloc(sizeof(struct mtab_list) + strlen(me->mnt_fsname) + strlen(me->mnt_dir) + strlen(me->mnt_type) + strlen(me->mnt_opts) + 4); dlist_add_nomalloc((void *)&mtlist, (void *)mt); // Collect details about mounted filesystem // Don't report errors, just leave data zeroed if (!path) { stat(me->mnt_dir, &(mt->stat)); statvfs(me->mnt_dir, &(mt->statvfs)); } // Remember information from /proc/mounts mt->dir = stpcpy(mt->type, me->mnt_type)+1; mt->device = stpcpy(mt->dir, me->mnt_dir)+1; mt->opts = stpcpy(mt->device, me->mnt_fsname)+1; strcpy(mt->opts, me->mnt_opts); octal_deslash(mt->dir); octal_deslash(mt->device); } endmntent(fp); return mtlist; } #endif #ifdef __APPLE__ #include <sys/event.h> struct xnotify *xnotify_init(int max) { struct xnotify *not = xzalloc(sizeof(struct xnotify)); not->max = max; if ((not->kq = kqueue()) == -1) perror_exit("kqueue"); not->paths = xmalloc(max * sizeof(char *)); not->fds = xmalloc(max * sizeof(int)); return not; } int xnotify_add(struct xnotify *not, int fd, char *path) { struct kevent event; if (not->count == not->max) error_exit("xnotify_add overflow"); EV_SET(&event, fd, EVFILT_VNODE, EV_ADD|EV_CLEAR, NOTE_WRITE, 0, NULL); if (kevent(not->kq, &event, 1, NULL, 0, NULL) == -1 || event.flags & EV_ERROR) return -1; not->paths[not->count] = path; not->fds[not->count++] = fd; return 0; } int xnotify_wait(struct xnotify *not, char **path) { struct kevent event; int i; for (;;) { if (kevent(not->kq, NULL, 0, &event, 1, NULL) != -1) { // We get the fd for free, but still have to search for the path. for (i = 0; i<not->count; i++) if (not->fds[i]==event.ident) { *path = not->paths[i]; return event.ident; } } } } #else #include <sys/inotify.h> struct xnotify *xnotify_init(int max) { struct xnotify *not = xzalloc(sizeof(struct xnotify)); not->max = max; if ((not->kq = inotify_init()) < 0) perror_exit("inotify_init"); not->paths = xmalloc(max * sizeof(char *)); not->fds = xmalloc(max * 2 * sizeof(int)); return not; } int xnotify_add(struct xnotify *not, int fd, char *path) { int i = 2*not->count; if (not->max == not->count) error_exit("xnotify_add overflow"); if ((not->fds[i] = inotify_add_watch(not->kq, path, IN_MODIFY))==-1) return -1; not->fds[i+1] = fd; not->paths[not->count++] = path; return 0; } int xnotify_wait(struct xnotify *not, char **path) { struct inotify_event ev; int i; for (;;) { if (sizeof(ev)!=read(not->kq, &ev, sizeof(ev))) perror_exit("inotify"); for (i = 0; i<not->count; i++) if (ev.wd==not->fds[2*i]) { *path = not->paths[i]; return not->fds[2*i+1]; } } } #endif #ifdef __APPLE__ ssize_t xattr_get(const char *path, const char *name, void *value, size_t size) { return getxattr(path, name, value, size, 0, 0); } ssize_t xattr_lget(const char *path, const char *name, void *value, size_t size) { return getxattr(path, name, value, size, 0, XATTR_NOFOLLOW); } ssize_t xattr_fget(int fd, const char *name, void *value, size_t size) { return fgetxattr(fd, name, value, size, 0, 0); } ssize_t xattr_list(const char *path, char *list, size_t size) { return listxattr(path, list, size, 0); } ssize_t xattr_llist(const char *path, char *list, size_t size) { return listxattr(path, list, size, XATTR_NOFOLLOW); } ssize_t xattr_flist(int fd, char *list, size_t size) { return flistxattr(fd, list, size, 0); } ssize_t xattr_set(const char* path, const char* name, const void* value, size_t size, int flags) { return setxattr(path, name, value, size, 0, flags); } ssize_t xattr_lset(const char* path, const char* name, const void* value, size_t size, int flags) { return setxattr(path, name, value, size, 0, flags | XATTR_NOFOLLOW); } ssize_t xattr_fset(int fd, const char* name, const void* value, size_t size, int flags) { return fsetxattr(fd, name, value, size, 0, flags); } #else ssize_t xattr_get(const char *path, const char *name, void *value, size_t size) { return getxattr(path, name, value, size); } ssize_t xattr_lget(const char *path, const char *name, void *value, size_t size) { return lgetxattr(path, name, value, size); } ssize_t xattr_fget(int fd, const char *name, void *value, size_t size) { return fgetxattr(fd, name, value, size); } ssize_t xattr_list(const char *path, char *list, size_t size) { return listxattr(path, list, size); } ssize_t xattr_llist(const char *path, char *list, size_t size) { return llistxattr(path, list, size); } ssize_t xattr_flist(int fd, char *list, size_t size) { return flistxattr(fd, list, size); } ssize_t xattr_set(const char* path, const char* name, const void* value, size_t size, int flags) { return setxattr(path, name, value, size, flags); } ssize_t xattr_lset(const char* path, const char* name, const void* value, size_t size, int flags) { return lsetxattr(path, name, value, size, flags); } ssize_t xattr_fset(int fd, const char* name, const void* value, size_t size, int flags) { return fsetxattr(fd, name, value, size, flags); } #endif #ifdef __APPLE__ // In the absence of a mknodat system call, fchdir to dirfd and back // around a regular mknod call... int mknodat(int dirfd, const char *path, mode_t mode, dev_t dev) { int old_dirfd = open(".", O_RDONLY), result; if (old_dirfd == -1 || fchdir(dirfd) == -1) return -1; result = mknod(path, mode, dev); if (fchdir(old_dirfd) == -1) perror_exit("mknodat couldn't return"); return result; } // As of 10.15, macOS offers an fcntl F_PREALLOCATE rather than fallocate() // or posix_fallocate() calls. int posix_fallocate(int fd, off_t offset, off_t length) { int e = errno, result; fstore_t f; f.fst_flags = F_ALLOCATEALL; f.fst_posmode = F_PEOFPOSMODE; f.fst_offset = offset; f.fst_length = length; if (fcntl(fd, F_PREALLOCATE, &f) == -1) result = errno; else result = ftruncate(fd, length); errno = e; return result; } #endif // Signals required by POSIX 2008: // http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/signal.h.html #define SIGNIFY(x) {SIG##x, #x} static const struct signame signames[] = { // POSIX SIGNIFY(ABRT), SIGNIFY(ALRM), SIGNIFY(BUS), SIGNIFY(FPE), SIGNIFY(HUP), SIGNIFY(ILL), SIGNIFY(INT), SIGNIFY(KILL), SIGNIFY(PIPE), SIGNIFY(QUIT), SIGNIFY(SEGV), SIGNIFY(TERM), SIGNIFY(USR1), SIGNIFY(USR2), SIGNIFY(SYS), SIGNIFY(TRAP), SIGNIFY(VTALRM), SIGNIFY(XCPU), SIGNIFY(XFSZ), // Non-POSIX signals that cause termination SIGNIFY(PROF), SIGNIFY(IO), #ifdef __linux__ SIGNIFY(STKFLT), SIGNIFY(POLL), SIGNIFY(PWR), #elif defined(__APPLE__) SIGNIFY(EMT), SIGNIFY(INFO), #endif // Note: sigatexit relies on all the signals with a default disposition that // terminates the process coming *before* SIGCHLD. // POSIX signals that don't cause termination SIGNIFY(CHLD), SIGNIFY(CONT), SIGNIFY(STOP), SIGNIFY(TSTP), SIGNIFY(TTIN), SIGNIFY(TTOU), SIGNIFY(URG), // Non-POSIX signals that don't cause termination SIGNIFY(WINCH), }; int signames_len = ARRAY_LEN(signames); #undef SIGNIFY void xsignal_all_killers(void *handler) { int i; for (i=0; signames[i].num != SIGCHLD; i++) if (signames[i].num != SIGKILL) xsignal(signames[i].num, handler ? exit_signal : SIG_DFL); } // Convert a string like "9", "KILL", "SIGHUP", or "SIGRTMIN+2" to a number. int sig_to_num(char *sigstr) { int i, offset; char *s; // Numeric? i = estrtol(sigstr, &s, 10); if (!errno && !*s) return i; // Skip leading "SIG". strcasestart(&sigstr, "sig"); // Named signal? for (i=0; i<ARRAY_LEN(signames); i++) if (!strcasecmp(sigstr, signames[i].name)) return signames[i].num; // Real-time signal? #ifdef SIGRTMIN if (strcasestart(&sigstr, "rtmin")) i = SIGRTMIN; else if (strcasestart(&sigstr, "rtmax")) i = SIGRTMAX; else return -1; // No offset? if (!*sigstr) return i; // We allow any offset that's still a real-time signal: SIGRTMIN+20 is fine. // Others are more restrictive, only accepting what they show with -l. offset = estrtol(sigstr, &s, 10); if (errno || *s) return -1; i += offset; if (i >= SIGRTMIN && i <= SIGRTMAX) return i; #endif return -1; } char *num_to_sig(int sig) { int i; // A named signal? for (i=0; i<signames_len; i++) if (signames[i].num == sig) return signames[i].name; // A real-time signal? #ifdef SIGRTMIN if (sig == SIGRTMIN) return "RTMIN"; if (sig == SIGRTMAX) return "RTMAX"; if (sig > SIGRTMIN && sig < SIGRTMAX) { if (sig-SIGRTMIN <= SIGRTMAX-sig) sprintf(libbuf, "RTMIN+%d", sig-SIGRTMIN); else sprintf(libbuf, "RTMAX-%d", SIGRTMAX-sig); return libbuf; } #endif return NULL; }