diff options
-rw-r--r-- | ChangeLog.txt | 8 | ||||
-rw-r--r-- | ports/linux/guts/openat.c | 25 | ||||
-rw-r--r-- | ports/unix/guts/linkat.c | 144 |
3 files changed, 129 insertions, 48 deletions
diff --git a/ChangeLog.txt b/ChangeLog.txt index 888334a..73a12d4 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,3 +1,11 @@ +2018-01-16: + * (seebs) rework the LINKAT case significantly but now + it's actually probably right. + +2017-12-22: + * (seebs) handle the pathological case of LINKAT with + AT_SYMLINK_FOLLOW on /proc/self/fd/N. + 2017-12-18: * (seebs) Add a list of clients as a handler for SIGUSR2. (Useful for debugging, maybe.) diff --git a/ports/linux/guts/openat.c b/ports/linux/guts/openat.c index eb7c0b5..a3637c8 100644 --- a/ports/linux/guts/openat.c +++ b/ports/linux/guts/openat.c @@ -38,7 +38,16 @@ ); #endif +#ifdef O_TMPFILE + /* don't handle O_CREAT the same way if O_TMPFILE exists + * and is set. + */ + if (flags & O_TMPFILE) { + existed = 0; + } else +#endif /* if a creation has been requested, check whether file exists */ + /* note "else" in #ifdef O_TMPFILE above */ if (flags & O_CREAT) { save_errno = errno; #ifdef PSEUDO_NO_REAL_AT_FUNCTIONS @@ -62,10 +71,21 @@ #else rc = real_openat(dirfd, path, flags, PSEUDO_FS_MODE(mode, 0)); #endif - save_errno = errno; if (rc != -1) { + save_errno = errno; int stat_rc; +#ifdef O_TMPFILE + /* in O_TMPFILE case, nothing gets put in the + * database, because there's no directory entries for + * the file yet. + */ + if (flags & O_TMPFILE) { + real_fchmod(rc, PSEUDO_FS_MODE(mode, 0)); + errno = save_errno; + return rc; + } +#endif #ifdef PSEUDO_NO_REAL_AT_FUNCTIONS stat_rc = real___xstat64(_STAT_VER, path, &buf); #else @@ -76,9 +96,10 @@ buf.st_mode = PSEUDO_DB_MODE(buf.st_mode, mode); if (!existed) { real_fchmod(rc, PSEUDO_FS_MODE(mode, 0)); + // file has no path, but has been created pseudo_client_op(OP_CREAT, 0, -1, dirfd, path, &buf); } - pseudo_client_op(OP_OPEN, PSEUDO_ACCESS(flags), rc, dirfd, path, &buf); + pseudo_client_op(OP_OPEN, PSEUDO_ACCESS(flags), rc, dirfd, path, &buf); } else { pseudo_debug(PDBGF_FILE, "openat (fd %d, path %d/%s, flags %d) succeeded, but stat failed (%s).\n", rc, dirfd, path, flags, strerror(errno)); diff --git a/ports/unix/guts/linkat.c b/ports/unix/guts/linkat.c index ec27e47..279a15b 100644 --- a/ports/unix/guts/linkat.c +++ b/ports/unix/guts/linkat.c @@ -6,25 +6,25 @@ * int rc = -1; */ - int rc2, rflags, save_errno; + int rc2 = 0, rflags, save_errno, tmpfile_fd = -1; pseudo_msg_t *msg; - char *oldpath = NULL, *newpath = NULL; + const char *oldpath = NULL, *newpath = NULL; PSEUDO_STATBUF buf; - /* This is gratuitously complicated. On Linux 2.6.18 and later, - * flags may contain AT_SYMLINK_FOLLOW, which implies following - * symlinks; otherwise, linkat() will *not* follow symlinks. FreeBSD - * appears to use the same semantics. - * - * So on Darwin, always pass AT_SYMLINK_FOLLOW, because the - * alternative doesn't work. And never pass AT_SYMLINK_NOFOLLOW - * because that's not a valid flag to linkat(). - * - * So we need a flag for path resolution which is AT_SYMLINK_NOFOLLOW - * unless AT_SYMLINK_FOLLOW was specified, in which case it's 0. - */ - - rflags = (flags & AT_SYMLINK_FOLLOW) ? 0 : AT_SYMLINK_NOFOLLOW; + /* This is gratuitously complicated. On Linux 2.6.18 and later, + * flags may contain AT_SYMLINK_FOLLOW, which implies following + * symlinks; otherwise, linkat() will *not* follow symlinks. FreeBSD + * appears to use the same semantics. + * + * So on Darwin, always pass AT_SYMLINK_FOLLOW, because the + * alternative doesn't work. And never pass AT_SYMLINK_NOFOLLOW + * because that's not a valid flag to linkat(). + * + * So we need a flag for path resolution which is AT_SYMLINK_NOFOLLOW + * unless AT_SYMLINK_FOLLOW was specified, in which case it's 0. + */ + + rflags = (flags & AT_SYMLINK_FOLLOW) ? 0 : AT_SYMLINK_NOFOLLOW; #ifdef PSEUDO_NO_REAL_AT_FUNCTIONS if (olddirfd != AT_FDCWD || newdirfd != AT_FDCWD) { @@ -32,38 +32,90 @@ return -1; } #endif - oldpath = pseudo_root_path(__func__, __LINE__, olddirfd, oldname, rflags); - newpath = pseudo_root_path(__func__, __LINE__, newdirfd, newname, AT_SYMLINK_NOFOLLOW); - rc = real_link(oldpath, newpath); - save_errno = errno; - if (rc == -1) { - errno = save_errno; - return rc; - } + oldpath = oldname; + if (pseudo_chroot_len && strncmp(oldpath, pseudo_chroot, pseudo_chroot_len) && + oldpath[pseudo_chroot_len] == '/') { + oldpath += pseudo_chroot_len; + } + + newpath = pseudo_root_path(__func__, __LINE__, newdirfd, newname, AT_SYMLINK_NOFOLLOW); - /* if we got this far, the link succeeded, and oldpath and newpath - * are the newly-allocated canonical paths. If OS, filesystem, or - * the flags value prevent hard linking to symlinks, the resolved - * path should be the target's path anyway, so lstat is safe here. - */ - /* find the target: */ - rc2 = base_lstat(oldpath, &buf); - if (rc2 == -1) { - pseudo_diag("Fatal: Tried to stat '%s' after linking it, but failed: %s.\n", - oldpath, strerror(errno)); - errno = ENOENT; - return rc; - } - msg = pseudo_client_op(OP_STAT, 0, -1, -1, oldpath, &buf); - if (msg && msg->result == RESULT_SUCCEED) { - pseudo_stat_msg(&buf, msg); - } - /* Long story short: I am pretty sure we still want OP_LINK even - * if the thing linked is a symlink. - */ - pseudo_client_op(OP_LINK, 0, -1, -1, newpath, &buf); + /* weird special case: if you link /proc/self/fd/N, you're supposed + * to get a link to fd N. Used in conjunction with opening a directory + * with O_TMPFILE in flags, which actually opens an already-deleted + * file atomically, so there's no way to access the file *at all* + * unless it's linked. + */ +#ifdef O_TMPFILE + if (!strncmp(oldpath, "/proc/self/fd/", 14) && (flags & AT_SYMLINK_FOLLOW)) { +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + /* only linkat() lets you do this horrible thing */ + errno = ENOSYS; + return -1; +#endif + tmpfile_fd = atoi(oldpath + 14); + // call actual link + rc = real_linkat(AT_FDCWD, oldpath, AT_FDCWD, newpath, AT_SYMLINK_FOLLOW); + } else +#endif + { + oldpath = pseudo_root_path(__func__, __LINE__, olddirfd, oldname, rflags); + rc = real_link(oldpath, newpath); + } + + save_errno = errno; + if (rc == -1) { + return rc; + } + + /* if we got this far, the link succeeded, and oldpath and newpath + * are the newly-allocated canonical paths. If OS, filesystem, or + * the flags value prevent hard linking to symlinks, the resolved + * path should be the target's path anyway, so lstat is safe here. + */ + /* find the target: */ + if (tmpfile_fd != -1) { + rc2 = base_fstat(tmpfile_fd, &buf); + } else { + rc2 = base_lstat(oldpath, &buf); + } + if (rc2 == -1) { + pseudo_diag("Fatal: Tried to stat '%s' after linking it, but failed: %s.\n", + oldpath, strerror(errno)); + errno = ENOENT; + return rc2; + } + if (tmpfile_fd != -1) { + /* if tmpfile_fd is set, it's *possible* but not *certain* that + * we're looking at a file descriptor from an O_TMPFILE open, + * which would not be in the database. + * + * If it's not, we want to treat this like a CREAT operation, + * instead of a "link". + */ + msg = pseudo_client_op(OP_FSTAT, 0, tmpfile_fd, -1, 0, &buf); + if (!msg || msg->result != RESULT_SUCCEED) { + /* create it, mark it as open so we have a path recorded + * in the client, and return rc immediately instead + * of continuing on to call OP_LINK. + */ + pseudo_client_op(OP_CREAT, 0, tmpfile_fd, -1, newpath, &buf); + pseudo_client_op(OP_OPEN, 0, tmpfile_fd, -1, newpath, &buf); + errno = save_errno; + return rc; + } + } else { + msg = pseudo_client_op(OP_STAT, 0, -1, -1, oldpath, &buf); + } + if (msg && msg->result == RESULT_SUCCEED) { + pseudo_stat_msg(&buf, msg); + } + /* Long story short: I am pretty sure we still want OP_LINK even + * if the thing linked is a symlink. + */ + pseudo_client_op(OP_LINK, 0, -1, -1, newpath, &buf); - errno = save_errno; + errno = save_errno; /* return rc; * } |