/*
 * File functions for the CUPS scheduler.
 *
 * Copyright © 2020-2024 by OpenPrinting.
 * Copyright © 2007-2014 by Apple Inc.
 * Copyright © 1997-2007 by Easy Software Products, all rights reserved.
 *
 * Licensed under Apache License v2.0.  See the file "LICENSE" for more
 * information.
 */

/*
 * Include necessary headers...
 */

#include "cupsd.h"
#include <cups/dir.h>
#include <fnmatch.h>
#ifdef HAVE_REMOVEFILE
#  include <removefile.h>
#else
static int	overwrite_data(int fd, const char *buffer, int bufsize,
		               int filesize);
#endif /* HAVE_REMOVEFILE */


/*
 * 'cupsdCleanFiles()' - Clean out old files.
 */

void
cupsdCleanFiles(const char *path,	/* I - Directory to clean */
                const char *pattern)	/* I - Filename pattern or NULL */
{
  cups_dir_t	*dir;			/* Directory */
  cups_dentry_t	*dent;			/* Directory entry */
  char		filename[1024];		/* Filename */
  int		status;			/* Status from unlink/rmdir */


  cupsdLogMessage(CUPSD_LOG_DEBUG,
                  "cupsdCleanFiles(path=\"%s\", pattern=\"%s\")", path,
		  pattern ? pattern : "(null)");

  if ((dir = cupsDirOpen(path)) == NULL)
  {
    cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to open directory \"%s\" - %s",
		    path, strerror(errno));
    return;
  }

  cupsdLogMessage(CUPSD_LOG_INFO, "Cleaning out old files in \"%s\".", path);

  while ((dent = cupsDirRead(dir)) != NULL)
  {
    if (pattern && fnmatch(pattern, dent->filename, 0))
      continue;

    snprintf(filename, sizeof(filename), "%s/%s", path, dent->filename);

    if (S_ISDIR(dent->fileinfo.st_mode))
    {
      cupsdCleanFiles(filename, pattern);

      status = rmdir(filename);
    }
    else
      status = cupsdUnlinkOrRemoveFile(filename);

    if (status)
      cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to remove \"%s\" - %s", filename,
		      strerror(errno));
  }

  cupsDirClose(dir);
}


/*
 * 'cupsdCloseCreatedConfFile()' - Close a created configuration file and move
 *                                 into place.
 */

int					/* O - 0 on success, -1 on error */
cupsdCloseCreatedConfFile(
    cups_file_t *fp,			/* I - File to close */
    const char  *filename)		/* I - Filename */
{
  char	newfile[1024],			/* filename.N */
	oldfile[1024];			/* filename.O */


 /*
  * Synchronize changes to disk if SyncOnClose is enabled.
  */

  if (SyncOnClose)
  {
    if (cupsFileFlush(fp))
    {
      cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to write changes to \"%s\": %s",
		      filename, strerror(errno));
      cupsFileClose(fp);
      return (-1);
    }

    if (fsync(cupsFileNumber(fp)))
    {
      cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to sync changes to \"%s\": %s",
		      filename, strerror(errno));
      cupsFileClose(fp);
      return (-1);
    }
  }

 /*
  * First close the file...
  */

  if (cupsFileClose(fp))
    return (-1);

 /*
  * Then remove "filename.O", rename "filename" to "filename.O", and rename
  * "filename.N" to "filename".
  */

  snprintf(newfile, sizeof(newfile), "%s.N", filename);
  snprintf(oldfile, sizeof(oldfile), "%s.O", filename);

  if ((cupsdUnlinkOrRemoveFile(oldfile) && errno != ENOENT) ||
      (rename(filename, oldfile) && errno != ENOENT) ||
      rename(newfile, filename))
  {
    cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to finalize \"%s\": %s",
                    filename, strerror(errno));
    return (-1);
  }

  return (0);
}


/*
 * 'cupsdClosePipe()' - Close a pipe as necessary.
 */

void
cupsdClosePipe(int *fds)		/* I - Pipe file descriptors (2) */
{
 /*
  * Close file descriptors as needed...
  */

  if (fds[0] >= 0)
  {
    close(fds[0]);
    fds[0] = -1;
  }

  if (fds[1] >= 0)
  {
    close(fds[1]);
    fds[1] = -1;
  }
}


/*
 * 'cupsdCreateConfFile()' - Create a configuration file safely.
 */

cups_file_t *				/* O - File pointer */
cupsdCreateConfFile(
    const char *filename,		/* I - Filename */
    mode_t     mode)			/* I - Permissions */
{
  cups_file_t	*fp;			/* File pointer */
  char		newfile[1024];		/* filename.N */


  snprintf(newfile, sizeof(newfile), "%s.N", filename);
  if ((fp = cupsFileOpen(newfile, "w")) == NULL)
  {
    cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to create \"%s\": %s", newfile,
		    strerror(errno));
  }
  else
  {
    if (!getuid() && fchown(cupsFileNumber(fp), getuid(), Group))
      cupsdLogMessage(CUPSD_LOG_WARN, "Unable to change group for \"%s\": %s",
		      newfile, strerror(errno));

    if (fchmod(cupsFileNumber(fp), mode))
      cupsdLogMessage(CUPSD_LOG_WARN,
                      "Unable to change permissions for \"%s\": %s",
		      newfile, strerror(errno));
  }

  return (fp);
}


/*
 * 'cupsdOpenConfFile()' - Open a configuration file.
 *
 * This function looks for "filename.O" if "filename" does not exist and does
 * a rename as needed.
 */

cups_file_t *				/* O - File pointer */
cupsdOpenConfFile(const char *filename)	/* I - Filename */
{
  cups_file_t	*fp;			/* File pointer */


  if ((fp = cupsFileOpen(filename, "r")) == NULL)
  {
    if (errno == ENOENT)
    {
     /*
      * Try opening the backup file...
      */

      char	oldfile[1024];		/* filename.O */

      snprintf(oldfile, sizeof(oldfile), "%s.O", filename);
      fp = cupsFileOpen(oldfile, "r");
    }
    else
      cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to open \"%s\": %s", filename,
		      strerror(errno));
  }

  return (fp);
}


/*
 * 'cupsdOpenPipe()' - Create a pipe which is closed on exec.
 */

int					/* O - 0 on success, -1 on error */
cupsdOpenPipe(int *fds)			/* O - Pipe file descriptors (2) */
{
 /*
  * Create the pipe...
  */

  if (pipe(fds))
  {
    fds[0] = -1;
    fds[1] = -1;

    return (-1);
  }

 /*
  * Set the "close on exec" flag on each end of the pipe...
  */

  if (fcntl(fds[0], F_SETFD, fcntl(fds[0], F_GETFD) | FD_CLOEXEC))
  {
    close(fds[0]);
    close(fds[1]);

    fds[0] = -1;
    fds[1] = -1;

    return (-1);
  }

  if (fcntl(fds[1], F_SETFD, fcntl(fds[1], F_GETFD) | FD_CLOEXEC))
  {
    close(fds[0]);
    close(fds[1]);

    fds[0] = -1;
    fds[1] = -1;

    return (-1);
  }

 /*
  * Return 0 indicating success...
  */

  return (0);
}


/*
 * 'cupsdRemoveFile()' - Remove a file securely.
 */

int					/* O - 0 on success, -1 on error */
cupsdRemoveFile(const char *filename)	/* I - File to remove */
{
#ifdef HAVE_REMOVEFILE
 /*
  * See if the file exists...
  */

  if (access(filename, 0))
    return (0);

  cupsdLogMessage(CUPSD_LOG_DEBUG, "Securely removing \"%s\".", filename);

 /*
  * Remove the file...
  */

  return (removefile(filename, NULL, REMOVEFILE_SECURE_1_PASS));

#else
  int			fd;		/* File descriptor */
  struct stat		info;		/* File information */
  char			buffer[512];	/* Data buffer */
  size_t			i;		/* Looping var */


 /*
  * See if the file exists...
  */

  if (access(filename, 0))
    return (0);

  cupsdLogMessage(CUPSD_LOG_DEBUG, "Securely removing \"%s\".", filename);

 /*
  * First open the file for writing in exclusive mode.
  */

  if ((fd = open(filename, O_WRONLY | O_EXCL)) < 0)
    return (-1);

 /*
  * Delete the file now - it will still be around as long as the file is
  * open...
  */

  if (unlink(filename))
  {
    close(fd);
    return (-1);
  }

 /*
  * Then get the file size...
  */

  if (fstat(fd, &info))
  {
    close(fd);
    return (-1);
  }

 /*
  * Overwrite the file with random data.
  */

  CUPS_SRAND(time(NULL));

  for (i = 0; i < sizeof(buffer); i ++)
    buffer[i] = CUPS_RAND();
  if (overwrite_data(fd, buffer, sizeof(buffer), (int)info.st_size))
  {
    close(fd);
    return (-1);
  }

 /*
  * Close the file, which will lead to the actual deletion, and return...
  */

  return (close(fd));
#endif /* HAVE_REMOVEFILE */
}


/*
 * 'cupsdUnlinkOrRemoveFile()' - Unlink or securely remove a file depending
 *                               on the configuration.
 */

int					/* O - 0 on success, -1 on error */
cupsdUnlinkOrRemoveFile(
    const char *filename)		/* I - Filename */
{
  if (Classification)
    return (cupsdRemoveFile(filename));
  else
    return (unlink(filename));
}


#ifndef HAVE_REMOVEFILE
/*
 * 'overwrite_data()' - Overwrite the data in a file.
 */

static int				/* O - 0 on success, -1 on error */
overwrite_data(int        fd,		/* I - File descriptor */
               const char *buffer,	/* I - Buffer to write */
	       int        bufsize,	/* I - Size of buffer */
               int        filesize)	/* I - Size of file */
{
  int	bytes;				/* Bytes to write/written */


 /*
  * Start at the beginning of the file...
  */

  if (lseek(fd, 0, SEEK_SET) < 0)
    return (-1);

 /*
  * Fill the file with the provided data...
  */

  while (filesize > 0)
  {
    if (filesize > bufsize)
      bytes = bufsize;
    else
      bytes = filesize;

    if ((bytes = write(fd, buffer, (size_t)bytes)) < 0)
      return (-1);

    filesize -= bytes;
  }

 /*
  * Force the changes to disk...
  */

  return (fsync(fd));
}
#endif /* HAVE_REMOVEFILE */
