/*********************************************************** * Smithsonian Astrophysical Observatory * Submillimeter Receiver Laboratory * am * * fileops.c S. Paine rev. 2024 July 21 * * 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 Windows standard C library. * '\\' - 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. */ snprintf(fpath, AM_MAX_PATH, 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) { strncat(dirpath, DEFAULT_DIR_SEPARATOR, AM_MAX_DIRPATH - length); } 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() */