/* Open a file, without destroying an old file with the same name.

   Copyright (C) 2020-2021 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 */
