1 /* @(#)openat.c 1.3 18/07/16 Copyright 2011-2018 J. Schilling */
2 /*
3 * Emulate the behavior of openat(fd, name, flag, mode)
4 *
5 * Note that emulation methods that do not use the /proc filesystem are
6 * not MT safe. In the non-MT-safe case, we do:
7 *
8 * savewd()/fchdir()/open(name)/restorewd()
9 *
10 * Since the /proc method may fail with ENAMETOOLONG, we need to fall back
11 * to the fchdir() method in case of long path names and as a result are
12 * not MT-safe with long path names.
13 *
14 * Errors may force us to abort the program as our caller is not expected
15 * to know that we do more than a simple open() here and that the
16 * working directory may be changed by us.
17 *
18 * Copyright (c) 2011-2018 J. Schilling
19 */
20 /*
21 * The contents of this file are subject to the terms of the
22 * Common Development and Distribution License, Version 1.0 only
23 * (the "License"). You may not use this file except in compliance
24 * with the License.
25 *
26 * See the file CDDL.Schily.txt in this distribution for details.
27 * A copy of the CDDL is also available via the Internet at
28 * http://www.opensource.org/licenses/cddl1.txt
29 *
30 * When distributing Covered Code, include this CDDL HEADER in each
31 * file and include the License file CDDL.Schily.txt from this distribution.
32 */
33
34 #include <schily/unistd.h>
35 #include <schily/types.h>
36 #include <schily/fcntl.h>
37 #include <schily/varargs.h>
38 #include <schily/maxpath.h>
39 #include <schily/errno.h>
40 #include <schily/standard.h>
41 #include <schily/schily.h>
42 #include "at-defs.h"
43
44 #ifndef HAVE_OPENAT
45
46 #ifndef ENAMETOOLONG
47 #define ENAMETOOLONG EINVAL
48 #endif
49
50 /* VARARGS3 */
51 #ifdef PROTOTYPES
52 EXPORT int
openat(int fd,const char * name,int oflag,...)53 openat(int fd, const char *name, int oflag, ...)
54 #else
55 EXPORT int
56 openat(fd, name, oflag, va_alist)
57 int fd;
58 const char *name;
59 int oflag;
60 va_dcl
61 #endif
62 {
63 va_list args;
64 mode_t omode = 0;
65 int n;
66 int ret;
67 char buf[PATH_MAX];
68 char *proc_name;
69 struct save_wd save_wd;
70
71 if (oflag & O_CREAT) {
72 #ifdef PROTOTYPES
73 va_start(args, oflag);
74 #else
75 va_start(args);
76 #endif
77 /*
78 * If sizeof (mode_t) is < sizeof (int) and used with va_arg(),
79 * GCC4 will abort the code. So we need to use the promoted
80 * size.
81 */
82 omode = va_arg(args, PROMOTED_MODE_T);
83 va_end(args);
84 }
85 if (fd == AT_FDCWD || ABS_NAME(name))
86 return (open(name, oflag, omode));
87
88 if ((proc_name = proc_fd2name(buf, fd, name)) != NULL) {
89 /*
90 * The next open() frequently results in ENAMETOOLONG
91 */
92 ret = open(proc_name, oflag, omode);
93 if (ret >= 0 || NON_PROCFS_ERRNO(errno))
94 return (ret);
95 }
96
97 /*
98 * /proc open failed or /proc not available on this platform.
99 * Give it a chance using fchdir(). But the following code is
100 * not MT-safe!
101 */
102
103 if (savewd(&save_wd) < 0) {
104 /*
105 * We abort here as the caller may not know that we are forced
106 * to savewd/fchdir/restorewd here and misinterpret errno.
107 */
108 savewd_abort(geterrno());
109 /* NOTREACHED */
110 return (-1);
111 }
112 if (fd >= 0 && save_wd.fd == fd) {
113 /*
114 * If we just opened "fd" with the same number in savewd(), fd
115 * must have been illegal when calling openat();
116 */
117 closewd(&save_wd);
118 seterrno(EBADF);
119 return (-1);
120 }
121 if ((n = fchdir(fd)) < 0) {
122 int err = geterrno();
123 /*
124 * In case that fchdir() is emulated via chdir() and we use a
125 * multi hop chdir(), we may be on an undefined intermediate
126 * directory. Try to return to last working directory and if
127 * this fails, abort for security.
128 */
129 if (n == -2 && restorewd(&save_wd) < 0) {
130 restorewd_abort(geterrno());
131 }
132 closewd(&save_wd);
133 seterrno(err);
134 return (-1);
135 }
136
137 ret = open(name, oflag, omode); /* The actual open() */
138
139 if (restorewd(&save_wd) < 0) {
140 int err = geterrno();
141
142 close(ret);
143 closewd(&save_wd);
144 restorewd_abort(err);
145 seterrno(err);
146 return (-1);
147 }
148 closewd(&save_wd);
149
150 return (ret);
151 }
152
153 #endif /* HAVE_OPENAT */
154