1/* Define an at-style functions like linkat or renameat.
2   Copyright (C) 2006, 2009-2010 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 3 of the License, or
7   (at your option) 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, see <http://www.gnu.org/licenses/>.  */
16
17/* written by Jim Meyering and Eric Blake */
18
19#include <config.h>
20
21#include "openat-priv.h"
22
23#include <errno.h>
24#include <stdlib.h>
25#include <string.h>
26#include <unistd.h>
27
28#include "dirname.h" /* solely for definition of IS_ABSOLUTE_FILE_NAME */
29#include "filenamecat.h"
30#include "openat.h"
31#include "same-inode.h"
32#include "save-cwd.h"
33
34/* Call FUNC to operate on a pair of files, where FILE1 is relative to FD1,
35   and FILE2 is relative to FD2.  If possible, do it without changing the
36   working directory.  Otherwise, resort to using save_cwd/fchdir,
37   FUNC, restore_cwd (up to two times).  If either the save_cwd or the
38   restore_cwd fails, then give a diagnostic and exit nonzero.  */
39int
40at_func2 (int fd1, char const *file1,
41          int fd2, char const *file2,
42          int (*func) (char const *file1, char const *file2))
43{
44  struct saved_cwd saved_cwd;
45  int saved_errno;
46  int err;
47  char *file1_alt;
48  char *file2_alt;
49  struct stat st1;
50  struct stat st2;
51
52  /* There are 16 possible scenarios, based on whether an fd is
53     AT_FDCWD or real, and whether a file is absolute or relative:
54
55         fd1  file1 fd2  file2  action
56     0   cwd  abs   cwd  abs    direct call
57     1   cwd  abs   cwd  rel    direct call
58     2   cwd  abs   fd   abs    direct call
59     3   cwd  abs   fd   rel    chdir to fd2
60     4   cwd  rel   cwd  abs    direct call
61     5   cwd  rel   cwd  rel    direct call
62     6   cwd  rel   fd   abs    direct call
63     7   cwd  rel   fd   rel    convert file1 to abs, then case 3
64     8   fd   abs   cwd  abs    direct call
65     9   fd   abs   cwd  rel    direct call
66     10  fd   abs   fd   abs    direct call
67     11  fd   abs   fd   rel    chdir to fd2
68     12  fd   rel   cwd  abs    chdir to fd1
69     13  fd   rel   cwd  rel    convert file2 to abs, then case 12
70     14  fd   rel   fd   abs    chdir to fd1
71     15a fd1  rel   fd1  rel    chdir to fd1
72     15b fd1  rel   fd2  rel    chdir to fd1, then case 7
73
74     Try some optimizations to reduce fd to AT_FDCWD, or to at least
75     avoid converting an absolute name or doing a double chdir.  */
76
77  if ((fd1 == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file1))
78      && (fd2 == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file2)))
79    return func (file1, file2); /* Case 0-2, 4-6, 8-10.  */
80
81  /* If /proc/self/fd works, we don't need any stat or chdir.  */
82  {
83    char proc_buf1[OPENAT_BUFFER_SIZE];
84    char *proc_file1 = ((fd1 == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file1))
85                        ? (char *) file1
86                        : openat_proc_name (proc_buf1, fd1, file1));
87    if (proc_file1)
88      {
89        char proc_buf2[OPENAT_BUFFER_SIZE];
90        char *proc_file2 = ((fd2 == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file2))
91                            ? (char *) file2
92                            : openat_proc_name (proc_buf2, fd2, file2));
93        if (proc_file2)
94          {
95            int proc_result = func (proc_file1, proc_file2);
96            int proc_errno = errno;
97            if (proc_file1 != proc_buf1 && proc_file1 != file1)
98              free (proc_file1);
99            if (proc_file2 != proc_buf2 && proc_file2 != file2)
100              free (proc_file2);
101            /* If the syscall succeeds, or if it fails with an unexpected
102               errno value, then return right away.  Otherwise, fall through
103               and resort to using save_cwd/restore_cwd.  */
104            if (0 <= proc_result)
105              return proc_result;
106            if (! EXPECTED_ERRNO (proc_errno))
107              {
108                errno = proc_errno;
109                return proc_result;
110              }
111          }
112        else if (proc_file1 != proc_buf1 && proc_file1 != file1)
113          free (proc_file1);
114      }
115  }
116
117  /* Cases 3, 7, 11-15 remain.  Time to normalize directory fds, if
118     possible.  */
119  if (IS_ABSOLUTE_FILE_NAME (file1))
120    fd1 = AT_FDCWD; /* Case 11 reduced to 3.  */
121  else if (IS_ABSOLUTE_FILE_NAME (file2))
122    fd2 = AT_FDCWD; /* Case 14 reduced to 12.  */
123
124  /* Cases 3, 7, 12, 13, 15 remain.  */
125
126  if (fd1 == AT_FDCWD) /* Cases 3, 7.  */
127    {
128      if (stat (".", &st1) == -1 || fstat (fd2, &st2) == -1)
129        return -1;
130      if (!S_ISDIR (st2.st_mode))
131        {
132          errno = ENOTDIR;
133          return -1;
134        }
135      if (SAME_INODE (st1, st2)) /* Reduced to cases 1, 5.  */
136        return func (file1, file2);
137    }
138  else if (fd2 == AT_FDCWD) /* Cases 12, 13.  */
139    {
140      if (stat (".", &st2) == -1 || fstat (fd1, &st1) == -1)
141        return -1;
142      if (!S_ISDIR (st1.st_mode))
143        {
144          errno = ENOTDIR;
145          return -1;
146        }
147      if (SAME_INODE (st1, st2)) /* Reduced to cases 4, 5.  */
148        return func (file1, file2);
149    }
150  else if (fd1 != fd2) /* Case 15b.  */
151    {
152      if (fstat (fd1, &st1) == -1 || fstat (fd2, &st2) == -1)
153        return -1;
154      if (!S_ISDIR (st1.st_mode) || !S_ISDIR (st2.st_mode))
155        {
156          errno = ENOTDIR;
157          return -1;
158        }
159      if (SAME_INODE (st1, st2)) /* Reduced to case 15a.  */
160        {
161          fd2 = fd1;
162          if (stat (".", &st1) == 0 && SAME_INODE (st1, st2))
163            return func (file1, file2); /* Further reduced to case 5.  */
164        }
165    }
166  else /* Case 15a.  */
167    {
168      if (fstat (fd1, &st1) == -1)
169        return -1;
170      if (!S_ISDIR (st1.st_mode))
171        {
172          errno = ENOTDIR;
173          return -1;
174        }
175      if (stat (".", &st2) == 0 && SAME_INODE (st1, st2))
176        return func (file1, file2); /* Reduced to case 5.  */
177    }
178
179  /* Cases 3, 7, 12, 13, 15a, 15b remain.  With all reductions in
180     place, it is time to start changing directories.  */
181
182  if (save_cwd (&saved_cwd) != 0)
183    openat_save_fail (errno);
184
185  if (fd1 != AT_FDCWD && fd2 != AT_FDCWD && fd1 != fd2) /* Case 15b.  */
186    {
187      if (fchdir (fd1) != 0)
188        {
189          saved_errno = errno;
190          free_cwd (&saved_cwd);
191          errno = saved_errno;
192          return -1;
193        }
194      fd1 = AT_FDCWD; /* Reduced to case 7.  */
195    }
196
197  /* Cases 3, 7, 12, 13, 15a remain.  Convert one relative name to
198     absolute, if necessary.  */
199
200  file1_alt = (char *) file1;
201  file2_alt = (char *) file2;
202
203  if (fd1 == AT_FDCWD && !IS_ABSOLUTE_FILE_NAME (file1)) /* Case 7.  */
204    {
205      /* It would be nicer to use:
206         file1_alt = file_name_concat (xgetcwd (), file1, NULL);
207         but libraries should not call xalloc_die.  */
208      char *cwd = getcwd (NULL, 0);
209      if (!cwd)
210        {
211          saved_errno = errno;
212          free_cwd (&saved_cwd);
213          errno = saved_errno;
214          return -1;
215        }
216      file1_alt = mfile_name_concat (cwd, file1, NULL);
217      if (!file1_alt)
218        {
219          saved_errno = errno;
220          free (cwd);
221          free_cwd (&saved_cwd);
222          errno = saved_errno;
223          return -1;
224        }
225      free (cwd); /* Reduced to case 3.  */
226    }
227  else if (fd2 == AT_FDCWD && !IS_ABSOLUTE_FILE_NAME (file2)) /* Case 13.  */
228    {
229      char *cwd = getcwd (NULL, 0);
230      if (!cwd)
231        {
232          saved_errno = errno;
233          free_cwd (&saved_cwd);
234          errno = saved_errno;
235          return -1;
236        }
237      file2_alt = mfile_name_concat (cwd, file2, NULL);
238      if (!file2_alt)
239        {
240          saved_errno = errno;
241          free (cwd);
242          free_cwd (&saved_cwd);
243          errno = saved_errno;
244          return -1;
245        }
246      free (cwd); /* Reduced to case 12.  */
247    }
248
249  /* Cases 3, 12, 15a remain.  Change to the correct directory.  */
250  if (fchdir (fd1 == AT_FDCWD ? fd2 : fd1) != 0)
251    {
252      saved_errno = errno;
253      free_cwd (&saved_cwd);
254      if (file1 != file1_alt)
255        free (file1_alt);
256      else if (file2 != file2_alt)
257        free (file2_alt);
258      errno = saved_errno;
259      return -1;
260    }
261
262  /* Finally safe to perform the user's function, then clean up.  */
263
264  err = func (file1_alt, file2_alt);
265  saved_errno = (err < 0 ? errno : 0);
266
267  if (file1 != file1_alt)
268    free (file1_alt);
269  else if (file2 != file2_alt)
270    free (file2_alt);
271
272  if (restore_cwd (&saved_cwd) != 0)
273    openat_restore_fail (errno);
274
275  free_cwd (&saved_cwd);
276
277  if (saved_errno)
278    errno = saved_errno;
279  return err;
280}
281#undef CALL_FUNC
282#undef FUNC_RESULT
283