/* * Copyright (C) 2003 Serge van den Boom * * This program is free software; you can redistribute it and/or modify * it under the terms of version 2 of the GNU General Public License as * published by the Free Software Foundation. * Nota bene: later versions of the GNU General Public License do not apply * to this program. * * 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include #include #include #ifdef _MSC_VER # include #endif /* _MSC_VER */ #include "iointrn.h" #include "ioaux.h" #include "utils.h" static int uio_copyError(uio_Handle *srcHandle, uio_Handle *dstHandle, uio_DirHandle *unlinkHandle, const char *unlinkPath, uio_uint8 *buf); struct uio_StdioAccessHandle { uio_DirHandle *tempRoot; char *tempDirName; uio_DirHandle *tempDir; char *fileName; char *stdioPath; }; static inline uio_StdioAccessHandle *uio_StdioAccessHandle_new( uio_DirHandle *tempRoot, char *tempDirName, uio_DirHandle *tempDir, char *fileName, char *stdioPath); static inline void uio_StdioAccessHandle_delete( uio_StdioAccessHandle *handle); static inline uio_StdioAccessHandle *uio_StdioAccessHandle_alloc(void); static inline void uio_StdioAccessHandle_free(uio_StdioAccessHandle *handle); /* * Copy a file with path srcName to a file with name newName. * If the destination already exists, the operation fails. * Links are followed. * Special files (fifos, char devices, block devices, etc) will be * read as long as there is data available and the destination will be * a regular file with that data. * The new file will have the same permissions as the old. * If an error occurs during copying, an attempt will be made to * remove the copy. */ int uio_copyFile(uio_DirHandle *srcDir, const char *srcName, uio_DirHandle *dstDir, const char *newName) { uio_Handle *src, *dst; struct stat sb; #define BUFSIZE 65536 uio_uint8 *buf, *bufPtr; ssize_t numInBuf, numWritten; src = uio_open(srcDir, srcName, O_RDONLY #ifdef WIN32 | O_BINARY #endif , 0); if (src == NULL) return -1; if (uio_fstat(src, &sb) == -1) return uio_copyError(src, NULL, NULL, NULL, NULL); dst = uio_open(dstDir, newName, O_WRONLY | O_CREAT | O_EXCL #ifdef WIN32 | O_BINARY #endif , sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)); if (dst == NULL) return uio_copyError(src, NULL, NULL, NULL, NULL); buf = uio_malloc(BUFSIZE); // This was originally a statically allocated buffer, // but as this function might be run from a thread with // a small Stack, this is better. while (1) { numInBuf = uio_read(src, buf, BUFSIZE); if (numInBuf == -1) { if (errno == EINTR) continue; return uio_copyError(src, dst, dstDir, newName, buf); } if (numInBuf == 0) break; bufPtr = buf; do { numWritten = uio_write(dst, bufPtr, numInBuf); if (numWritten == -1) { if (errno == EINTR) continue; return uio_copyError(src, dst, dstDir, newName, buf); } numInBuf -= numWritten; bufPtr += numWritten; } while (numInBuf > 0); } uio_free(buf); uio_close(src); uio_close(dst); errno = 0; return 0; } /* * Closes srcHandle if it's not -1. * Closes dstHandle if it's not -1. * Removes unlinkpath from the unlinkHandle dir if it's not NULL. * Frees 'buf' if not NULL. * Always returns -1. * errno is what was before the call. */ static int uio_copyError(uio_Handle *srcHandle, uio_Handle *dstHandle, uio_DirHandle *unlinkHandle, const char *unlinkPath, uio_uint8 *buf) { int savedErrno; savedErrno = errno; #ifdef DEBUG fprintf(stderr, "Error while copying: %s\n", strerror(errno)); #endif if (srcHandle != NULL) uio_close(srcHandle); if (dstHandle != NULL) uio_close(dstHandle); if (unlinkPath != NULL) uio_unlink(unlinkHandle, unlinkPath); if (buf != NULL) uio_free(buf); errno = savedErrno; return -1; } #define NUM_TEMP_RETRIES 16 // Retry this many times to create a temporary dir, before giving // up. If undefined, keep trying indefinately. uio_StdioAccessHandle * uio_getStdioAccess(uio_DirHandle *dir, const char *path, int flags, uio_DirHandle *tempDir) { int res; uio_MountHandle *mountHandle; const char *name; char *newPath; char *tempDirName; uio_DirHandle *newDir; uio_FileSystemID fsID; res = uio_getFileLocation(dir, path, flags, &mountHandle, &newPath); if (res == -1) { // errno is set return NULL; } fsID = uio_getMountFileSystemType(mountHandle); if (fsID == uio_FSTYPE_STDIO) { // Current location is usable. return uio_StdioAccessHandle_new(NULL, NULL, NULL, NULL, newPath); } uio_free(newPath); { uio_uint32 dirNum; int i; // Current location is not usable. Create a directory with a // generated name, as a temporary location to store a copy of // the file. dirNum = (uio_uint32) time(NULL); tempDirName = uio_malloc(sizeof "01234567"); for (i = 0; ; i++) { #ifdef NUM_TEMP_RETRIES if (i >= NUM_TEMP_RETRIES) { // Using ENOSPC to report that we couldn't create a // temporary dir, getting EEXIST. uio_free(tempDirName); errno = ENOSPC; return NULL; } #endif sprintf(tempDirName, "%08lx", (unsigned long) dirNum + i); res = uio_mkdir(tempDir, tempDirName, 0700); if (res == -1) { int savedErrno; if (errno == EEXIST) continue; savedErrno = errno; #ifdef DEBUG fprintf(stderr, "Error: Could not create temporary dir: %s\n", strerror(errno)); #endif uio_free(tempDirName); errno = savedErrno; return NULL; } break; } newDir = uio_openDirRelative(tempDir, tempDirName, 0); if (newDir == NULL) { #ifdef DEBUG fprintf(stderr, "Error: Could not open temporary dir: %s\n", strerror(errno)); #endif res = uio_rmdir(tempDir, tempDirName); #ifdef DEBUG if (res == -1) fprintf(stderr, "Warning: Could not remove temporary dir: " "%s.\n", strerror(errno)); #endif uio_free(tempDirName); errno = EIO; return NULL; } // Get the last component of path. This should be the file to // access. name = strrchr(path, '/'); if (name == NULL) name = path; // Copy the file res = uio_copyFile(dir, path, newDir, name); if (res == -1) { int savedErrno = errno; #ifdef DEBUG fprintf(stderr, "Error: Could not copy file to temporary dir: " "%s\n", strerror(errno)); #endif uio_closeDir(newDir); uio_free(tempDirName); errno = savedErrno; return NULL; } } res = uio_getFileLocation(newDir, name, flags, &mountHandle, &newPath); if (res == -1) { int savedErrno = errno; fprintf(stderr, "Error: uio_getStdioAccess: Could not get location " "of temporary dir: %s.\n", strerror(errno)); uio_closeDir(newDir); uio_free(tempDirName); errno = savedErrno; return NULL; } fsID = uio_getMountFileSystemType(mountHandle); if (fsID != uio_FSTYPE_STDIO) { // Temp dir isn't on a stdio fs either. fprintf(stderr, "Error: uio_getStdioAccess: Temporary file location " "isn't on a stdio filesystem.\n"); uio_closeDir(newDir); uio_free(tempDirName); uio_free(newPath); // errno = EXDEV; errno = EINVAL; return NULL; } uio_DirHandle_ref(tempDir); return uio_StdioAccessHandle_new(tempDir, tempDirName, newDir, uio_strdup(name), newPath); } void uio_releaseStdioAccess(uio_StdioAccessHandle *handle) { if (handle->tempDir != NULL) { if (uio_unlink(handle->tempDir, handle->fileName) == -1) { #ifdef DEBUG fprintf(stderr, "Error: Could not remove temporary file: " "%s\n", strerror(errno)); #endif } // Need to free this handle in advance. There should be no handles // to a dir left when removing it. uio_DirHandle_unref(handle->tempDir); handle->tempDir = NULL; if (uio_rmdir(handle->tempRoot, handle->tempDirName) == -1) { #ifdef DEBUG fprintf(stderr, "Error: Could not remove temporary directory: " "%s\n", strerror(errno)); #endif } } uio_StdioAccessHandle_delete(handle); } const char * uio_StdioAccessHandle_getPath(uio_StdioAccessHandle *handle) { return (const char *) handle->stdioPath; } // references to tempRoot and tempDir are not increased. // no copies of arguments are made. // By calling this function control of the values is transfered to // the handle. static inline uio_StdioAccessHandle * uio_StdioAccessHandle_new( uio_DirHandle *tempRoot, char *tempDirName, uio_DirHandle *tempDir, char *fileName, char *stdioPath) { uio_StdioAccessHandle *result; result = uio_StdioAccessHandle_alloc(); result->tempRoot = tempRoot; result->tempDirName = tempDirName; result->tempDir = tempDir; result->fileName = fileName; result->stdioPath = stdioPath; return result; } static inline void uio_StdioAccessHandle_delete(uio_StdioAccessHandle *handle) { if (handle->tempDir != NULL) uio_DirHandle_unref(handle->tempDir); if (handle->fileName != NULL) uio_free(handle->fileName); if (handle->tempRoot != NULL) uio_DirHandle_unref(handle->tempRoot); if (handle->tempDirName != NULL) uio_free(handle->tempDirName); uio_free(handle->stdioPath); uio_StdioAccessHandle_free(handle); } static inline uio_StdioAccessHandle * uio_StdioAccessHandle_alloc(void) { return uio_malloc(sizeof (uio_StdioAccessHandle)); } static inline void uio_StdioAccessHandle_free(uio_StdioAccessHandle *handle) { uio_free(handle); } #ifdef _MSC_VER # include #if 0 /* Unneeded for now */ // MSVC does not have snprintf(). It does have a _snprintf(), but it does // not \0-terminate a truncated string as the C standard prescribes. static inline int snprintf(char *str, size_t size, const char *format, ...) { int result; va_list args; va_start (args, format); result = _vsnprintf (str, size, format, args); if (str != NULL && size != 0) str[size - 1] = '\0'; va_end (args); return result; } #endif // MSVC does not have vsnprintf(). It does have a _vsnprintf(), but it does // not \0-terminate a truncated string as the C standard prescribes. static inline int vsnprintf(char *str, size_t size, const char *format, va_list args) { int result = _vsnprintf (str, size, format, args); if (str != NULL && size != 0) str[size - 1] = '\0'; return result; } #endif /* _MSC_VER */ // The result should be freed using uio_free(). // NB. POSIX allows errno to be set for vsprintf(), but does not require it: // "The value of errno may be set to nonzero by a library function call // whether or not there is an error, provided the use of errno is not // documented in the description of the function in this International // Standard." The latter is the case for vsprintf(). char * uio_vasprintf(const char *format, va_list args) { // TODO: If there is a system vasprintf, use that. // XXX: That would mean that the allocation would always go through // malloc() or so, instead of uio_malloc(), which may not be // desirable. char *buf; size_t bufSize = 128; // Start with enough for one screen line, and a power of 2, // which might give faster result with allocations. buf = uio_malloc(bufSize); if (buf == NULL) { // errno is set. return NULL; } for (;;) { int printResult = vsnprintf(buf, bufSize, format, args); if (printResult < 0) { // This means the buffer was not large enough, but vsnprintf() // does not give us any clue on how large it should be. // Note that this does not happen with a C'99 compliant // vsnprintf(), but it will happen on MS Windows, and on // glibc before version 2.1. bufSize *= 2; } else if ((unsigned int) printResult >= bufSize) { // The buffer was too small, but printResult contains the size // that the buffer needs to be (excluding the '\0' character). bufSize = printResult + 1; } else { // Success. if ((unsigned int) printResult + 1 != bufSize) { // Shorten the resulting buffer to the size that was // actually needed. char *newBuf = uio_realloc(buf, printResult + 1); if (newBuf == NULL) { // We could have returned the (overly large) original // buffer, but the unused memory might not be // acceptable, and the program would be likely to run // into problems sooner or later anyhow. int savedErrno = errno; uio_free(buf); errno = savedErrno; return NULL; } return newBuf; } return buf; } { char *newBuf = uio_realloc(buf, bufSize); if (newBuf == NULL) { int savedErrno = errno; uio_free(buf); errno = savedErrno; return NULL; } buf = newBuf; } } } // As uio_vasprintf(), but with an argument list. char * uio_asprintf(const char *format, ...) { // TODO: If there is a system asprintf, use that. // XXX: That would mean that the allocation would always go through // malloc() or so, instead of uio_malloc(), which may not be // desirable. va_list args; char *result; int savedErrno; va_start(args, format); result = uio_vasprintf(format, args); savedErrno = errno; va_end(args); errno = savedErrno; return result; }