xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/edit_file.c (revision 67b9b338a7386232ac596b5fd0cd5a9cc8a03c71)
1 /*	$NetBSD: edit_file.c,v 1.4 2022/10/08 16:12:50 christos Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	edit_file 3
6 /* SUMMARY
7 /*	simple cooperative file updating protocol
8 /* SYNOPSIS
9 /*	#include <edit_file.h>
10 /*
11 /*	typedef struct {
12 /* .in +4
13 /*		char	*tmp_path;	/* temp. pathname */
14 /*		VSTREAM *tmp_fp;	/* temp. stream */
15 /*		/* private members... */
16 /* .in -4
17 /*	} EDIT_FILE;
18 /*
19 /*	EDIT_FILE *edit_file_open(original_path, output_flags, output_mode)
20 /*	const char *original_path;
21 /*	int	output_flags;
22 /*	mode_t	output_mode;
23 /*
24 /*	int	edit_file_close(edit_file)
25 /*	EDIT_FILE *edit_file;
26 /*
27 /*	void	edit_file_cleanup(edit_file)
28 /*	EDIT_FILE *edit_file;
29 /* DESCRIPTION
30 /*	This module implements a simple protocol for cooperative
31 /*	processes to update one file. The idea is to 1) create a
32 /*	new file under a deterministic temporary pathname, 2)
33 /*	populate the new file with updated information, and 3)
34 /*	rename the new file into the place of the original file.
35 /*	This module provides 1) and 3), and leaves 2) to the
36 /*	application. The temporary pathname is deterministic to
37 /*	avoid accumulation of thrash after program crashes.
38 /*
39 /*	edit_file_open() implements the first phase of the protocol.
40 /*	It creates or opens an output file with a deterministic
41 /*	temporary pathname, obtained by appending the suffix defined
42 /*	with EDIT_FILE_SUFFIX to the specified original file pathname.
43 /*	The original file itself is not opened.  edit_file_open()
44 /*	then locks the output file for exclusive access, and verifies
45 /*	that the file still exists under the temporary pathname.
46 /*	At this point in the protocol, the current process controls
47 /*	both the output file content and its temporary pathname.
48 /*
49 /*	In the second phase, the application opens the original
50 /*	file if needed, and updates the output file via the
51 /*	\fBtmp_fp\fR member of the EDIT_FILE data structure.  This
52 /*	phase is not implemented by the edit_file() module.
53 /*
54 /*	edit_file_close() implements the third and final phase of
55 /*	the protocol.  It flushes the output file to persistent
56 /*	storage, and renames the output file from its temporary
57 /*	pathname into the place of the original file. When any of
58 /*	these operations fails, edit_file_close() behaves as if
59 /*	edit_file_cleanup() was called. Regardless of whether these
60 /*	operations succeed, edit_file_close() releases the exclusive
61 /*	lock, closes the output file, and frees up memory that was
62 /*	allocated by edit_file_open().
63 /*
64 /*	edit_file_cleanup() aborts the protocol. It discards the
65 /*	output file, releases the exclusive lock, closes the output
66 /*	file, and frees up memory that was allocated by edit_file_open().
67 /*
68 /*	Arguments:
69 /* .IP original_path
70 /*	The pathname of the original file that will be replaced by
71 /*	the output file. The temporary pathname for the output file
72 /*	is obtained by appending the suffix defined with EDIT_FILE_SUFFIX
73 /*	to a copy of the specified original file pathname, and is
74 /*	made available via the \fBtmp_path\fR member of the EDIT_FILE
75 /*	data structure.
76 /* .IP output_flags
77 /*	Flags for opening the output file. These are as with open(2),
78 /*	except that the O_TRUNC flag is ignored.  edit_file_open()
79 /*	always truncates the output file after it has obtained
80 /*	exclusive control over the output file content and temporary
81 /*	pathname.
82 /* .IP output_mode
83 /*	Permissions for the output file. These are as with open(2),
84 /*	except that the output file is initially created with no
85 /*	group or other access permissions. The specified output
86 /*	file permissions are applied by edit_file_close().
87 /* .IP edit_file
88 /*	Pointer to data structure that is returned upon successful
89 /*	completion by edit_file_open(), and that must be passed to
90 /*	edit_file_close() or edit_file_cleanup().
91 /* DIAGNOSTICS
92 /*	Fatal errors: memory allocation failure, fstat() failure,
93 /*	unlink() failure, lock failure, ftruncate() failure.
94 /*
95 /*	edit_file_open() immediately returns a null pointer when
96 /*	it cannot open the output file.
97 /*
98 /*	edit_file_close() returns zero on success, VSTREAM_EOF on
99 /*	failure.
100 /*
101 /*	With both functions, the global errno variable indicates
102 /*	the nature of the problem.  All errors are relative to the
103 /*	temporary output's pathname. With both functions, this
104 /*	pathname is not available via the EDIT_FILE data structure,
105 /*	because that structure was already destroyed, or not created.
106 /* BUGS
107 /*	In the non-error case, edit_file_open() will not return
108 /*	until it obtains exclusive control over the output file
109 /*	content and temporary pathname.  Applications that are
110 /*	concerned about deadlock should protect the edit_file_open()
111 /*	call with a watchdog timer.
112 /*
113 /*	When interrupted, edit_file_close() may leave behind a
114 /*	world-readable output file under the temporary pathname.
115 /*	On some systems this can be used to inflict a shared-lock
116 /*	DOS on the protocol.  Applications that are concerned about
117 /*	maximal safety should protect the edit_file_close() call
118 /*	with sigdelay() and sigresume() calls, but this introduces
119 /*	the risk that the program will get stuck forever.
120 /* LICENSE
121 /* .ad
122 /* .fi
123 /*	The Secure Mailer license must be distributed with this software.
124 /* AUTHOR(S)
125 /*	Based on code originally by:
126 /*	Victor Duchovni
127 /*	Morgan Stanley
128 /*
129 /*	Packaged into one module with minor improvements by:
130 /*	Wietse Venema
131 /*	IBM T.J. Watson Research
132 /*	P.O. Box 704
133 /*	Yorktown Heights, NY 10598, USA
134 /*--*/
135 
136 /* System library. */
137 
138 #include <sys_defs.h>
139 #include <sys/stat.h>
140 #include <stdio.h>			/* rename(2) */
141 #include <errno.h>
142 
143  /*
144   * This mask selects all permission bits in the st_mode stat data. There is
145   * no portable definition (unlike S_IFMT, which is defined for the file type
146   * bits). For example, BSD / Linux have ALLPERMS, while Solaris has S_IAMB.
147   */
148 #define FILE_PERM_MASK \
149 	(S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO)
150 
151 /* Utility Library. */
152 
153 #include <msg.h>
154 #include <vstream.h>
155 #include <mymalloc.h>
156 #include <stringops.h>
157 #include <myflock.h>
158 #include <edit_file.h>
159 #include <warn_stat.h>
160 
161  /*
162   * Do we reuse and truncate an output file that persists after a crash, or
163   * do we unlink it and create a new file?
164   */
165 #define EDIT_FILE_REUSE_AFTER_CRASH
166 
167  /*
168   * Protocol internals: the temporary file permissions.
169   */
170 #define EDIT_FILE_MODE	(S_IRUSR | S_IWUSR)	/* temp file mode */
171 
172  /*
173   * Make complex operations more readable. We could use functions, instead.
174   * The main thing is that we keep the _alloc and _free code together.
175   */
176 #define EDIT_FILE_ALLOC(ep, path, mode) do { \
177 	(ep) = (EDIT_FILE *) mymalloc(sizeof(EDIT_FILE)); \
178 	(ep)->final_path = mystrdup(path); \
179 	(ep)->final_mode = (mode); \
180 	(ep)->tmp_path = concatenate((path), EDIT_FILE_SUFFIX, (char *) 0); \
181 	(ep)->tmp_fp = 0; \
182     } while (0)
183 
184 #define EDIT_FILE_FREE(ep) do { \
185 	myfree((ep)->final_path); \
186 	myfree((ep)->tmp_path); \
187 	myfree((void *) (ep)); \
188     } while (0)
189 
190 /* edit_file_open - open and lock file with deterministic temporary pathname */
191 
edit_file_open(const char * path,int flags,mode_t mode)192 EDIT_FILE *edit_file_open(const char *path, int flags, mode_t mode)
193 {
194     struct stat before_lock;
195     struct stat after_lock;
196     int     saved_errno;
197     EDIT_FILE *ep;
198 
199     /*
200      * Initialize. Do not bother to optimize for the error case.
201      */
202     EDIT_FILE_ALLOC(ep, path, mode);
203 
204     /*
205      * As long as the output file can be opened under the temporary pathname,
206      * this code can loop or block forever.
207      *
208      * Applications that are concerned about deadlock should protect the
209      * edit_file_open() call with a watchdog timer.
210      */
211     for ( /* void */ ; /* void */ ; (void) vstream_fclose(ep->tmp_fp)) {
212 
213 	/*
214 	 * Try to open the output file under the temporary pathname. This
215 	 * succeeds or fails immediately. To avoid creating a shared-lock DOS
216 	 * opportunity after we crash, we create the output file with no
217 	 * group or other permissions, and set the final permissions at the
218 	 * end (this is one reason why we try to get exclusive control over
219 	 * the output file instead of the original file). We postpone file
220 	 * truncation until we have obtained exclusive control over the file
221 	 * content and temporary pathname. If the open operation fails, we
222 	 * give up immediately. The caller can retry the call if desirable.
223 	 *
224 	 * XXX If we replace the vstream_fopen() call by safe_open(), then we
225 	 * should replace the stat() call below by lstat().
226 	 */
227 	if ((ep->tmp_fp = vstream_fopen(ep->tmp_path, flags & ~(O_TRUNC),
228 					EDIT_FILE_MODE)) == 0) {
229 	    saved_errno = errno;
230 	    EDIT_FILE_FREE(ep);
231 	    errno = saved_errno;
232 	    return (0);
233 	}
234 
235 	/*
236 	 * At this point we may have opened an existing output file that was
237 	 * already locked. Try to lock the open file exclusively. This may
238 	 * take some time.
239 	 */
240 	if (myflock(vstream_fileno(ep->tmp_fp), INTERNAL_LOCK,
241 		    MYFLOCK_OP_EXCLUSIVE) < 0)
242 	    msg_fatal("lock %s: %m", ep->tmp_path);
243 
244 	/*
245 	 * At this point we have an exclusive lock, but some other process
246 	 * may have renamed or removed the output file while we were waiting
247 	 * for the lock. If that is the case, back out and try again.
248 	 */
249 	if (fstat(vstream_fileno(ep->tmp_fp), &before_lock) < 0)
250 	    msg_fatal("open %s: %m", ep->tmp_path);
251 	if (stat(ep->tmp_path, &after_lock) < 0
252 	    || before_lock.st_dev != after_lock.st_dev
253 	    || before_lock.st_ino != after_lock.st_ino
254 #ifdef HAS_ST_GEN
255 	    || before_lock.st_gen != after_lock.st_gen
256 #endif
257 	/* No need to compare st_rdev or st_nlink here. */
258 	    ) {
259 	    continue;
260 	}
261 
262 	/*
263 	 * At this point we have exclusive control over the output file
264 	 * content and its temporary pathname (within the rules of the
265 	 * cooperative protocol). But wait, there is more.
266 	 *
267 	 * There are many opportunities for trouble when opening a pre-existing
268 	 * output file. Here are just a few.
269 	 *
270 	 * - Victor observes that a system crash in the middle of the
271 	 * final-phase rename() operation may result in the output file
272 	 * having both the temporary pathname and the final pathname. In that
273 	 * case we must not write to the output file.
274 	 *
275 	 * - Wietse observes that crashes may also leave the output file in
276 	 * other inconsistent states. To avoid permission-related trouble, we
277 	 * simply refuse to work with an output file that has the wrong
278 	 * temporary permissions. This won't stop the shared-lock DOS if we
279 	 * crash after changing the file permissions, though.
280 	 *
281 	 * To work around these crash-related problems, remove the temporary
282 	 * pathname, back out, and try again.
283 	 */
284 	if (!S_ISREG(after_lock.st_mode)
285 #ifndef EDIT_FILE_REUSE_AFTER_CRASH
286 	    || after_lock.st_size > 0
287 #endif
288 	    || after_lock.st_nlink > 1
289 	    || (after_lock.st_mode & FILE_PERM_MASK) != EDIT_FILE_MODE) {
290 	    if (unlink(ep->tmp_path) < 0 && errno != ENOENT)
291 		msg_fatal("unlink %s: %m", ep->tmp_path);
292 	    continue;
293 	}
294 
295 	/*
296 	 * Settle the final details.
297 	 */
298 #ifdef EDIT_FILE_REUSE_AFTER_CRASH
299 	if (ftruncate(vstream_fileno(ep->tmp_fp), 0) < 0)
300 	    msg_fatal("truncate %s: %m", ep->tmp_path);
301 #endif
302 	return (ep);
303     }
304 }
305 
306 /* edit_file_cleanup - clean up without completing the protocol */
307 
edit_file_cleanup(EDIT_FILE * ep)308 void    edit_file_cleanup(EDIT_FILE *ep)
309 {
310 
311     /*
312      * Don't touch the file after we lose the exclusive lock!
313      */
314     if (unlink(ep->tmp_path) < 0 && errno != ENOENT)
315 	msg_fatal("unlink %s: %m", ep->tmp_path);
316     (void) vstream_fclose(ep->tmp_fp);
317     EDIT_FILE_FREE(ep);
318 }
319 
320 /* edit_file_close - rename the file into place and close the file */
321 
edit_file_close(EDIT_FILE * ep)322 int     edit_file_close(EDIT_FILE *ep)
323 {
324     VSTREAM *fp = ep->tmp_fp;
325     int     fd = vstream_fileno(fp);
326     int     saved_errno;
327 
328     /*
329      * The rename/unlock portion of the protocol is relatively simple. The
330      * only things that really matter here are that we change permissions as
331      * late as possible, and that we rename the file to its final pathname
332      * before we lose the exclusive lock.
333      *
334      * Applications that are concerned about maximal safety should protect the
335      * edit_file_close() call with sigdelay() and sigresume() calls. It is
336      * not safe for us to call these functions directly, because the calls do
337      * not nest. It is also not nice to force every caller to run with
338      * interrupts turned off.
339      */
340     if (vstream_fflush(fp) < 0
341 	|| fchmod(fd, ep->final_mode) < 0
342 #ifdef HAS_FSYNC
343 	|| fsync(fd) < 0
344 #endif
345 	|| rename(ep->tmp_path, ep->final_path) < 0) {
346 	saved_errno = errno;
347 	edit_file_cleanup(ep);
348 	errno = saved_errno;
349 	return (VSTREAM_EOF);
350     } else {
351 	(void) vstream_fclose(ep->tmp_fp);
352 	EDIT_FILE_FREE(ep);
353 	return (0);
354     }
355 }
356