maint: update copyright
[gnulib.git] / lib / at-func2.c
1 /* Define 2-FD at-style functions like linkat or renameat.
2    Copyright (C) 2006, 2009-2014 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 "dosname.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.  */
39 int
40 at_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