1 /* Implement ln -f "atomically"
2 
3    Copyright 2017-2023 Free Software Foundation, Inc.
4 
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9 
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
17 
18 /* Written by Paul Eggert.  */
19 
20 /* A naive "ln -f A B" unlinks B and then links A to B.  This module
21    instead links A to a randomly-named temporary T in B's directory,
22    and then renames T to B.  This approach has a window with a
23    randomly-named temporary, which is safer for many applications than
24    a window where B does not exist.  */
25 
26 #include <config.h>
27 #include "system.h"
28 
29 #include "force-link.h"
30 
31 #include <tempname.h>
32 
33 /* A basename pattern suitable for a temporary file.  It should work
34    even on file systems like FAT that support only short names.
35    "Cu" is short for "Coreutils" or for "Changeable unstable",
36    take your pick....  */
37 
38 static char const simple_pattern[] = "CuXXXXXX";
39 enum { x_suffix_len = sizeof "XXXXXX" - 1 };
40 
41 /* A size for smallish buffers containing file names.  Longer file
42    names can use malloc.  */
43 
44 enum { smallsize = 256 };
45 
46 /* Return a template for a file in the same directory as DSTNAME.
47    Use BUF if the template fits, otherwise use malloc and return nullptr
48    (setting errno) if unsuccessful.  */
49 
50 static char *
samedir_template(char const * dstname,char buf[smallsize])51 samedir_template (char const *dstname, char buf[smallsize])
52 {
53   ptrdiff_t dstdirlen = last_component (dstname) - dstname;
54   size_t dsttmpsize = dstdirlen + sizeof simple_pattern;
55   char *dsttmp;
56   if (dsttmpsize <= smallsize)
57     dsttmp = buf;
58   else
59     {
60       dsttmp = malloc (dsttmpsize);
61       if (!dsttmp)
62         return dsttmp;
63     }
64   strcpy (mempcpy (dsttmp, dstname, dstdirlen), simple_pattern);
65   return dsttmp;
66 }
67 
68 
69 /* Auxiliaries for force_linkat.  */
70 
71 struct link_arg
72 {
73   int srcdir;
74   char const *srcname;
75   int dstdir;
76   int flags;
77 };
78 
79 static int
try_link(char * dest,void * arg)80 try_link (char *dest, void *arg)
81 {
82   struct link_arg *a = arg;
83   return linkat (a->srcdir, a->srcname, a->dstdir, dest, a->flags);
84 }
85 
86 /* Hard-link directory SRCDIR's file SRCNAME to directory DSTDIR's
87    file DSTNAME, using linkat-style FLAGS to control the linking.
88    If FORCE and DSTNAME already exists, replace it atomically.
89    If LINKAT_ERRNO is 0, the hard link is already done; if positive,
90    the hard link was tried and failed with errno == LINKAT_ERRNO.  Return
91    -1 if successful and DSTNAME already existed,
92    0 if successful and DSTNAME did not already exist, and
93    a positive errno value on failure.  */
94 extern int
force_linkat(int srcdir,char const * srcname,int dstdir,char const * dstname,int flags,bool force,int linkat_errno)95 force_linkat (int srcdir, char const *srcname,
96               int dstdir, char const *dstname, int flags, bool force,
97               int linkat_errno)
98 {
99   if (linkat_errno < 0)
100     linkat_errno = (linkat (srcdir, srcname, dstdir, dstname, flags) == 0
101                     ? 0 : errno);
102   if (!force || linkat_errno != EEXIST)
103     return linkat_errno;
104 
105   char buf[smallsize];
106   char *dsttmp = samedir_template (dstname, buf);
107   if (! dsttmp)
108     return errno;
109   struct link_arg arg = { srcdir, srcname, dstdir, flags };
110   int err;
111 
112   if (try_tempname_len (dsttmp, 0, &arg, try_link, x_suffix_len) != 0)
113     err = errno;
114   else
115     {
116       err = renameat (dstdir, dsttmp, dstdir, dstname) == 0 ? -1 : errno;
117       /* Unlink DSTTMP even if renameat succeeded, in case DSTTMP
118          and DSTNAME were already the same hard link and renameat
119          was a no-op.  */
120       unlinkat (dstdir, dsttmp, 0);
121     }
122 
123   if (dsttmp != buf)
124     free (dsttmp);
125   return err;
126 }
127 
128 
129 /* Auxiliaries for force_symlinkat.  */
130 
131 struct symlink_arg
132 {
133   char const *srcname;
134   int dstdir;
135 };
136 
137 static int
try_symlink(char * dest,void * arg)138 try_symlink (char *dest, void *arg)
139 {
140   struct symlink_arg *a = arg;
141   return symlinkat (a->srcname, a->dstdir, dest);
142 }
143 
144 /* Create a symlink containing SRCNAME in directory DSTDIR's file DSTNAME.
145    If FORCE and DSTNAME already exists, replace it atomically.
146    If SYMLINKAT_ERRNO is 0, the symlink is already done; if positive,
147    the symlink was tried and failed with errno == SYMLINKAT_ERRNO.  Return
148    -1 if successful and DSTNAME already existed,
149    0 if successful and DSTNAME did not already exist, and
150    a positive errno value on failure.  */
151 extern int
force_symlinkat(char const * srcname,int dstdir,char const * dstname,bool force,int symlinkat_errno)152 force_symlinkat (char const *srcname, int dstdir, char const *dstname,
153                  bool force, int symlinkat_errno)
154 {
155   if (symlinkat_errno < 0)
156     symlinkat_errno = symlinkat (srcname, dstdir, dstname) == 0 ? 0 : errno;
157   if (!force || symlinkat_errno != EEXIST)
158     return symlinkat_errno;
159 
160   char buf[smallsize];
161   char *dsttmp = samedir_template (dstname, buf);
162   if (!dsttmp)
163     return errno;
164   struct symlink_arg arg = { srcname, dstdir };
165   int err;
166 
167   if (try_tempname_len (dsttmp, 0, &arg, try_symlink, x_suffix_len) != 0)
168     err = errno;
169   else if (renameat (dstdir, dsttmp, dstdir, dstname) != 0)
170     {
171       err = errno;
172       unlinkat (dstdir, dsttmp, 0);
173     }
174   else
175     {
176       /* Don't worry about renameat being a no-op, since DSTTMP is
177          newly created.  */
178       err = -1;
179     }
180 
181   if (dsttmp != buf)
182     free (dsttmp);
183   return err;
184 }
185