summaryrefslogtreecommitdiff
path: root/lib/supersede.h
blob: 111d15b8649d7f06634cb2b0d41db8b3c6e057b7 (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
/* Open a file, without destroying an old file with the same name.

   Copyright (C) 2020 Free Software Foundation, Inc.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */

/* Written by Bruno Haible, 2020.  */

#ifndef _GL_SUPERSEDE_H
#define _GL_SUPERSEDE_H

#include <stdbool.h>
#include <stdio.h>
#include <sys/types.h>

#ifdef __cplusplus
extern "C" {
#endif

/* When writing a file, for some usages it is important that at any moment,
   a process that opens the file will see consistent data in the file.  This
   can be important in two situations:
     * If supersede_if_exists == true, then when the file already existed,
       it is important that a process that opens the file while the new file's
       contents is being written sees consistent data - namely the old file's
       data.
     * If supersede_if_does_not_exist == true, then when the file did not exist,
       it is important that a process that opens the file while the new file's
       contents is being written sees no file (as opposed to a file with
       truncated contents).

   In both situations, the effect is implemented by creating a temporary file,
   writing into that temporary file, and renaming the temporary file when the
   temporary file's contents is complete.

   Note that opening a file with superseding may fail when it would succeed
   without superseding (for example, for a writable file in an unwritable
   directory).  And also the other way around: Opening a file with superseding
   may succeed although it would fail without superseding (for example, for
   an unwritable file in a writable directory).  */

/* This type holds everything that needs to needs to be remembered in order to
   execute the final rename action.  */
struct supersede_final_action
{
  char *final_rename_temp;
  char *final_rename_dest;
};

/* =================== open() and close() with supersede =================== */

/* The typical code idiom is like this:

     struct supersede_final_action action;
     int fd = open_supersede (filename, O_RDWR, mode,
                              supersede_if_exists, supersede_if_does_not_exist,
                              &action);
     if (fd >= 0)
       {
         ... write the file's contents ...
         if (successful)
           {
             if (close_supersede (fd, &action) < 0)
               error (...);
           }
         else
           {
             // Abort the operation.
             close (fd);
             close_supersede (-1, &action);
           }
       }
  */

/* Opens a file (typically for writing) in superseding mode, depending on
   supersede_if_exists and supersede_if_does_not_exist.
   FLAGS should not contain O_CREAT nor O_EXCL.
   MODE is used when the file does not yet exist.  The umask of the process
   is considered, like in open(), i.e. the effective mode is
   (MODE & ~ getumask ()).
   Upon success, it fills in ACTION and returns a file descriptor.
   Upon failure, it returns -1 and sets errno.  */
extern int open_supersede (const char *filename, int flags, mode_t mode,
                           bool supersede_if_exists,
                           bool supersede_if_does_not_exist,
                           struct supersede_final_action *action);

/* Closes a file and executes the final rename action.
   FD must have been returned by open_supersede(), or -1 if you want to abort
   the operation.  */
extern int close_supersede (int fd,
                            const struct supersede_final_action *action);

/* ================== fopen() and fclose() with supersede ================== */

/* The typical code idiom is like this:

     struct supersede_final_action action;
     FILE *stream =
       fopen_supersede (filename, O_RDWR, mode,
                        supersede_if_exists, supersede_if_does_not_exist,
                        &action);
     if (stream != NULL)
       {
         ... write the file's contents ...
         if (successful)
           {
             if (fclose_supersede (stream, &action) < 0)
               error (...);
           }
         else
           {
             // Abort the operation.
             fclose (stream);
             fclose_supersede (NULL, &action);
           }
       }
  */

/* Opens a file (typically for writing) in superseding mode, depending on
   supersede_if_exists and supersede_if_does_not_exist.
   Upon success, it fills in ACTION and returns a file stream.
   Upon failure, it returns NULL and sets errno.  */
extern FILE *fopen_supersede (const char *filename, const char *mode,
                              bool supersede_if_exists,
                              bool supersede_if_does_not_exist,
                              struct supersede_final_action *action);

/* Closes a file stream and executes the final rename action.
   STREAM must have been returned by fopen_supersede(), or NULL if you want to
   abort the operation.  */
extern int fclose_supersede (FILE *stream,
                             const struct supersede_final_action *action);

/* Closes a file stream, like with fwriteerror, and executes the final rename
   action.
   STREAM must have been returned by fopen_supersede(), or NULL if you want to
   abort the operation.  */
extern int fwriteerror_supersede (FILE *stream,
                                  const struct supersede_final_action *action);

#ifdef __cplusplus
}
#endif

#endif /* _GL_SUPERSEDE_H */

Return to:

Send suggestions and report system problems to the System administrator.