summaryrefslogtreecommitdiffstats
path: root/src/util/edit_file.c
blob: 9d76b9314cd03b1c98284d76dbbdd7f9d5ec0732 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
/*++
/* NAME
/*	edit_file 3
/* SUMMARY
/*	simple cooperative file updating protocol
/* SYNOPSIS
/*	#include <edit_file.h>
/*
/*	typedef struct {
/* .in +4
/*		char	*tmp_path;	/* temp. pathname */
/*		VSTREAM *tmp_fp;	/* temp. stream */
/*		/* private members... */
/* .in -4
/*	} EDIT_FILE;
/*
/*	EDIT_FILE *edit_file_open(original_path, output_flags, output_mode)
/*	const char *original_path;
/*	int	output_flags;
/*	mode_t	output_mode;
/*
/*	int	edit_file_close(edit_file)
/*	EDIT_FILE *edit_file;
/*
/*	void	edit_file_cleanup(edit_file)
/*	EDIT_FILE *edit_file;
/* DESCRIPTION
/*	This module implements a simple protocol for cooperative
/*	processes to update one file. The idea is to 1) create a
/*	new file under a deterministic temporary pathname, 2)
/*	populate the new file with updated information, and 3)
/*	rename the new file into the place of the original file.
/*	This module provides 1) and 3), and leaves 2) to the
/*	application. The temporary pathname is deterministic to
/*	avoid accumulation of thrash after program crashes.
/*
/*	edit_file_open() implements the first phase of the protocol.
/*	It creates or opens an output file with a deterministic
/*	temporary pathname, obtained by appending the suffix defined
/*	with EDIT_FILE_SUFFIX to the specified original file pathname.
/*	The original file itself is not opened.  edit_file_open()
/*	then locks the output file for exclusive access, and verifies
/*	that the file still exists under the temporary pathname.
/*	At this point in the protocol, the current process controls
/*	both the output file content and its temporary pathname.
/*
/*	In the second phase, the application opens the original
/*	file if needed, and updates the output file via the
/*	\fBtmp_fp\fR member of the EDIT_FILE data structure.  This
/*	phase is not implemented by the edit_file() module.
/*
/*	edit_file_close() implements the third and final phase of
/*	the protocol.  It flushes the output file to persistent
/*	storage, and renames the output file from its temporary
/*	pathname into the place of the original file. When any of
/*	these operations fails, edit_file_close() behaves as if
/*	edit_file_cleanup() was called. Regardless of whether these
/*	operations succeed, edit_file_close() releases the exclusive
/*	lock, closes the output file, and frees up memory that was
/*	allocated by edit_file_open().
/*
/*	edit_file_cleanup() aborts the protocol. It discards the
/*	output file, releases the exclusive lock, closes the output
/*	file, and frees up memory that was allocated by edit_file_open().
/*
/*	Arguments:
/* .IP original_path
/*	The pathname of the original file that will be replaced by
/*	the output file. The temporary pathname for the output file
/*	is obtained by appending the suffix defined with EDIT_FILE_SUFFIX
/*	to a copy of the specified original file pathname, and is
/*	made available via the \fBtmp_path\fR member of the EDIT_FILE
/*	data structure.
/* .IP output_flags
/*	Flags for opening the output file. These are as with open(2),
/*	except that the O_TRUNC flag is ignored.  edit_file_open()
/*	always truncates the output file after it has obtained
/*	exclusive control over the output file content and temporary
/*	pathname.
/* .IP output_mode
/*	Permissions for the output file. These are as with open(2),
/*	except that the output file is initially created with no
/*	group or other access permissions. The specified output
/*	file permissions are applied by edit_file_close().
/* .IP edit_file
/*	Pointer to data structure that is returned upon successful
/*	completion by edit_file_open(), and that must be passed to
/*	edit_file_close() or edit_file_cleanup().
/* DIAGNOSTICS
/*	Fatal errors: memory allocation failure, fstat() failure,
/*	unlink() failure, lock failure, ftruncate() failure.
/*
/*	edit_file_open() immediately returns a null pointer when
/*	it cannot open the output file.
/*
/*	edit_file_close() returns zero on success, VSTREAM_EOF on
/*	failure.
/*
/*	With both functions, the global errno variable indicates
/*	the nature of the problem.  All errors are relative to the
/*	temporary output's pathname. With both functions, this
/*	pathname is not available via the EDIT_FILE data structure,
/*	because that structure was already destroyed, or not created.
/* BUGS
/*	In the non-error case, edit_file_open() will not return
/*	until it obtains exclusive control over the output file
/*	content and temporary pathname.  Applications that are
/*	concerned about deadlock should protect the edit_file_open()
/*	call with a watchdog timer.
/*
/*	When interrupted, edit_file_close() may leave behind a
/*	world-readable output file under the temporary pathname.
/*	On some systems this can be used to inflict a shared-lock
/*	DOS on the protocol.  Applications that are concerned about
/*	maximal safety should protect the edit_file_close() call
/*	with sigdelay() and sigresume() calls, but this introduces
/*	the risk that the program will get stuck forever.
/* LICENSE
/* .ad
/* .fi
/*	The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/*	Based on code originally by:
/*	Victor Duchovni
/*	Morgan Stanley
/*
/*	Packaged into one module with minor improvements by:
/*	Wietse Venema
/*	IBM T.J. Watson Research
/*	P.O. Box 704
/*	Yorktown Heights, NY 10598, USA
/*--*/

/* System library. */

#include <sys_defs.h>
#include <sys/stat.h>
#include <stdio.h>			/* rename(2) */
#include <errno.h>

 /*
  * This mask selects all permission bits in the st_mode stat data. There is
  * no portable definition (unlike S_IFMT, which is defined for the file type
  * bits). For example, BSD / Linux have ALLPERMS, while Solaris has S_IAMB.
  */
#define FILE_PERM_MASK \
	(S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO)

/* Utility Library. */

#include <msg.h>
#include <vstream.h>
#include <mymalloc.h>
#include <stringops.h>
#include <myflock.h>
#include <edit_file.h>
#include <warn_stat.h>

 /*
  * Do we reuse and truncate an output file that persists after a crash, or
  * do we unlink it and create a new file?
  */
#define EDIT_FILE_REUSE_AFTER_CRASH

 /*
  * Protocol internals: the temporary file permissions.
  */
#define EDIT_FILE_MODE	(S_IRUSR | S_IWUSR)	/* temp file mode */

 /*
  * Make complex operations more readable. We could use functions, instead.
  * The main thing is that we keep the _alloc and _free code together.
  */
#define EDIT_FILE_ALLOC(ep, path, mode) do { \
	(ep) = (EDIT_FILE *) mymalloc(sizeof(EDIT_FILE)); \
	(ep)->final_path = mystrdup(path); \
	(ep)->final_mode = (mode); \
	(ep)->tmp_path = concatenate((path), EDIT_FILE_SUFFIX, (char *) 0); \
	(ep)->tmp_fp = 0; \
    } while (0)

#define EDIT_FILE_FREE(ep) do { \
	myfree((ep)->final_path); \
	myfree((ep)->tmp_path); \
	myfree((void *) (ep)); \
    } while (0)

/* edit_file_open - open and lock file with deterministic temporary pathname */

EDIT_FILE *edit_file_open(const char *path, int flags, mode_t mode)
{
    struct stat before_lock;
    struct stat after_lock;
    int     saved_errno;
    EDIT_FILE *ep;

    /*
     * Initialize. Do not bother to optimize for the error case.
     */
    EDIT_FILE_ALLOC(ep, path, mode);

    /*
     * As long as the output file can be opened under the temporary pathname,
     * this code can loop or block forever.
     * 
     * Applications that are concerned about deadlock should protect the
     * edit_file_open() call with a watchdog timer.
     */
    for ( /* void */ ; /* void */ ; (void) vstream_fclose(ep->tmp_fp)) {

	/*
	 * Try to open the output file under the temporary pathname. This
	 * succeeds or fails immediately. To avoid creating a shared-lock DOS
	 * opportunity after we crash, we create the output file with no
	 * group or other permissions, and set the final permissions at the
	 * end (this is one reason why we try to get exclusive control over
	 * the output file instead of the original file). We postpone file
	 * truncation until we have obtained exclusive control over the file
	 * content and temporary pathname. If the open operation fails, we
	 * give up immediately. The caller can retry the call if desirable.
	 * 
	 * XXX If we replace the vstream_fopen() call by safe_open(), then we
	 * should replace the stat() call below by lstat().
	 */
	if ((ep->tmp_fp = vstream_fopen(ep->tmp_path, flags & ~(O_TRUNC),
					EDIT_FILE_MODE)) == 0) {
	    saved_errno = errno;
	    EDIT_FILE_FREE(ep);
	    errno = saved_errno;
	    return (0);
	}

	/*
	 * At this point we may have opened an existing output file that was
	 * already locked. Try to lock the open file exclusively. This may
	 * take some time.
	 */
	if (myflock(vstream_fileno(ep->tmp_fp), INTERNAL_LOCK,
		    MYFLOCK_OP_EXCLUSIVE) < 0)
	    msg_fatal("lock %s: %m", ep->tmp_path);

	/*
	 * At this point we have an exclusive lock, but some other process
	 * may have renamed or removed the output file while we were waiting
	 * for the lock. If that is the case, back out and try again.
	 */
	if (fstat(vstream_fileno(ep->tmp_fp), &before_lock) < 0)
	    msg_fatal("open %s: %m", ep->tmp_path);
	if (stat(ep->tmp_path, &after_lock) < 0
	    || before_lock.st_dev != after_lock.st_dev
	    || before_lock.st_ino != after_lock.st_ino
#ifdef HAS_ST_GEN
	    || before_lock.st_gen != after_lock.st_gen
#endif
	/* No need to compare st_rdev or st_nlink here. */
	    ) {
	    continue;
	}

	/*
	 * At this point we have exclusive control over the output file
	 * content and its temporary pathname (within the rules of the
	 * cooperative protocol). But wait, there is more.
	 * 
	 * There are many opportunities for trouble when opening a pre-existing
	 * output file. Here are just a few.
	 * 
	 * - Victor observes that a system crash in the middle of the
	 * final-phase rename() operation may result in the output file
	 * having both the temporary pathname and the final pathname. In that
	 * case we must not write to the output file.
	 * 
	 * - Wietse observes that crashes may also leave the output file in
	 * other inconsistent states. To avoid permission-related trouble, we
	 * simply refuse to work with an output file that has the wrong
	 * temporary permissions. This won't stop the shared-lock DOS if we
	 * crash after changing the file permissions, though.
	 * 
	 * To work around these crash-related problems, remove the temporary
	 * pathname, back out, and try again.
	 */
	if (!S_ISREG(after_lock.st_mode)
#ifndef EDIT_FILE_REUSE_AFTER_CRASH
	    || after_lock.st_size > 0
#endif
	    || after_lock.st_nlink > 1
	    || (after_lock.st_mode & FILE_PERM_MASK) != EDIT_FILE_MODE) {
	    if (unlink(ep->tmp_path) < 0 && errno != ENOENT)
		msg_fatal("unlink %s: %m", ep->tmp_path);
	    continue;
	}

	/*
	 * Settle the final details.
	 */
#ifdef EDIT_FILE_REUSE_AFTER_CRASH
	if (ftruncate(vstream_fileno(ep->tmp_fp), 0) < 0)
	    msg_fatal("truncate %s: %m", ep->tmp_path);
#endif
	return (ep);
    }
}

/* edit_file_cleanup - clean up without completing the protocol */

void    edit_file_cleanup(EDIT_FILE *ep)
{

    /*
     * Don't touch the file after we lose the exclusive lock!
     */
    if (unlink(ep->tmp_path) < 0 && errno != ENOENT)
	msg_fatal("unlink %s: %m", ep->tmp_path);
    (void) vstream_fclose(ep->tmp_fp);
    EDIT_FILE_FREE(ep);
}

/* edit_file_close - rename the file into place and close the file */

int     edit_file_close(EDIT_FILE *ep)
{
    VSTREAM *fp = ep->tmp_fp;
    int     fd = vstream_fileno(fp);
    int     saved_errno;

    /*
     * The rename/unlock portion of the protocol is relatively simple. The
     * only things that really matter here are that we change permissions as
     * late as possible, and that we rename the file to its final pathname
     * before we lose the exclusive lock.
     * 
     * Applications that are concerned about maximal safety should protect the
     * edit_file_close() call with sigdelay() and sigresume() calls. It is
     * not safe for us to call these functions directly, because the calls do
     * not nest. It is also not nice to force every caller to run with
     * interrupts turned off.
     */
    if (vstream_fflush(fp) < 0
	|| fchmod(fd, ep->final_mode) < 0
#ifdef HAS_FSYNC
	|| fsync(fd) < 0
#endif
	|| rename(ep->tmp_path, ep->final_path) < 0) {
	saved_errno = errno;
	edit_file_cleanup(ep);
	errno = saved_errno;
	return (VSTREAM_EOF);
    } else {
	(void) vstream_fclose(ep->tmp_fp);
	EDIT_FILE_FREE(ep);
	return (0);
    }
}