Tizen 2.0 Release
[external/tizen-coreutils.git] / lib / openat.c
1 /* provide a replacement openat function
2    Copyright (C) 2004, 2005, 2006 Free Software Foundation, Inc.
3
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2, or (at your option)
7    any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software Foundation,
16    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
17
18 /* written by Jim Meyering */
19
20 #include <config.h>
21
22 #include "openat.h"
23
24 #include <stdarg.h>
25 #include <stddef.h>
26
27 #include "dirname.h" /* solely for definition of IS_ABSOLUTE_FILE_NAME */
28 #include "fcntl--.h"
29 #include "lchown.h"
30 #include "lstat.h"
31 #include "openat-priv.h"
32 #include "save-cwd.h"
33
34 /* Replacement for Solaris' openat function.
35    <http://www.google.com/search?q=openat+site:docs.sun.com>
36    First, try to simulate it via open ("/proc/self/fd/FD/FILE").
37    Failing that, simulate it by doing save_cwd/fchdir/open/restore_cwd.
38    If either the save_cwd or the restore_cwd fails (relatively unlikely),
39    then give a diagnostic and exit nonzero.
40    Otherwise, upon failure, set errno and return -1, as openat does.
41    Upon successful completion, return a file descriptor.  */
42 int
43 openat (int fd, char const *file, int flags, ...)
44 {
45   mode_t mode = 0;
46
47   if (flags & O_CREAT)
48     {
49       va_list arg;
50       va_start (arg, flags);
51
52       /* If mode_t is narrower than int, use the promoted type (int),
53          not mode_t.  Use sizeof to guess whether mode_t is narrower;
54          we don't know of any practical counterexamples.  */
55       mode = (sizeof (mode_t) < sizeof (int)
56               ? va_arg (arg, int)
57               : va_arg (arg, mode_t));
58
59       va_end (arg);
60     }
61
62   return openat_permissive (fd, file, flags, mode, NULL);
63 }
64
65 /* Like openat (FD, FILE, FLAGS, MODE), but if CWD_ERRNO is
66    nonnull, set *CWD_ERRNO to an errno value if unable to save
67    or restore the initial working directory.  This is needed only
68    the first time remove.c's remove_dir opens a command-line
69    directory argument.
70
71    If a previous attempt to restore the current working directory
72    failed, then we must not even try to access a `.'-relative name.
73    It is the caller's responsibility not to call this function
74    in that case.  */
75
76 int
77 openat_permissive (int fd, char const *file, int flags, mode_t mode,
78                    int *cwd_errno)
79 {
80   struct saved_cwd saved_cwd;
81   int saved_errno;
82   int err;
83   bool save_ok;
84
85   if (fd == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file))
86     return open (file, flags, mode);
87
88   {
89     char buf[OPENAT_BUFFER_SIZE];
90     char *proc_file = openat_proc_name (buf, fd, file);
91     if (proc_file)
92       {
93         int open_result = open (proc_file, flags, mode);
94         int open_errno = errno;
95         if (proc_file != buf)
96           free (proc_file);
97         /* If the syscall succeeds, or if it fails with an unexpected
98            errno value, then return right away.  Otherwise, fall through
99            and resort to using save_cwd/restore_cwd.  */
100         if (0 <= open_result || ! EXPECTED_ERRNO (open_errno))
101           {
102             errno = open_errno;
103             return open_result;
104           }
105       }
106   }
107
108   save_ok = (save_cwd (&saved_cwd) == 0);
109   if (! save_ok)
110     {
111       if (! cwd_errno)
112         openat_save_fail (errno);
113       *cwd_errno = errno;
114     }
115
116   err = fchdir (fd);
117   saved_errno = errno;
118
119   if (! err)
120     {
121       err = open (file, flags, mode);
122       saved_errno = errno;
123       if (save_ok && restore_cwd (&saved_cwd) != 0)
124         {
125           if (! cwd_errno)
126             openat_restore_fail (errno);
127           *cwd_errno = errno;
128         }
129     }
130
131   free_cwd (&saved_cwd);
132   errno = saved_errno;
133   return err;
134 }
135
136 /* Return true if our openat implementation must resort to
137    using save_cwd and restore_cwd.  */
138 bool
139 openat_needs_fchdir (void)
140 {
141   bool needs_fchdir = true;
142   int fd = open ("/", O_RDONLY);
143
144   if (0 <= fd)
145     {
146       char buf[OPENAT_BUFFER_SIZE];
147       char *proc_file = openat_proc_name (buf, fd, ".");
148       if (proc_file)
149         {
150           needs_fchdir = false;
151           if (proc_file != buf)
152             free (proc_file);
153         }
154       close (fd);
155     }
156
157   return needs_fchdir;
158 }
159
160 #if !HAVE_FDOPENDIR
161
162 /* Replacement for Solaris' function by the same name.
163    <http://www.google.com/search?q=fdopendir+site:docs.sun.com>
164    First, try to simulate it via opendir ("/proc/self/fd/FD").  Failing
165    that, simulate it by doing save_cwd/fchdir/opendir(".")/restore_cwd.
166    If either the save_cwd or the restore_cwd fails (relatively unlikely),
167    then give a diagnostic and exit nonzero.
168    Otherwise, this function works just like Solaris' fdopendir.
169
170    W A R N I N G:
171    Unlike the other fd-related functions here, this one
172    effectively consumes its FD parameter.  The caller should not
173    close or otherwise manipulate FD if this function returns successfully.  */
174 DIR *
175 fdopendir (int fd)
176 {
177   struct saved_cwd saved_cwd;
178   int saved_errno;
179   DIR *dir;
180
181   char buf[OPENAT_BUFFER_SIZE];
182   char *proc_file = openat_proc_name (buf, fd, ".");
183   if (proc_file)
184     {
185       dir = opendir (proc_file);
186       saved_errno = errno;
187     }
188   else
189     {
190       dir = NULL;
191       saved_errno = EOPNOTSUPP;
192     }
193
194   /* If the syscall fails with an expected errno value, resort to
195      save_cwd/restore_cwd.  */
196   if (! dir && EXPECTED_ERRNO (saved_errno))
197     {
198       if (save_cwd (&saved_cwd) != 0)
199         openat_save_fail (errno);
200
201       if (fchdir (fd) != 0)
202         {
203           dir = NULL;
204           saved_errno = errno;
205         }
206       else
207         {
208           dir = opendir (".");
209           saved_errno = errno;
210
211           if (restore_cwd (&saved_cwd) != 0)
212             openat_restore_fail (errno);
213         }
214
215       free_cwd (&saved_cwd);
216     }
217
218   if (dir)
219     close (fd);
220   if (proc_file != buf)
221     free (proc_file);
222   errno = saved_errno;
223   return dir;
224 }
225
226 #endif
227
228 /* Replacement for Solaris' function by the same name.
229    <http://www.google.com/search?q=fstatat+site:docs.sun.com>
230    First, try to simulate it via l?stat ("/proc/self/fd/FD/FILE").
231    Failing that, simulate it via save_cwd/fchdir/(stat|lstat)/restore_cwd.
232    If either the save_cwd or the restore_cwd fails (relatively unlikely),
233    then give a diagnostic and exit nonzero.
234    Otherwise, this function works just like Solaris' fstatat.  */
235
236 #define AT_FUNC_NAME fstatat
237 #define AT_FUNC_F1 lstat
238 #define AT_FUNC_F2 stat
239 #define AT_FUNC_USE_F1_COND flag == AT_SYMLINK_NOFOLLOW
240 #define AT_FUNC_POST_FILE_PARAM_DECLS , struct stat *st, int flag
241 #define AT_FUNC_POST_FILE_ARGS        , st
242 #include "at-func.c"
243 #undef AT_FUNC_NAME
244 #undef AT_FUNC_F1
245 #undef AT_FUNC_F2
246 #undef AT_FUNC_USE_F1_COND
247 #undef AT_FUNC_POST_FILE_PARAM_DECLS
248 #undef AT_FUNC_POST_FILE_ARGS
249
250 /* Replacement for Solaris' function by the same name.
251    <http://www.google.com/search?q=unlinkat+site:docs.sun.com>
252    First, try to simulate it via (unlink|rmdir) ("/proc/self/fd/FD/FILE").
253    Failing that, simulate it via save_cwd/fchdir/(unlink|rmdir)/restore_cwd.
254    If either the save_cwd or the restore_cwd fails (relatively unlikely),
255    then give a diagnostic and exit nonzero.
256    Otherwise, this function works just like Solaris' unlinkat.  */
257
258 #define AT_FUNC_NAME unlinkat
259 #define AT_FUNC_F1 rmdir
260 #define AT_FUNC_F2 unlink
261 #define AT_FUNC_USE_F1_COND flag == AT_REMOVEDIR
262 #define AT_FUNC_POST_FILE_PARAM_DECLS , int flag
263 #define AT_FUNC_POST_FILE_ARGS        /* empty */
264 #include "at-func.c"
265 #undef AT_FUNC_NAME
266 #undef AT_FUNC_F1
267 #undef AT_FUNC_F2
268 #undef AT_FUNC_USE_F1_COND
269 #undef AT_FUNC_POST_FILE_PARAM_DECLS
270 #undef AT_FUNC_POST_FILE_ARGS