| /*------------------------------------------------------------------------- |
| * |
| * dirmod.c |
| * directory handling functions |
| * |
| * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * This includes replacement versions of functions that work on |
| * Win32 (NT4 and newer). |
| * |
| * IDENTIFICATION |
| * $PostgreSQL: pgsql/src/port/dirmod.c,v 1.63 2010/07/06 19:19:01 momjian Exp $ |
| * |
| *------------------------------------------------------------------------- |
| */ |
| |
| #ifndef FRONTEND |
| #include "postgres.h" |
| #else |
| #include "postgres_fe.h" |
| #endif |
| |
| /* Don't modify declarations in system headers */ |
| #if defined(WIN32) || defined(__CYGWIN__) |
| #undef rename |
| #undef unlink |
| #endif |
| |
| #include <unistd.h> |
| #include <dirent.h> |
| #include <sys/stat.h> |
| |
| #if defined(WIN32) || defined(__CYGWIN__) |
| #ifndef __CYGWIN__ |
| #include <winioctl.h> |
| #else |
| #include <windows.h> |
| #include <w32api/winioctl.h> |
| #endif |
| #endif |
| |
| |
| #ifndef FRONTEND |
| |
| /* |
| * On Windows, call non-macro versions of palloc; we can't reference |
| * CurrentMemoryContext in this file because of PGDLLIMPORT conflict. |
| */ |
| #if defined(WIN32) || defined(__CYGWIN__) |
| #undef palloc |
| #undef pstrdup |
| #define palloc(sz) pgport_palloc(sz) |
| #define pstrdup(str) pgport_pstrdup(str) |
| #endif |
| #else /* FRONTEND */ |
| |
| /* |
| * In frontend, fake palloc behavior with these |
| */ |
| #undef palloc |
| #undef pstrdup |
| #define palloc(sz) fe_palloc(sz) |
| #define pstrdup(str) fe_pstrdup(str) |
| #define repalloc(pointer,sz) fe_repalloc(pointer,sz) |
| #define pfree(pointer) free(pointer) |
| |
| static void * |
| fe_palloc(Size size) |
| { |
| void *res; |
| |
| if ((res = malloc(size)) == NULL) |
| { |
| fprintf(stderr, _("out of memory\n")); |
| exit(1); |
| } |
| return res; |
| } |
| |
| static char * |
| fe_pstrdup(const char *string) |
| { |
| char *res; |
| |
| if ((res = strdup(string)) == NULL) |
| { |
| fprintf(stderr, _("out of memory\n")); |
| exit(1); |
| } |
| return res; |
| } |
| |
| static void * |
| fe_repalloc(void *pointer, Size size) |
| { |
| void *res; |
| |
| if ((res = realloc(pointer, size)) == NULL) |
| { |
| fprintf(stderr, _("out of memory\n")); |
| exit(1); |
| } |
| return res; |
| } |
| #endif /* FRONTEND */ |
| |
| |
| #if defined(WIN32) || defined(__CYGWIN__) |
| |
| /* |
| * pgrename |
| */ |
| int |
| pgrename(const char *from, const char *to) |
| { |
| int loops = 0; |
| |
| /* |
| * We need to loop because even though PostgreSQL uses flags that allow |
| * rename while the file is open, other applications might have the file |
| * open without those flags. However, we won't wait indefinitely for |
| * someone else to close the file, as the caller might be holding locks |
| * and blocking other backends. |
| */ |
| #if defined(WIN32) && !defined(__CYGWIN__) |
| while (!MoveFileEx(from, to, MOVEFILE_REPLACE_EXISTING)) |
| #else |
| while (rename(from, to) < 0) |
| #endif |
| { |
| #if defined(WIN32) && !defined(__CYGWIN__) |
| DWORD err = GetLastError(); |
| |
| _dosmaperr(err); |
| |
| /* |
| * Modern NT-based Windows versions return ERROR_SHARING_VIOLATION if |
| * another process has the file open without FILE_SHARE_DELETE. |
| * ERROR_LOCK_VIOLATION has also been seen with some anti-virus |
| * software. This used to check for just ERROR_ACCESS_DENIED, so |
| * presumably you can get that too with some OS versions. We don't |
| * expect real permission errors where we currently use rename(). |
| */ |
| if (err != ERROR_ACCESS_DENIED && |
| err != ERROR_SHARING_VIOLATION && |
| err != ERROR_LOCK_VIOLATION) |
| return -1; |
| #else |
| if (errno != EACCES) |
| return -1; |
| #endif |
| |
| if (++loops > 100) /* time out after 10 sec */ |
| return -1; |
| pg_usleep(100000); /* us */ |
| } |
| return 0; |
| } |
| |
| |
| /* |
| * pgunlink |
| */ |
| int |
| pgunlink(const char *path) |
| { |
| int loops = 0; |
| |
| /* |
| * We need to loop because even though PostgreSQL uses flags that allow |
| * unlink while the file is open, other applications might have the file |
| * open without those flags. However, we won't wait indefinitely for |
| * someone else to close the file, as the caller might be holding locks |
| * and blocking other backends. |
| */ |
| while (unlink(path)) |
| { |
| if (errno != EACCES) |
| return -1; |
| if (++loops > 100) /* time out after 10 sec */ |
| return -1; |
| pg_usleep(100000); /* us */ |
| } |
| return 0; |
| } |
| |
| /* We undefined these above; now redefine for possible use below */ |
| #define rename(from, to) pgrename(from, to) |
| #define unlink(path) pgunlink(path) |
| #endif /* defined(WIN32) || defined(__CYGWIN__) */ |
| |
| |
| #if defined(WIN32) && !defined(__CYGWIN__) /* Cygwin has its own symlinks */ |
| |
| /* |
| * pgsymlink support: |
| * |
| * This struct is a replacement for REPARSE_DATA_BUFFER which is defined in VC6 winnt.h |
| * but omitted in later SDK functions. |
| * We only need the SymbolicLinkReparseBuffer part of the original struct's union. |
| */ |
| typedef struct |
| { |
| DWORD ReparseTag; |
| WORD ReparseDataLength; |
| WORD Reserved; |
| /* SymbolicLinkReparseBuffer */ |
| WORD SubstituteNameOffset; |
| WORD SubstituteNameLength; |
| WORD PrintNameOffset; |
| WORD PrintNameLength; |
| WCHAR PathBuffer[1]; |
| } REPARSE_JUNCTION_DATA_BUFFER; |
| |
| #define REPARSE_JUNCTION_DATA_BUFFER_HEADER_SIZE \ |
| FIELD_OFFSET(REPARSE_JUNCTION_DATA_BUFFER, SubstituteNameOffset) |
| |
| |
| /* |
| * pgsymlink - uses Win32 junction points |
| * |
| * For reference: http://www.codeproject.com/KB/winsdk/junctionpoints.aspx |
| */ |
| int |
| pgsymlink(const char *oldpath, const char *newpath) |
| { |
| HANDLE dirhandle; |
| DWORD len; |
| char buffer[MAX_PATH * sizeof(WCHAR) + sizeof(REPARSE_JUNCTION_DATA_BUFFER)]; |
| char nativeTarget[MAX_PATH]; |
| char *p = nativeTarget; |
| REPARSE_JUNCTION_DATA_BUFFER *reparseBuf = (REPARSE_JUNCTION_DATA_BUFFER *) buffer; |
| |
| CreateDirectory(newpath, 0); |
| dirhandle = CreateFile(newpath, GENERIC_READ | GENERIC_WRITE, |
| 0, 0, OPEN_EXISTING, |
| FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, 0); |
| |
| if (dirhandle == INVALID_HANDLE_VALUE) |
| return -1; |
| |
| /* make sure we have an unparsed native win32 path */ |
| if (memcmp("\\??\\", oldpath, 4)) |
| sprintf(nativeTarget, "\\??\\%s", oldpath); |
| else |
| strcpy(nativeTarget, oldpath); |
| |
| while ((p = strchr(p, '/')) != 0) |
| *p++ = '\\'; |
| |
| len = strlen(nativeTarget) * sizeof(WCHAR); |
| reparseBuf->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; |
| reparseBuf->ReparseDataLength = len + 12; |
| reparseBuf->Reserved = 0; |
| reparseBuf->SubstituteNameOffset = 0; |
| reparseBuf->SubstituteNameLength = len; |
| reparseBuf->PrintNameOffset = len + sizeof(WCHAR); |
| reparseBuf->PrintNameLength = 0; |
| MultiByteToWideChar(CP_ACP, 0, nativeTarget, -1, |
| reparseBuf->PathBuffer, MAX_PATH); |
| |
| /* |
| * FSCTL_SET_REPARSE_POINT is coded differently depending on SDK version; |
| * we use our own definition |
| */ |
| if (!DeviceIoControl(dirhandle, |
| CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 41, METHOD_BUFFERED, FILE_ANY_ACCESS), |
| reparseBuf, |
| reparseBuf->ReparseDataLength + REPARSE_JUNCTION_DATA_BUFFER_HEADER_SIZE, |
| 0, 0, &len, 0)) |
| { |
| LPSTR msg; |
| |
| errno = 0; |
| FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, |
| NULL, GetLastError(), |
| MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), |
| (LPSTR) &msg, 0, NULL); |
| #ifndef FRONTEND |
| ereport(ERROR, |
| (errcode_for_file_access(), |
| errmsg("could not set junction for \"%s\": %s", |
| nativeTarget, msg))); |
| #else |
| fprintf(stderr, _("could not set junction for \"%s\": %s\n"), |
| nativeTarget, msg); |
| #endif |
| LocalFree(msg); |
| |
| CloseHandle(dirhandle); |
| RemoveDirectory(newpath); |
| return -1; |
| } |
| |
| CloseHandle(dirhandle); |
| |
| return 0; |
| } |
| #endif /* defined(WIN32) && !defined(__CYGWIN__) */ |
| |
| |
| /* |
| * pgfnames |
| * |
| * return a list of the names of objects in the argument directory. Caller |
| * must call pgfnames_cleanup later to free the memory allocated by this |
| * function. |
| */ |
| char ** |
| pgfnames(const char *path) |
| { |
| DIR *dir; |
| struct dirent *file; |
| char **filenames; |
| int numnames = 0; |
| int fnsize = 200; /* enough for many small dbs */ |
| |
| dir = opendir(path); |
| if (dir == NULL) |
| { |
| #ifndef FRONTEND |
| elog(WARNING, "could not open directory \"%s\": %m", path); |
| #else |
| fprintf(stderr, _("could not open directory \"%s\": %s\n"), |
| path, strerror(errno)); |
| #endif |
| return NULL; |
| } |
| |
| filenames = (char **) palloc(fnsize * sizeof(char *)); |
| |
| errno = 0; |
| while ((file = readdir(dir)) != NULL) |
| { |
| if (strcmp(file->d_name, ".") != 0 && strcmp(file->d_name, "..") != 0) |
| { |
| if (numnames + 1 >= fnsize) |
| { |
| fnsize *= 2; |
| filenames = (char **) repalloc(filenames, |
| fnsize * sizeof(char *)); |
| } |
| filenames[numnames++] = pstrdup(file->d_name); |
| } |
| errno = 0; |
| } |
| #ifdef WIN32 |
| |
| /* |
| * This fix is in mingw cvs (runtime/mingwex/dirent.c rev 1.4), but not in |
| * released version |
| */ |
| if (GetLastError() == ERROR_NO_MORE_FILES) |
| errno = 0; |
| #endif |
| if (errno) |
| { |
| #ifndef FRONTEND |
| elog(WARNING, "could not read directory \"%s\": %m", path); |
| #else |
| fprintf(stderr, _("could not read directory \"%s\": %s\n"), |
| path, strerror(errno)); |
| #endif |
| } |
| |
| filenames[numnames] = NULL; |
| |
| closedir(dir); |
| |
| return filenames; |
| } |
| |
| |
| /* |
| * pgfnames_cleanup |
| * |
| * deallocate memory used for filenames |
| */ |
| void |
| pgfnames_cleanup(char **filenames) |
| { |
| char **fn; |
| |
| for (fn = filenames; *fn; fn++) |
| pfree(*fn); |
| |
| pfree(filenames); |
| } |
| |
| |
| /* |
| * rmtree |
| * |
| * Delete a directory tree recursively. |
| * Assumes path points to a valid directory. |
| * Deletes everything under path. |
| * If rmtopdir is true deletes the directory too. |
| * Returns true if successful, false if there was any problem. |
| * (The details of the problem are reported already, so caller |
| * doesn't really have to say anything more, but most do.) |
| */ |
| bool |
| rmtree(const char *path, bool rmtopdir) |
| { |
| bool result = true; |
| char pathbuf[MAXPGPATH]; |
| char **filenames; |
| char **filename; |
| struct stat statbuf; |
| |
| /* |
| * we copy all the names out of the directory before we start modifying |
| * it. |
| */ |
| filenames = pgfnames(path); |
| |
| if (filenames == NULL) |
| return false; |
| |
| /* now we have the names we can start removing things */ |
| for (filename = filenames; *filename; filename++) |
| { |
| snprintf(pathbuf, MAXPGPATH, "%s/%s", path, *filename); |
| |
| /* |
| * It's ok if the file is not there anymore; we were just about to |
| * delete it anyway. |
| * |
| * This is not an academic possibility. One scenario where this |
| * happens is when bgwriter has a pending unlink request for a file in |
| * a database that's being dropped. In dropdb(), we call |
| * ForgetDatabaseFsyncRequests() to flush out any such pending unlink |
| * requests, but because that's asynchronous, it's not guaranteed that |
| * the bgwriter receives the message in time. |
| */ |
| if (lstat(pathbuf, &statbuf) != 0) |
| { |
| if (errno != ENOENT) |
| { |
| #ifndef FRONTEND |
| elog(WARNING, "could not stat file or directory \"%s\": %m", |
| pathbuf); |
| #else |
| fprintf(stderr, _("could not stat file or directory \"%s\": %s\n"), |
| pathbuf, strerror(errno)); |
| #endif |
| result = false; |
| } |
| continue; |
| } |
| |
| if (S_ISDIR(statbuf.st_mode)) |
| { |
| /* call ourselves recursively for a directory */ |
| if (!rmtree(pathbuf, true)) |
| { |
| /* we already reported the error */ |
| result = false; |
| } |
| } |
| else |
| { |
| if (unlink(pathbuf) != 0) |
| { |
| if (errno != ENOENT) |
| { |
| #ifndef FRONTEND |
| elog(WARNING, "could not remove file or directory \"%s\": %m", |
| pathbuf); |
| #else |
| fprintf(stderr, _("could not remove file or directory \"%s\": %s\n"), |
| pathbuf, strerror(errno)); |
| #endif |
| result = false; |
| } |
| } |
| } |
| } |
| |
| if (rmtopdir) |
| { |
| if (rmdir(path) != 0) |
| { |
| #ifndef FRONTEND |
| elog(WARNING, "could not remove file or directory \"%s\": %m", |
| path); |
| #else |
| fprintf(stderr, _("could not remove file or directory \"%s\": %s\n"), |
| path, strerror(errno)); |
| #endif |
| result = false; |
| } |
| } |
| |
| pgfnames_cleanup(filenames); |
| |
| return result; |
| } |
| |
| |
| #if defined(WIN32) && !defined(__CYGWIN__) |
| |
| #undef stat |
| |
| /* |
| * The stat() function in win32 is not guaranteed to update the st_size |
| * field when run. So we define our own version that uses the Win32 API |
| * to update this field. |
| */ |
| int |
| pgwin32_safestat(const char *path, struct stat * buf) |
| { |
| int r; |
| WIN32_FILE_ATTRIBUTE_DATA attr; |
| |
| r = stat(path, buf); |
| if (r < 0) |
| return r; |
| |
| if (!GetFileAttributesEx(path, GetFileExInfoStandard, &attr)) |
| { |
| _dosmaperr(GetLastError()); |
| return -1; |
| } |
| |
| /* |
| * XXX no support for large files here, but we don't do that in general on |
| * Win32 yet. |
| */ |
| buf->st_size = attr.nFileSizeLow; |
| |
| return 0; |
| } |
| |
| #endif |