/*********************************************************** * Smithsonian Astrophysical Observatory * Submillimeter Receiver Laboratory * am * * fileops.c S. Paine rev. 2017 January 2 * * Miscellaneous file operations. ************************************************************/ #include #include #include #include #include #include "am_sysdep.h" #include "fileops.h" /* * Format string for am temporary file names */ static const char TMPNAME_FMT[] = "%sam_tmp_%04x"; /* * Initial retry delays for failed file operations. Retry * delays double on each attempt, up to the maximum number * of retries. */ static const double REMOVE_WAIT_INIT = 0.125; /* seconds */ static const double RENAME_WAIT_INIT = 0.125; /* seconds */ /* * Maximum number of retries for failed file operations. */ enum { TMPFILE_NUM_TRIES = 8, REMOVE_NUM_RETRIES = 8, RENAME_NUM_RETRIES = 8 }; /* * recognized directory separators: * '/' - Unix, POSIX, OS X. Also works in standard C library under Windows. * '\\' - Windows native * ':' - Older MacOS * ']' - VMS */ static const char DIR_SEPARATORS[] = "/\\:]"; static const char DEFAULT_DIR_SEPARATOR[] = "/"; /*********************************************************** * FILE *am_tmpfile(const char *dirpath, char *fpath) * * Purpose: * Creates a temporary file, opened in binary update mode, and * returns a file pointer. The file is created in the directory * named by the string dirpath. If dirpath is NULL, or points * to an empty string, the file is created in the current * directory. The full path to the temporary file is written * to the buffer fpath, which should be at least AM_MAX_PATH * characters long. * * If the file cannot be created, NULL is returned, and an empty * string is written to fpath. * * The last character of dirpath must be a directory separator * appropriate for the current environment. * * Arguments: * const char *path - path to directory * char *fpath - pointer to buffer to receive path to file. * * Return: * file pointer, or NULL if the file could not be created. ************************************************************/ FILE *am_tmpfile(const char *dirpath, char *fpath) { int i; FILE *stream; for (i = 0; i < TMPFILE_NUM_TRIES; ++i) { /* * generate a random file name */ sprintf(fpath, TMPNAME_FMT, ((dirpath == NULL) ? "" : dirpath), rand()); if ((stream = fopen(fpath, "rb")) != NULL) { /* * File already exists. Close and try another name. */ fclose(stream); } else { /* * Didn't exist already, so try opening in binary update mode. * If, by chance, this process lost a race for this file name, * the outcome depends on the implementation of fopen(). If * "wb+" opens with exclusive access, then this fopen() fails * and all is well. However, another possibility is that this * fopen() causes the other process to lose its file name to * file pointer association. In am, a possible consequence of * this would be a file getting copied to the wrong hash bucket * of the disk cache. This situation resolves itself-- the * misplaced file will always be a cache miss based on its header * data, and it will eventually be evicted from the cache. */ if ((stream = fopen(fpath, "wb+")) != NULL) return stream; } } /* * Too many tries. Give up and return error status. */ fpath[0] = '\0'; return NULL; } /* am_tmpfile() */ /*********************************************************** * int check_for_dir_separator(char *dirpath) * * Purpose: * Checks that dirpath ends in a directory separator character. * If not, a default one is appended, unless the resulting * string would be longer than AM_MAX_DIRPATH. * * Arguments: * char *dirpath - path to directory * * Return: * 0 on success * 1 otherwise ************************************************************/ int check_for_dir_separator(char *dirpath) { size_t length; if (dirpath == NULL) return 1; /* NULL path doesn't exist */ if ((length = strlen(dirpath)) == 0) return 0; /* empty string means use the current working dir */ /* * Look for a valid directory separator character at the * end of dirpath. If one isn't found, append a default. */ if (strchr(DIR_SEPARATORS, dirpath[length - 1]) == NULL) { if (length + sizeof(DEFAULT_DIR_SEPARATOR) < AM_MAX_DIRPATH) { strcat(dirpath, DEFAULT_DIR_SEPARATOR); } else { return 1; } } return 0; } /* check_for_dir_separator() */ /*********************************************************** * int remove_with_retry(const char *filename) * * Purpose: * This function is a wrapper around the standard C library * function remove(). It calls remove(), and, if the call * fails, keeps trying with exponential back-off for up * to REMOVE_NUM_RETRIES attempts. * * Arguments: * const char *filename - file name or path string * * Return: * 0 if the remove() call eventually succeeds * 1 if the timeout expires ************************************************************/ int remove_with_retry(const char *filename) { double t_wait; int i; if (remove(filename) == 0) return 0; t_wait = REMOVE_WAIT_INIT; for (i = 0; i < REMOVE_NUM_RETRIES; ++i) { am_sleep((unsigned int)t_wait + 1); errno = 0; if (remove(filename) == 0) return 0; /* * See if the file was already removed. Note that * ENOENT is defined by POSIX, not by ANSI C. */ if (errno == ENOENT) return 0; t_wait *= 2.; } return 1; } /* remove_with_retry() */ /*********************************************************** * int rename_with_retry(const char *oldname, const char *newname) * * Purpose: * This function is a wrapper around the standard C library * function rename(). It calls rename(), and, if the call * fails, keeps trying with exponential back-off for up * to RENAME_NUM_RETRIES attempts. * * Arguments: * const char *oldname - old file name or path string * const char *newname - new file name or path string * * Return: * 0 if the rename() call eventually succeeds * 1 if the timeout expires ************************************************************/ int rename_with_retry(const char *oldname, const char *newname) { double t_wait; int i; if (rename(oldname, newname) == 0) return 0; t_wait = RENAME_WAIT_INIT; for (i = 0; i < RENAME_NUM_RETRIES; ++i) { am_sleep((unsigned int)t_wait + 1); if (rename(oldname, newname) == 0) return 0; t_wait *= 2.; } return 1; } /* rename_with_retry() */