1/*
2 * Copyright 2005-2011, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Copyright 2012, John Scipione, jscipione@gmail.com.
4 * Distributed under the terms of the MIT License.
5 */
6
7#include <dirent.h>
8#include <errno.h>
9#include <fcntl.h>
10#include <stdio.h>
11#include <stdlib.h>
12#include <string.h>
13#include <sys/param.h>
14#include <sys/stat.h>
15#include <sys/time.h>
16#include <unistd.h>
17
18
19// Private helper functions
20static int get_path(int fd, const char* path, char fullPath[]);
21static int eaccess(const char* path, int accessMode);
22
23
24static int
25get_path(int fd, const char* path, char fullPath[])
26{
27	struct stat dirst;
28	if (fstat(fd, &dirst) < 0) {
29		// failed to grab stat information, fstat() sets errno
30		return -1;
31	}
32
33	if (!S_ISDIR(dirst.st_mode)) {
34		// fd does not point to a directory
35		errno = ENOTDIR;
36		return -1;
37	}
38
39	if (fcntl(fd, F_GETPATH, fullPath) < 0) {
40		// failed to get the path of fd, fcntl() sets errno
41		return -1;
42	}
43
44	if (strlcat(fullPath, "/", MAXPATHLEN) > MAXPATHLEN
45		|| strlcat(fullPath, path, MAXPATHLEN) > MAXPATHLEN) {
46		// full path is too long
47		errno = ENAMETOOLONG;
48		return -1;
49	}
50
51	return 0;
52}
53
54
55static int
56eaccess(const char* path, int accessMode)
57{
58	uid_t uid = geteuid();
59	int fileMode = 0;
60
61	struct stat st;
62	if (stat(path, &st) < 0) {
63		// failed to get stat information on path, stat() sets errno
64		return -1;
65	}
66
67	if (uid == 0) {
68		// user is root
69		// root has always read/write permission, but at least one of the
70		// X bits must be set for execute permission
71		fileMode = R_OK | W_OK;
72		if ((st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0)
73			fileMode |= X_OK;
74	} else if (st.st_uid == uid) {
75		// user is node owner
76		if ((st.st_mode & S_IRUSR) != 0)
77			fileMode |= R_OK;
78		if ((st.st_mode & S_IWUSR) != 0)
79			fileMode |= W_OK;
80		if ((st.st_mode & S_IXUSR) != 0)
81			fileMode |= X_OK;
82	} else if (st.st_gid == getegid()) {
83		// user is in owning group
84		if ((st.st_mode & S_IRGRP) != 0)
85			fileMode |= R_OK;
86		if ((st.st_mode & S_IWGRP) != 0)
87			fileMode |= W_OK;
88		if ((st.st_mode & S_IXGRP) != 0)
89			fileMode |= X_OK;
90	} else {
91		// user is one of the others
92		if ((st.st_mode & S_IROTH) != 0)
93			fileMode |= R_OK;
94		if ((st.st_mode & S_IWOTH) != 0)
95			fileMode |= W_OK;
96		if ((st.st_mode & S_IXOTH) != 0)
97			fileMode |= X_OK;
98	}
99
100	if ((accessMode & ~fileMode) != 0) {
101		errno = EACCES;
102		return -1;
103	}
104
105	return 0;
106}
107
108
109int
110faccessat(int fd, const char* path, int accessMode, int flag)
111{
112	if (flag != AT_EACCESS && flag != 0) {
113		// invalid flag
114		errno = EINVAL;
115		return -1;
116	}
117
118	if (fd == AT_FDCWD || (path != NULL && path[0] == '/')) {
119		// call access() ignoring fd
120		return (flag & AT_EACCESS) != 0 ? eaccess(path, accessMode)
121			: access(path, accessMode);
122	}
123
124	if (fd < 0) {
125		// Invalid file descriptor
126		errno = EBADF;
127		return -1;
128	}
129
130	char fullPath[MAXPATHLEN];
131	if (get_path(fd, path, fullPath) < 0)
132		return -1;
133
134	return (flag & AT_EACCESS) != 0 ? eaccess(fullPath, accessMode)
135		: access(fullPath, accessMode);
136}
137
138
139int
140fchmodat(int fd, const char* path, mode_t mode, int flag)
141{
142	if ((flag & AT_SYMLINK_NOFOLLOW) == 0 && flag != 0) {
143		// invalid flag
144		errno = EINVAL;
145		return -1;
146	}
147
148	if (fd == AT_FDCWD || (path != NULL && path[0] == '/')) {
149		// call chmod() ignoring fd
150		if ((flag & AT_SYMLINK_NOFOLLOW) != 0) {
151			// fake lchmod() with open() and fchmod()
152			int symlinkfd = open(path, O_RDONLY | O_SYMLINK);
153			int status = fchmod(symlinkfd, mode);
154			close(symlinkfd);
155			return status;
156		} else
157			return chmod(path, mode);
158	}
159
160	if (fd < 0) {
161		// Invalid file descriptor
162		errno = EBADF;
163		return -1;
164	}
165
166	char fullPath[MAXPATHLEN];
167	if (get_path(fd, path, fullPath) < 0)
168		return -1;
169
170	int status;
171	if ((flag & AT_SYMLINK_NOFOLLOW) != 0) {
172		// fake lchmod() with open() and fchmod()
173		int fullfd = open(fullPath, O_RDONLY | O_SYMLINK);
174		status = fchmod(fullfd, mode);
175		close(fullfd);
176	} else
177		status = chmod(fullPath, mode);
178
179	return status;
180}
181
182
183int
184fchownat(int fd, const char* path, uid_t owner, gid_t group, int flag)
185{
186	if (flag != AT_SYMLINK_NOFOLLOW && flag != 0) {
187		// invalid flag
188		errno = EINVAL;
189		return -1;
190	}
191
192	if (fd == AT_FDCWD || (path != NULL && path[0] == '/')) {
193		// call chown() ignoring fd
194		return (flag & AT_SYMLINK_NOFOLLOW) != 0 ? lchown(path, owner, group)
195			: chown(path, owner, group);
196	}
197
198	if (fd < 0) {
199		// Invalid file descriptor
200		errno = EBADF;
201		return -1;
202	}
203
204	char fullPath[MAXPATHLEN];
205	if (get_path(fd, path, fullPath) < 0)
206		return -1;
207
208	return (flag & AT_SYMLINK_NOFOLLOW) != 0 ? lchown(fullPath, owner, group)
209		: chown(fullPath, owner, group);
210}
211
212
213DIR*
214fdopendir(int fd)
215{
216	struct stat st;
217	if (fstat(fd, &st)) {
218		// failed to get the stat info for fd, fstat() sets errno
219		return NULL;
220	}
221
222	if (!S_ISDIR(st.st_mode)) {
223		errno = ENOTDIR;
224		return NULL;
225	}
226
227	char path[MAXPATHLEN];
228	if (fcntl(fd, F_GETPATH, path) < 0) {
229		// failed to get the path of fd, fcntl() sets errno
230		return NULL;
231	}
232
233	DIR* dir = opendir(path);
234	if (dir != NULL)
235		close(fd);
236
237	return dir;
238}
239
240
241int
242fstatat(int fd, const char *path, struct stat *st, int flag)
243{
244	if (flag != AT_SYMLINK_NOFOLLOW && flag != 0) {
245		// invalid flag
246		errno = EINVAL;
247		return -1;
248	}
249
250	if (fd == AT_FDCWD || (path != NULL && path[0] == '/')) {
251		// call stat() or lstat() ignoring fd
252		return (flag & AT_SYMLINK_NOFOLLOW) != 0 ? lstat(path, st)
253			: stat(path, st);
254	}
255
256	if (fd < 0) {
257		// Invalid file descriptor
258		errno = EBADF;
259		return -1;
260	}
261
262	char fullPath[MAXPATHLEN];
263	if (get_path(fd, path, fullPath) < 0)
264		return -1;
265
266	return (flag & AT_SYMLINK_NOFOLLOW) != 0 ? lstat(fullPath, st)
267		: stat(fullPath, st);
268}
269
270
271int
272mkdirat(int fd, const char *path, mode_t mode)
273{
274	if (fd == AT_FDCWD || (path != NULL && path[0] == '/')) {
275		// call mkdir() ignoring fd
276		return mkdir(path, mode);
277	}
278
279	if (fd < 0) {
280		// Invalid file descriptor
281		errno = EBADF;
282		return -1;
283	}
284
285	char fullPath[MAXPATHLEN];
286	if (get_path(fd, path, fullPath) < 0)
287		return -1;
288
289	return mkdir(fullPath, mode);
290}
291
292
293int
294mkfifoat(int fd, const char *path, mode_t mode)
295{
296	if (fd == AT_FDCWD || (path != NULL && path[0] == '/')) {
297		// call mkfifo() ignoring fd
298		return mkfifo(path, mode);
299	}
300
301	if (fd < 0) {
302		// Invalid file descriptor
303		errno = EBADF;
304		return -1;
305	}
306
307	char fullPath[MAXPATHLEN];
308	if (get_path(fd, path, fullPath) < 0)
309		return -1;
310
311	return mkfifo(fullPath, mode);
312}
313
314
315int
316mknodat(int fd, const char *path, mode_t mode, dev_t dev)
317{
318	if (fd == AT_FDCWD || (path != NULL && path[0] == '/')) {
319		// call mknod() ignoring fd
320		return mknod(path, mode, dev);
321	}
322
323	if (fd < 0) {
324		// Invalid file descriptor
325		errno = EBADF;
326		return -1;
327	}
328
329	char fullPath[MAXPATHLEN];
330	if (get_path(fd, path, fullPath) < 0)
331		return -1;
332
333	return mknod(fullPath, mode, dev);
334}
335
336
337int
338renameat(int oldFD, const char* oldPath, int newFD, const char* newPath)
339{
340	bool ignoreOldFD = false;
341	bool ignoreNewFD = false;
342
343	if (oldFD == AT_FDCWD || (oldPath != NULL && oldPath[0] == '/'))
344		ignoreOldFD = true;
345
346	if (newFD == AT_FDCWD || (newPath != NULL && newPath[0] == '/'))
347		ignoreNewFD = true;
348
349	if (ignoreOldFD && ignoreNewFD) {
350		// call rename() ignoring the fd's
351		return rename(oldPath, newPath);
352	}
353
354	char oldFullPath[MAXPATHLEN];
355	if (!ignoreOldFD) {
356		if (oldFD < 0) {
357			// Invalid file descriptor
358			errno = EBADF;
359			return -1;
360		}
361
362		if (get_path(oldFD, oldPath, oldFullPath) < 0)
363			return -1;
364	}
365
366	char newFullPath[MAXPATHLEN];
367	if (!ignoreNewFD) {
368		if (newFD < 0) {
369			// Invalid file descriptor
370			errno = EBADF;
371			return -1;
372		}
373
374		if (get_path(newFD, newPath, newFullPath) < 0)
375			return -1;
376	}
377
378	return rename(ignoreOldFD ? oldPath : oldFullPath,
379		ignoreNewFD ? newPath : newFullPath);
380}
381
382
383ssize_t
384readlinkat(int fd, const char *path, char *buffer, size_t bufferSize)
385{
386	if (fd == AT_FDCWD || (path != NULL && path[0] == '/')) {
387		// call readlink() ignoring fd
388		return readlink(path, buffer, bufferSize);
389	}
390
391	if (fd < 0) {
392		// Invalid file descriptor
393		errno = EBADF;
394		return -1;
395	}
396
397	char fullPath[MAXPATHLEN];
398	if (get_path(fd, path, fullPath) < 0)
399		return -1;
400
401	return readlink(fullPath, buffer, bufferSize);
402}
403
404
405int
406symlinkat(const char *oldPath, int fd, const char *newPath)
407{
408	if (fd == AT_FDCWD || (newPath != NULL && newPath[0] == '/')) {
409		// call symlink() ignoring fd
410		return symlink(oldPath, newPath);
411	}
412
413	if (fd < 0) {
414		// Invalid file descriptor
415		errno = EBADF;
416		return -1;
417	}
418
419	// newPath is relative to the fd
420	char newFullPath[MAXPATHLEN];
421	if (get_path(fd, newPath, newFullPath) < 0)
422		return -1;
423
424	return symlink(oldPath, newFullPath);
425}
426
427
428int
429unlinkat(int fd, const char *path, int flag)
430{
431	if (flag != AT_REMOVEDIR && flag != 0) {
432		// invalid flag
433		errno = EINVAL;
434		return -1;
435	}
436
437	if (fd == AT_FDCWD || (path != NULL && path[0] == '/')) {
438		// call rmdir() or unlink() ignoring fd
439		return (flag & AT_REMOVEDIR) != 0 ? rmdir(path) : unlink(path);
440	}
441
442	if (fd < 0) {
443		// Invalid file descriptor
444		errno = EBADF;
445		return -1;
446	}
447
448	char fullPath[MAXPATHLEN];
449	if (get_path(fd, path, fullPath) < 0)
450		return -1;
451
452	return (flag & AT_REMOVEDIR) != 0 ? rmdir(fullPath)
453		: unlink(fullPath);
454}
455
456
457int
458linkat(int oldFD, const char *oldPath, int newFD, const char *newPath,
459	   int flag)
460{
461	if ((flag & AT_SYMLINK_FOLLOW) != 0) {
462		// Dereference oldPath
463		// CURRENTLY UNSUPPORTED
464		errno = ENOTSUP;
465		return -1;
466	} else if (flag != 0) {
467		errno = EINVAL;
468		return -1;
469	}
470
471	bool ignoreOldFD = false;
472	bool ignoreNewFD = false;
473
474	if (oldFD == AT_FDCWD || (oldPath != NULL && oldPath[0] == '/'))
475		ignoreOldFD = true;
476
477	if (newFD == AT_FDCWD || (newPath != NULL && newPath[0] == '/'))
478		ignoreNewFD = true;
479
480	if (ignoreOldFD && ignoreNewFD) {
481		// call link() ignoring the fd's
482		return link(oldPath, newPath);
483	}
484
485	char oldFullPath[MAXPATHLEN];
486	if (!ignoreOldFD) {
487		if (oldFD < 0) {
488			// Invalid file descriptor
489			errno = EBADF;
490			return -1;
491		}
492
493		if (get_path(oldFD, oldPath, oldFullPath) < 0)
494			return -1;
495	}
496
497	char newFullPath[MAXPATHLEN];
498	if (!ignoreNewFD) {
499		if (newFD < 0) {
500			// Invalid file descriptor
501			errno = EBADF;
502			return -1;
503		}
504
505		if (get_path(newFD, newPath, newFullPath) < 0)
506			return -1;
507	}
508
509	return link(ignoreOldFD ? oldPath : oldFullPath,
510		ignoreNewFD ? newPath : newFullPath);
511}
512
513
514int
515futimesat(int fd, const char *path, const struct timeval times[2])
516{
517	if (fd == AT_FDCWD || (path != NULL && path[0] == '/')) {
518		// call utimes() ignoring fd
519		return utimes(path, times);
520	}
521
522	if (fd < 0) {
523		// Invalid file descriptor
524		errno = EBADF;
525		return -1;
526	}
527
528	char fullPath[MAXPATHLEN];
529	if (get_path(fd, path, fullPath) < 0)
530		return -1;
531
532	return utimes(fullPath, times);
533}
534