aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ChangeLog.txt8
-rw-r--r--ports/linux/guts/openat.c25
-rw-r--r--ports/unix/guts/linkat.c144
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;
* }