aboutsummaryrefslogtreecommitdiffstats
path: root/ports/unix/guts/linkat.c
blob: 279a15b321e1e7c3e50f62184e9301a331c95023 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
/*
 * Copyright (c) 2012, 2013 Wind River Systems; see
 * guts/COPYRIGHT for information.
 *
 * int linkat(int olddirfd, const char *oldname, int newdirfd, const char *newname, int flags)
 *	int rc = -1;
 */

	int rc2 = 0, rflags, save_errno, tmpfile_fd = -1;
 	pseudo_msg_t *msg;
	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;

#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
	if (olddirfd != AT_FDCWD || newdirfd != AT_FDCWD) {
		errno = ENOSYS;
		return -1;
	}
#endif
	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);

	/* 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;

/*	return rc;
 * }
 */