text: /*-------------------------------------------------------------------------
   *
   * file_ops.c
   *	  Helper functions for operating on files.
   *
   * Most of the functions in this file are helper functions for writing to
   * the target data directory. The functions check the --dry-run flag, and
   * do nothing if it's enabled. You should avoid accessing the target files
   * directly but if you do, make sure you honor the --dry-run mode!
   *
   * Portions Copyright (c) 2013-2016, PostgreSQL Global Development Group
   *
   *-------------------------------------------------------------------------
   */
  #include "postgres_fe.h"
  
  #include <sys/types.h>
  #include <sys/stat.h>
  #include <fcntl.h>
  #include <unistd.h>
  
  #include "file_ops.h"
  #include "filemap.h"
  #include "logging.h"
  #include "pg_rewind.h"
  
  /*
   * Currently open target file.
   */
  static int	dstfd = -1;
  static char dstpath[MAXPGPATH] = "";
  
  static void create_target_dir(const char *path);
  static void remove_target_dir(const char *path);
  static void create_target_symlink(const char *path, const char *link);
  static void remove_target_symlink(const char *path);
  
  /*
   * Open a target file for writing. If 'trunc' is true and the file already
   * exists, it will be truncated.
   */
  void
  open_target_file(const char *path, bool trunc)
  {
  	int			mode;
  
  	if (dry_run)
  		return;
  
  	if (dstfd != -1 && !trunc &&
  		strcmp(path, &dstpath[strlen(datadir_target) + 1]) == 0)
  		return;					/* already open */
  
  	close_target_file();
  
  	snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
  
  	mode = O_WRONLY | O_CREAT | PG_BINARY;
  	if (trunc)
  		mode |= O_TRUNC;
  	dstfd = open(dstpath, mode, 0600);
  	if (dstfd < 0)
  		pg_fatal("could not open target file \"%s\": %s\n",
  				 dstpath, strerror(errno));
  }
  
  /*
   * Close target file, if it's open.
   */
  void
  close_target_file(void)
  {
  	if (dstfd == -1)
  		return;
  
  	if (close(dstfd) != 0)
  		pg_fatal("could not close target file \"%s\": %s\n",
  				 dstpath, strerror(errno));
  
  	dstfd = -1;
  }
  
  void
  write_target_range(char *buf, off_t begin, size_t size)
  {
  	int			writeleft;
  	char	   *p;
  
  	/* update progress report */
  	fetch_done += size;
  	progress_report(false);
  
  	if (dry_run)
  		return;
  
  	if (lseek(dstfd, begin, SEEK_SET) == -1)
  		pg_fatal("could not seek in target file \"%s\": %s\n",
  				 dstpath, strerror(errno));
  
  	writeleft = size;
  	p = buf;
  	while (writeleft > 0)
  	{
  		int			writelen;
  
  		errno = 0;
  		writelen = write(dstfd, p, writeleft);
  		if (writelen < 0)
  		{
  			/* if write didn't set errno, assume problem is no disk space */
  			if (errno == 0)
  				errno = ENOSPC;
  			pg_fatal("could not write file \"%s\": %s\n",
  					 dstpath, strerror(errno));
  		}
  
  		p += writelen;
  		writeleft -= writelen;
  	}
  
  	/* keep the file open, in case we need to copy more blocks in it */
  }
  
  
  void
  remove_target(file_entry_t *entry)
  {
  	Assert(entry->action == FILE_ACTION_REMOVE);
  
  	switch (entry->type)
  	{
  		case FILE_TYPE_DIRECTORY:
  			remove_target_dir(entry->path);
  			break;
  
  		case FILE_TYPE_REGULAR:
  			remove_target_file(entry->path, false);
  			break;
  
  		case FILE_TYPE_SYMLINK:
  			remove_target_symlink(entry->path);
  			break;
  	}
  }
  
  void
  create_target(file_entry_t *entry)
  {
  	Assert(entry->action == FILE_ACTION_CREATE);
  
  	switch (entry->type)
  	{
  		case FILE_TYPE_DIRECTORY:
  			create_target_dir(entry->path);
  			break;
  
  		case FILE_TYPE_SYMLINK:
  			create_target_symlink(entry->path, entry->link_target);
  			break;
  
  		case FILE_TYPE_REGULAR:
  			/* can't happen. Regular files are created with open_target_file. */
  			pg_fatal("invalid action (CREATE) for regular file\n");
  			break;
  	}
  }
  
  /*
   * Remove a file from target data directory.  If missing_ok is true, it
   * is fine for the target file to not exist.
   */
  void
  remove_target_file(const char *path, bool missing_ok)
  {
  	char		dstpath[MAXPGPATH];
  
  	if (dry_run)
  		return;
  
  	snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
  	if (unlink(dstpath) != 0)
  	{
  		if (errno == ENOENT && missing_ok)
  			return;
  
  		pg_fatal("could not remove file \"%s\": %s\n",
  				 dstpath, strerror(errno));
  	}
  }
  
  void
  truncate_target_file(const char *path, off_t newsize)
  {
  	char		dstpath[MAXPGPATH];
  	int			fd;
  
  	if (dry_run)
  		return;
  
  	snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
  
  	fd = open(dstpath, O_WRONLY, 0);
  	if (fd < 0)
  		pg_fatal("could not open file \"%s\" for truncation: %s\n",
  				 dstpath, strerror(errno));
  
  	if (ftruncate(fd, newsize) != 0)
  		pg_fatal("could not truncate file \"%s\" to %u: %s\n",
  				 dstpath, (unsigned int) newsize, strerror(errno));
  
  	close(fd);
  }
  
  static void
  create_target_dir(const char *path)
  {
  	char		dstpath[MAXPGPATH];
  
  	if (dry_run)
  		return;
  
  	snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
  	if (mkdir(dstpath, S_IRWXU) != 0)
  		pg_fatal("could not create directory \"%s\": %s\n",
  				 dstpath, strerror(errno));
  }
  
  static void
  remove_target_dir(const char *path)
  {
  	char		dstpath[MAXPGPATH];
  
  	if (dry_run)
  		return;
  
  	snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
  	if (rmdir(dstpath) != 0)
  		pg_fatal("could not remove directory \"%s\": %s\n",
  				 dstpath, strerror(errno));
  }
  
  static void
  create_target_symlink(const char *path, const char *link)
  {
  	char		dstpath[MAXPGPATH];
  
  	if (dry_run)
  		return;
  
  	snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
  	if (symlink(link, dstpath) != 0)
  		pg_fatal("could not create symbolic link at \"%s\": %s\n",
  				 dstpath, strerror(errno));
  }
  
  static void
  remove_target_symlink(const char *path)
  {
  	char		dstpath[MAXPGPATH];
  
  	if (dry_run)
  		return;
  
  	snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
  	if (unlink(dstpath) != 0)
  		pg_fatal("could not remove symbolic link \"%s\": %s\n",
  				 dstpath, strerror(errno));
  }
  
  
  /*
   * Read a file into memory. The file to be read is <datadir>/<path>.
   * The file contents are returned in a malloc'd buffer, and *filesize
   * is set to the length of the file.
   *
   * The returned buffer is always zero-terminated; the size of the returned
   * buffer is actually *filesize + 1. That's handy when reading a text file.
   * This function can be used to read binary files as well, you can just
   * ignore the zero-terminator in that case.
   *
   * This function is used to implement the fetchFile function in the "fetch"
   * interface (see fetch.c), but is also called directly.
   */
  char *
  slurpFile(const char *datadir, const char *path, size_t *filesize)
  {
  	int			fd;
  	char	   *buffer;
  	struct stat statbuf;
  	char		fullpath[MAXPGPATH];
  	int			len;
  
  	snprintf(fullpath, sizeof(fullpath), "%s/%s", datadir, path);
  
  	if ((fd = open(fullpath, O_RDONLY | PG_BINARY, 0)) == -1)
  		pg_fatal("could not open file \"%s\" for reading: %s\n",
  				 fullpath, strerror(errno));
  
  	if (fstat(fd, &statbuf) < 0)
  		pg_fatal("could not open file \"%s\" for reading: %s\n",
  				 fullpath, strerror(errno));
  
  	len = statbuf.st_size;
  
  	buffer = pg_malloc(len + 1);
  
  	if (read(fd, buffer, len) != len)
  		pg_fatal("could not read file \"%s\": %s\n",
  				 fullpath, strerror(errno));
  	close(fd);
  
  	/* Zero-terminate the buffer. */
  	buffer[len] = '\0';
  
  	if (filesize)
  		*filesize = len;
  	return buffer;
  }
  