diff options
Diffstat (limited to 'erts/emulator/drivers/win32/win_efile.c')
-rw-r--r-- | erts/emulator/drivers/win32/win_efile.c | 1426 |
1 files changed, 1426 insertions, 0 deletions
diff --git a/erts/emulator/drivers/win32/win_efile.c b/erts/emulator/drivers/win32/win_efile.c new file mode 100644 index 0000000000..89aaad31da --- /dev/null +++ b/erts/emulator/drivers/win32/win_efile.c @@ -0,0 +1,1426 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1997-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +/* + * Purpose: Provides file and directory operations for Windows. + */ + +#include <windows.h> +#include "sys.h" +#include <ctype.h> + +#include "erl_efile.h" + +/* + * Microsoft-specific function to map a WIN32 error code to a Posix errno. + */ + +#define ISSLASH(a) ((a) == '\\' || (a) == '/') + +#define ISDIR(st) (((st).st_mode&S_IFMT) == S_IFDIR) +#define ISREG(st) (((st).st_mode&S_IFMT) == S_IFREG) + +#define IS_DOT_OR_DOTDOT(s) \ + (s[0] == '.' && (s[1] == '\0' || (s[1] == '.' && s[2] == '\0'))) + +#ifndef INVALID_FILE_ATTRIBUTES +#define INVALID_FILE_ATTRIBUTES ((DWORD) 0xFFFFFFFF) +#endif + +static int check_error(int result, Efile_error* errInfo); +static int set_error(Efile_error* errInfo); +static int IsRootUNCName(const char* path); +static int extract_root(char* name); +static unsigned short dos_to_posix_mode(int attr, const char *name); + +static int errno_map(DWORD last_error) { + + switch (last_error) { + case ERROR_SUCCESS: + return 0; + case ERROR_INVALID_FUNCTION: + case ERROR_INVALID_DATA: + case ERROR_INVALID_PARAMETER: + case ERROR_INVALID_TARGET_HANDLE: + case ERROR_INVALID_CATEGORY: + case ERROR_NEGATIVE_SEEK: + return EINVAL; + case ERROR_DIR_NOT_EMPTY: + return EEXIST; + case ERROR_BAD_FORMAT: + return ENOEXEC; + case ERROR_PATH_NOT_FOUND: + case ERROR_FILE_NOT_FOUND: + case ERROR_NO_MORE_FILES: + return ENOENT; + case ERROR_TOO_MANY_OPEN_FILES: + return EMFILE; + case ERROR_ACCESS_DENIED: + case ERROR_INVALID_ACCESS: + case ERROR_CURRENT_DIRECTORY: + case ERROR_SHARING_VIOLATION: + case ERROR_LOCK_VIOLATION: + case ERROR_INVALID_PASSWORD: + case ERROR_DRIVE_LOCKED: + return EACCES; + case ERROR_INVALID_HANDLE: + return EBADF; + case ERROR_NOT_ENOUGH_MEMORY: + case ERROR_OUTOFMEMORY: + case ERROR_OUT_OF_STRUCTURES: + return ENOMEM; + case ERROR_INVALID_DRIVE: + case ERROR_BAD_UNIT: + case ERROR_NOT_READY: + case ERROR_REM_NOT_LIST: + case ERROR_DUP_NAME: + case ERROR_BAD_NETPATH: + case ERROR_NETWORK_BUSY: + case ERROR_DEV_NOT_EXIST: + case ERROR_BAD_NET_NAME: + return ENXIO; + case ERROR_NOT_SAME_DEVICE: + return EXDEV; + case ERROR_WRITE_PROTECT: + return EROFS; + case ERROR_BAD_LENGTH: + case ERROR_BUFFER_OVERFLOW: + return E2BIG; + case ERROR_SEEK: + case ERROR_SECTOR_NOT_FOUND: + return ESPIPE; + case ERROR_NOT_DOS_DISK: + return ENODEV; + case ERROR_GEN_FAILURE: + return ENODEV; + case ERROR_SHARING_BUFFER_EXCEEDED: + case ERROR_NO_MORE_SEARCH_HANDLES: + return EMFILE; + case ERROR_HANDLE_EOF: + case ERROR_BROKEN_PIPE: + return EPIPE; + case ERROR_HANDLE_DISK_FULL: + case ERROR_DISK_FULL: + return ENOSPC; + case ERROR_NOT_SUPPORTED: + return ENOTSUP; + case ERROR_FILE_EXISTS: + case ERROR_ALREADY_EXISTS: + case ERROR_CANNOT_MAKE: + return EEXIST; + case ERROR_ALREADY_ASSIGNED: + return EBUSY; + case ERROR_NO_PROC_SLOTS: + return EAGAIN; + case ERROR_ARENA_TRASHED: + case ERROR_INVALID_BLOCK: + case ERROR_BAD_ENVIRONMENT: + case ERROR_BAD_COMMAND: + case ERROR_CRC: + case ERROR_OUT_OF_PAPER: + case ERROR_READ_FAULT: + case ERROR_WRITE_FAULT: + case ERROR_WRONG_DISK: + case ERROR_NET_WRITE_FAULT: + return EIO; + default: /* not to do with files I expect. */ + return EIO; + } +} + +static int +check_error(int result, Efile_error* errInfo) +{ + if (result < 0) { + errInfo->posix_errno = errno; + errInfo->os_errno = GetLastError(); + return 0; + } + return 1; +} + +/* + * Fills the provided error information structure with information + * with the error code given by GetLastError() and its corresponding + * Posix error number. + * + * Returns 0. + */ + +static int +set_error(Efile_error* errInfo) +{ + errInfo->posix_errno = errno_map(errInfo->os_errno = GetLastError()); + return 0; +} + +/* + * A writev with Unix semantics, but with Windows arguments + */ +static int +win_writev(Efile_error* errInfo, + HANDLE fd, /* handle to file */ + FILE_SEGMENT_ELEMENT iov[], /* array of buffer pointers */ + DWORD *size) /* number of bytes to write */ +{ + OVERLAPPED ov; + ov.Offset = 0L; + ov.OffsetHigh = 0L; + ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (ov.hEvent == NULL) + return set_error(errInfo); + if (! write_file_gather(fd, iov, *size, NULL, &ov)) + return set_error(errInfo); + if (WaitForSingleObject(ov.hEvent, INFINITE) != WAIT_OBJECT_0) + return set_error(errInfo); + if (! GetOverlappedResult(fd, &ov, size, FALSE)) + return set_error(errInfo); + return 1; +} + + + +int +efile_mkdir(errInfo, name) +Efile_error* errInfo; /* Where to return error codes. */ +char* name; /* Name of directory to create. */ +{ + return check_error(mkdir(name), errInfo); +} + +int +efile_rmdir(errInfo, name) +Efile_error* errInfo; /* Where to return error codes. */ +char* name; /* Name of directory to delete. */ +{ + OSVERSIONINFO os; + DWORD attr; + + if (RemoveDirectory(name) != FALSE) { + return 1; + } + errno = errno_map(GetLastError()); + if (errno == EACCES) { + attr = GetFileAttributes(name); + if (attr != (DWORD) -1) { + if ((attr & FILE_ATTRIBUTE_DIRECTORY) == 0) { + /* + * Windows 95 reports calling RemoveDirectory on a file as an + * EACCES, not an ENOTDIR. + */ + + errno = ENOTDIR; + goto end; + } + + /* + * Windows 95 reports removing a non-empty directory as + * an EACCES, not an EEXIST. If the directory is not empty, + * change errno so caller knows what's going on. + */ + + os.dwOSVersionInfoSize = sizeof(os); + GetVersionEx(&os); + if (os.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) { + HANDLE handle; + WIN32_FIND_DATA data; + char buffer[2*MAX_PATH]; + int len; + + len = strlen(name); + strcpy(buffer, name); + if (buffer[0] && buffer[len-1] != '\\' && buffer[len-1] != '/') { + strcat(buffer, "\\"); + } + strcat(buffer, "*.*"); + handle = FindFirstFile(buffer, &data); + if (handle != INVALID_HANDLE_VALUE) { + while (1) { + if ((strcmp(data.cFileName, ".") != 0) + && (strcmp(data.cFileName, "..") != 0)) { + /* + * Found something in this directory. + */ + + errno = EEXIST; + break; + } + if (FindNextFile(handle, &data) == FALSE) { + break; + } + } + FindClose(handle); + } + } + } + } + + if (errno == ENOTEMPTY) { + /* + * Posix allows both EEXIST or ENOTEMPTY, but we'll always + * return EEXIST to allow easy matching in Erlang code. + */ + + errno = EEXIST; + } + + end: + return check_error(-1, errInfo); +} + +int +efile_delete_file(errInfo, name) +Efile_error* errInfo; /* Where to return error codes. */ +char* name; /* Name of file to delete. */ +{ + DWORD attr; + + if (DeleteFile(name) != FALSE) { + return 1; + } + + errno = errno_map(GetLastError()); + if (errno == EACCES) { + attr = GetFileAttributes(name); + if (attr != (DWORD) -1) { + if (attr & FILE_ATTRIBUTE_DIRECTORY) { + /* + * Windows NT reports removing a directory as EACCES instead + * of EPERM. + */ + + errno = EPERM; + } + } + } else if (errno == ENOENT) { + attr = GetFileAttributes(name); + if (attr != (DWORD) -1) { + if (attr & FILE_ATTRIBUTE_DIRECTORY) { + /* + * Windows 95 reports removing a directory as ENOENT instead + * of EPERM. + */ + + errno = EPERM; + } + } + } else if (errno == EINVAL) { + /* + * Windows NT reports removing a char device as EINVAL instead of + * EACCES. + */ + + errno = EACCES; + } + + return check_error(-1, errInfo); +} + +/* + *--------------------------------------------------------------------------- + * + * Changes the name of an existing file or directory, from src to dst. + * If src and dst refer to the same file or directory, does nothing + * and returns success. Otherwise if dst already exists, it will be + * deleted and replaced by src subject to the following conditions: + * If src is a directory, dst may be an empty directory. + * If src is a file, dst may be a file. + * In any other situation where dst already exists, the rename will + * fail. + * + * Some possible error codes: + * + * EACCES: src or dst parent directory can't be read and/or written. + * EEXIST: dst is a non-empty directory. + * EINVAL: src is a root directory or dst is a subdirectory of src. + * EISDIR: dst is a directory, but src is not. + * ENOENT: src doesn't exist, or src or dst is "". + * ENOTDIR: src is a directory, but dst is not. + * EXDEV: src and dst are on different filesystems. + * + * Side effects: + * The implementation of rename may allow cross-filesystem renames, + * but the caller should be prepared to emulate it with copy and + * delete if errno is EXDEV. + * + *--------------------------------------------------------------------------- + */ + +int +efile_rename(errInfo, src, dst) +Efile_error* errInfo; /* Where to return error codes. */ +char* src; /* Original name. */ +char* dst; /* New name. */ +{ + DWORD srcAttr, dstAttr; + + if (MoveFile(src, dst) != FALSE) { + return 1; + } + + errno = errno_map(GetLastError()); + srcAttr = GetFileAttributes(src); + dstAttr = GetFileAttributes(dst); + if (srcAttr == (DWORD) -1) { + srcAttr = 0; + } + if (dstAttr == (DWORD) -1) { + dstAttr = 0; + } + + if (errno == EBADF) { + errno = EACCES; + return check_error(-1, errInfo); + } + if (errno == EACCES) { + decode: + if (srcAttr & FILE_ATTRIBUTE_DIRECTORY) { + char srcPath[MAX_PATH], dstPath[MAX_PATH]; + char *srcRest, *dstRest; + int size; + + size = GetFullPathName(src, sizeof(srcPath), srcPath, &srcRest); + if ((size == 0) || (size > sizeof(srcPath))) { + return check_error(-1, errInfo); + } + size = GetFullPathName(dst, sizeof(dstPath), dstPath, &dstRest); + if ((size == 0) || (size > sizeof(dstPath))) { + return check_error(-1, errInfo); + } + if (srcRest == NULL) { + srcRest = srcPath + strlen(srcPath); + } + if (strnicmp(srcPath, dstPath, srcRest - srcPath) == 0) { + /* + * Trying to move a directory into itself. + */ + + errno = EINVAL; + } + if (extract_root(srcPath)) { + /* + * Attempt to move a root directory. Never allowed. + */ + errno = EINVAL; + } + + (void) extract_root(dstPath); + if (dstPath[0] == '\0') { + /* + * The filename was invalid. (Don't know why, + * but play it safe.) + */ + errno = EINVAL; + } + if (stricmp(srcPath, dstPath) != 0) { + /* + * If src is a directory and dst filesystem != src + * filesystem, errno should be EXDEV. It is very + * important to get this behavior, so that the caller + * can respond to a cross filesystem rename by + * simulating it with copy and delete. The MoveFile + * system call already handles the case of moving a + * *file* between filesystems. + */ + + errno = EXDEV; + } + } + + /* + * Other types of access failure is that dst is a read-only + * filesystem, that an open file referred to src or dest, or that + * src or dest specified the current working directory on the + * current filesystem. EACCES is returned for those cases. + */ + + } else if (errno == EEXIST) { + /* + * Reports EEXIST any time the target already exists. If it makes + * sense, remove the old file and try renaming again. + */ + + if (srcAttr & FILE_ATTRIBUTE_DIRECTORY) { + if (dstAttr & FILE_ATTRIBUTE_DIRECTORY) { + /* + * Overwrite empty dst directory with src directory. The + * following call will remove an empty directory. If it + * fails, it's because it wasn't empty. + */ + + if (RemoveDirectory(dst)) { + /* + * Now that that empty directory is gone, we can try + * renaming again. If that fails, we'll put this empty + * directory back, for completeness. + */ + + if (MoveFile(src, dst) != FALSE) { + return 1; + } + + /* + * Some new error has occurred. Don't know what it + * could be, but report this one. + */ + + errno = errno_map(GetLastError()); + CreateDirectory(dst, NULL); + SetFileAttributes(dst, dstAttr); + if (errno == EACCES) { + /* + * Decode the EACCES to a more meaningful error. + */ + + goto decode; + } + } + } else { /* (dstAttr & FILE_ATTRIBUTE_DIRECTORY) == 0 */ + errno = ENOTDIR; + } + } else { /* (srcAttr & FILE_ATTRIBUTE_DIRECTORY) == 0 */ + if (dstAttr & FILE_ATTRIBUTE_DIRECTORY) { + errno = EISDIR; + } else { + /* + * Overwrite existing file by: + * + * 1. Rename existing file to temp name. + * 2. Rename old file to new name. + * 3. If success, delete temp file. If failure, + * put temp file back to old name. + */ + + char tempName[MAX_PATH]; + int result, size; + char *rest; + + size = GetFullPathName(dst, sizeof(tempName), tempName, &rest); + if ((size == 0) || (size > sizeof(tempName)) || (rest == NULL)) { + return check_error(-1, errInfo); + } + *rest = '\0'; + result = -1; + if (GetTempFileName(tempName, "erlr", 0, tempName) != 0) { + /* + * Strictly speaking, need the following DeleteFile and + * MoveFile to be joined as an atomic operation so no + * other app comes along in the meantime and creates the + * same temp file. + */ + + DeleteFile(tempName); + if (MoveFile(dst, tempName) != FALSE) { + if (MoveFile(src, dst) != FALSE) { + SetFileAttributes(tempName, FILE_ATTRIBUTE_NORMAL); + DeleteFile(tempName); + return 1; + } else { + DeleteFile(dst); + MoveFile(tempName, dst); + } + } + + /* + * Can't backup dst file or move src file. Return that + * error. Could happen if an open file refers to dst. + */ + + errno = errno_map(GetLastError()); + if (errno == EACCES) { + /* + * Decode the EACCES to a more meaningful error. + */ + + goto decode; + } + } + return result; + } + } + } + return check_error(-1, errInfo); +} + +int +efile_chdir(errInfo, name) +Efile_error* errInfo; /* Where to return error codes. */ +char* name; /* Name of directory to make current. */ +{ + int success = check_error(chdir(name), errInfo); + if (!success && errInfo->posix_errno == EINVAL) + /* POSIXification of errno */ + errInfo->posix_errno = ENOENT; + return success; +} + +int +efile_getdcwd(errInfo, drive, buffer, size) +Efile_error* errInfo; /* Where to return error codes. */ +int drive; /* 0 - current, 1 - A, 2 - B etc. */ +char* buffer; /* Where to return the current directory. */ +size_t size; /* Size of buffer. */ +{ + if (_getdcwd(drive, buffer, size) == NULL) + return check_error(-1, errInfo); + for ( ; *buffer; buffer++) + if (*buffer == '\\') + *buffer = '/'; + return 1; +} + +int +efile_readdir(errInfo, name, dir_handle, buffer, size) +Efile_error* errInfo; /* Where to return error codes. */ +char* name; /* Name of directory to open. */ +EFILE_DIR_HANDLE* dir_handle; /* Directory handle of open directory. */ +char* buffer; /* Pointer to buffer for one filename. */ +size_t size; /* Size of buffer. */ +{ + HANDLE dir; /* Handle to directory. */ + char wildcard[MAX_PATH]; /* Wildcard to search for. */ + WIN32_FIND_DATA findData; /* Data found by FindFirstFile() or FindNext(). */ + + /* + * First time we must setup everything. + */ + + if (*dir_handle == NULL) { + int length = strlen(name); + char* s; + + if (length+3 >= MAX_PATH) { + errno = ENAMETOOLONG; + return check_error(-1, errInfo); + } + + strcpy(wildcard, name); + s = wildcard+length-1; + if (*s != '/' && *s != '\\') + *++s = '\\'; + *++s = '*'; + *++s = '\0'; + DEBUGF(("Reading %s\n", wildcard)); + dir = FindFirstFile(wildcard, &findData); + if (dir == INVALID_HANDLE_VALUE) + return set_error(errInfo); + *dir_handle = (EFILE_DIR_HANDLE) dir; + + if (!IS_DOT_OR_DOTDOT(findData.cFileName)) { + strcpy(buffer, findData.cFileName); + return 1; + } + } + + + /* + * Retrieve the name of the next file using the directory handle. + */ + + dir = (HANDLE) *dir_handle; + + for (;;) { + if (FindNextFile(dir, &findData)) { + if (IS_DOT_OR_DOTDOT(findData.cFileName)) + continue; + strcpy(buffer, findData.cFileName); + return 1; + } + + if (GetLastError() == ERROR_NO_MORE_FILES) { + FindClose(dir); + errInfo->posix_errno = errInfo->os_errno = 0; + return 0; + } + + set_error(errInfo); + FindClose(dir); + return 0; + } +} + +int +efile_openfile(errInfo, name, flags, pfd, pSize) +Efile_error* errInfo; /* Where to return error codes. */ +char* name; /* Name of directory to open. */ +int flags; /* Flags to use for opening. */ +int* pfd; /* Where to store the file descriptor. */ +Sint64* pSize; /* Where to store the size of the file. */ +{ + BY_HANDLE_FILE_INFORMATION fileInfo; /* File information from a handle. */ + HANDLE fd; /* Handle to open file. */ + DWORD access; /* Access mode: GENERIC_READ, GENERIC_WRITE. */ + DWORD crFlags; + + switch (flags & (EFILE_MODE_READ|EFILE_MODE_WRITE)) { + case EFILE_MODE_READ: + access = GENERIC_READ; + crFlags = OPEN_EXISTING; + break; + case EFILE_MODE_WRITE: + access = GENERIC_WRITE; + crFlags = CREATE_ALWAYS; + break; + case EFILE_MODE_READ_WRITE: + access = GENERIC_READ|GENERIC_WRITE; + crFlags = OPEN_ALWAYS; + break; + default: + errno = EINVAL; + check_error(-1, errInfo); + return 0; + } + + if (flags & EFILE_MODE_APPEND) { + crFlags = OPEN_ALWAYS; + } + fd = CreateFile(name, access, FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, crFlags, FILE_ATTRIBUTE_NORMAL, NULL); + + /* + * Check for errors. + */ + + if (fd == INVALID_HANDLE_VALUE) { + DWORD attr; + + set_error(errInfo); + + /* + * If the error is EACESS, the reason could be that we tried to + * open a directory. In that case, we'll change the error code + * to EISDIR. + */ + if (errInfo->posix_errno && + (attr = GetFileAttributes(name)) != INVALID_FILE_ATTRIBUTES && + (attr & FILE_ATTRIBUTE_DIRECTORY)) { + errInfo->posix_errno = EISDIR; + } + return 0; + } + + /* + * Get and return the length of the open file. + */ + + if (!GetFileInformationByHandle(fd, &fileInfo)) + return set_error(errInfo); + *pfd = (int) fd; + if (pSize) { + *pSize = (Sint64) + (((Uint64)fileInfo.nFileSizeHigh << 32) | + (Uint64)fileInfo.nFileSizeLow); + } + return 1; +} + +int +efile_may_openfile(Efile_error* errInfo, char *name) { + DWORD attr; + + if ((attr = GetFileAttributes(name)) == INVALID_FILE_ATTRIBUTES) { + return check_error(-1, errInfo); + } + + if (attr & FILE_ATTRIBUTE_DIRECTORY) { + errno = EISDIR; + return check_error(-1, errInfo); + } + return 1; +#if 0 + struct stat statbuf; + + if (stat(name, &statbuf)) { + return check_error(-1, errInfo); + } + if (ISDIR(statbuf)) { + errno = EISDIR; + return check_error(-1, errInfo); + } + return 1; +#endif +} + +void +efile_closefile(fd) +int fd; /* File descriptor for file to close. */ +{ + CloseHandle((HANDLE) fd); +} + +int +efile_fsync(errInfo, fd) +Efile_error* errInfo; /* Where to return error codes. */ +int fd; /* File descriptor for file to sync. */ +{ + if (!FlushFileBuffers((HANDLE) fd)) { + return check_error(-1, errInfo); + } + return 1; +} + +int +efile_fileinfo(Efile_error* errInfo, Efile_info* pInfo, + char* orig_name, int info_for_link) +{ + HANDLE findhandle; /* Handle returned by FindFirstFile(). */ + WIN32_FIND_DATA findbuf; /* Data return by FindFirstFile(). */ + char name[_MAX_PATH]; + int name_len; + char* path; + char pathbuf[_MAX_PATH]; + int drive; /* Drive for filename (1 = A:, 2 = B: etc). */ + + /* Don't allow wildcards to be interpreted by system */ + + if (strpbrk(orig_name, "?*")) { + enoent: + errInfo->posix_errno = ENOENT; + errInfo->os_errno = ERROR_FILE_NOT_FOUND; + return 0; + } + + /* + * Move the name to a buffer and make sure to remove a trailing + * slash, because it causes FindFirstFile() to fail on Win95. + */ + + if ((name_len = strlen(orig_name)) >= _MAX_PATH) { + goto enoent; + } else { + strcpy(name, orig_name); + if (name_len > 2 && ISSLASH(name[name_len-1]) && + name[name_len-2] != ':') { + name[name_len-1] = '\0'; + } + } + + /* Try to get disk from name. If none, get current disk. */ + + if (name[1] != ':') { + drive = 0; + if (GetCurrentDirectory(sizeof(pathbuf), pathbuf) && + pathbuf[1] == ':') { + drive = tolower(pathbuf[0]) - 'a' + 1; + } + } else if (*name && name[2] == '\0') { + /* + * X: and nothing more is an error. + */ + errInfo->posix_errno = ENOENT; + errInfo->os_errno = ERROR_FILE_NOT_FOUND; + return 0; + } else + drive = tolower(*name) - 'a' + 1; + + findhandle = FindFirstFile(name, &findbuf); + if (findhandle == INVALID_HANDLE_VALUE) { + if (!(strpbrk(name, "./\\") && + (path = _fullpath(pathbuf, name, _MAX_PATH)) && + /* root dir. ('C:\') or UNC root dir. ('\\server\share\') */ + ((strlen(path) == 3) || IsRootUNCName(path)) && + (GetDriveType(path) > 1) ) ) { + errInfo->posix_errno = ENOENT; + errInfo->os_errno = ERROR_FILE_NOT_FOUND; + return 0; + } + + /* + * Root directories (such as C:\ or \\server\share\ are fabricated. + */ + + findbuf.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY; + findbuf.nFileSizeHigh = 0; + findbuf.nFileSizeLow = 0; + findbuf.cFileName[0] = '\0'; + + pInfo->modifyTime.year = 1980; + pInfo->modifyTime.month = 1; + pInfo->modifyTime.day = 1; + pInfo->modifyTime.hour = 0; + pInfo->modifyTime.minute = 0; + pInfo->modifyTime.second = 0; + + pInfo->accessTime = pInfo->modifyTime; + } else { + SYSTEMTIME SystemTime; + FILETIME LocalFTime; + +#define GET_TIME(dst, src) \ +if (!FileTimeToLocalFileTime(&findbuf.src, &LocalFTime) || \ + !FileTimeToSystemTime(&LocalFTime, &SystemTime)) { \ + return set_error(errInfo); \ +} \ +(dst).year = SystemTime.wYear; \ +(dst).month = SystemTime.wMonth; \ +(dst).day = SystemTime.wDay; \ +(dst).hour = SystemTime.wHour; \ +(dst).minute = SystemTime.wMinute; \ +(dst).second = SystemTime.wSecond; + + GET_TIME(pInfo->modifyTime, ftLastWriteTime); + + if (findbuf.ftLastAccessTime.dwLowDateTime == 0 && + findbuf.ftLastAccessTime.dwHighDateTime == 0) { + pInfo->accessTime = pInfo->modifyTime; + } else { + GET_TIME(pInfo->accessTime, ftLastAccessTime); + } + + if (findbuf.ftCreationTime.dwLowDateTime == 0 && + findbuf.ftCreationTime.dwHighDateTime == 0) { + pInfo->cTime = pInfo->modifyTime; + } else { + GET_TIME(pInfo->cTime, ftCreationTime); + } +#undef GET_TIME + FindClose(findhandle); + } + + pInfo->size_low = findbuf.nFileSizeLow; + pInfo->size_high = findbuf.nFileSizeHigh; + + if (findbuf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + pInfo->type = FT_DIRECTORY; + else + pInfo->type = FT_REGULAR; + + if (findbuf.dwFileAttributes & FILE_ATTRIBUTE_READONLY) + pInfo->access = FA_READ; + else + pInfo->access = FA_READ|FA_WRITE; + + pInfo->mode = dos_to_posix_mode(findbuf.dwFileAttributes, name); + pInfo->links = 1; + pInfo->major_device = drive; + pInfo->minor_device = 0; + pInfo->inode = 0; + pInfo->uid = 0; + pInfo->gid = 0; + + return 1; +} + +int +efile_write_info(errInfo, pInfo, name) +Efile_error* errInfo; +Efile_info* pInfo; +char* name; +{ + SYSTEMTIME timebuf; + FILETIME LocalFileTime; + FILETIME ModifyFileTime; + FILETIME AccessFileTime; + FILETIME CreationFileTime; + HANDLE fd; + FILETIME* mtime = NULL; + FILETIME* atime = NULL; + FILETIME* ctime = NULL; + DWORD attr; + DWORD tempAttr; + BOOL modifyTime = FALSE; + + /* + * Get the attributes for the file. + */ + + tempAttr = attr = GetFileAttributes((LPTSTR)name); + if (attr == 0xffffffff) { + return set_error(errInfo); + } + if (pInfo->mode != -1) { + if (pInfo->mode & _S_IWRITE) { + /* clear read only bit */ + attr &= ~FILE_ATTRIBUTE_READONLY; + } else { + /* set read only bit */ + attr |= FILE_ATTRIBUTE_READONLY; + } + } + + /* + * Construct all file times. + */ + +#define MKTIME(tb, ts, ptr) \ + timebuf.wYear = ts.year; \ + timebuf.wMonth = ts.month; \ + timebuf.wDay = ts.day; \ + timebuf.wHour = ts.hour; \ + timebuf.wMinute = ts.minute; \ + timebuf.wSecond = ts.second; \ + timebuf.wMilliseconds = 0; \ + if (ts.year != -1) { \ + modifyTime = TRUE; \ + ptr = &tb; \ + if (!SystemTimeToFileTime(&timebuf, &LocalFileTime ) || \ + !LocalFileTimeToFileTime(&LocalFileTime, &tb)) { \ + errno = EINVAL; \ + return check_error(-1, errInfo); \ + } \ + } + + MKTIME(ModifyFileTime, pInfo->accessTime, mtime); + MKTIME(AccessFileTime, pInfo->modifyTime, atime); + MKTIME(CreationFileTime, pInfo->cTime, ctime); +#undef MKTIME + + /* + * If necessary, set the file times. + */ + + if (modifyTime) { + /* + * If the has read only access, we must temporarily turn on + * write access (this is necessary for native filesystems, + * but not for NFS filesystems). + */ + + if (tempAttr & FILE_ATTRIBUTE_READONLY) { + tempAttr &= ~FILE_ATTRIBUTE_READONLY; + if (!SetFileAttributes((LPTSTR) name, tempAttr)) { + return set_error(errInfo); + } + } + + fd = CreateFile(name, GENERIC_READ|GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (fd != INVALID_HANDLE_VALUE) { + BOOL result = SetFileTime(fd, ctime, atime, mtime); + if (!result) { + return set_error(errInfo); + } + CloseHandle(fd); + } + } + + /* + * If the file doesn't have the correct attributes, set them now. + * (It could have been done before setting the file times, above). + */ + + if (tempAttr != attr) { + if (!SetFileAttributes((LPTSTR) name, attr)) { + return set_error(errInfo); + } + } + return 1; +} + + +int +efile_pwrite(errInfo, fd, buf, count, offset) +Efile_error* errInfo; /* Where to return error codes. */ +int fd; /* File descriptor to write to. */ +char* buf; /* Buffer to write. */ +size_t count; /* Number of bytes to write. */ +Sint64 offset; /* where to write it */ +{ + int res = efile_seek(errInfo, fd, offset, EFILE_SEEK_SET, NULL); + if (res) { + return efile_write(errInfo, EFILE_MODE_WRITE, fd, buf, count); + } else { + return res; + } +} + +/* position and read/write as a single atomic op */ +int +efile_pread(errInfo, fd, offset, buf, count, pBytesRead) +Efile_error* errInfo; /* Where to return error codes. */ +int fd; /* File descriptor to read from. */ +Sint64 offset; /* Offset in bytes from BOF. */ +char* buf; /* Buffer to read into. */ +size_t count; /* Number of bytes to read. */ +size_t* pBytesRead; /* Where to return number of bytes read. */ +{ + int res = efile_seek(errInfo, fd, offset, EFILE_SEEK_SET, NULL); + if (res) { + return efile_read(errInfo, EFILE_MODE_READ, fd, buf, count, pBytesRead); + } else { + return res; + } +} + + + +int +efile_write(errInfo, flags, fd, buf, count) +Efile_error* errInfo; /* Where to return error codes. */ +int flags; /* Flags given when file was opened. */ +int fd; /* File descriptor to write to. */ +char* buf; /* Buffer to write. */ +size_t count; /* Number of bytes to write. */ +{ + DWORD written; /* Bytes written in last operation. */ + + if (flags & EFILE_MODE_APPEND) { + (void) SetFilePointer((HANDLE) fd, 0, NULL, FILE_END); + } + while (count > 0) { + if (!WriteFile((HANDLE) fd, buf, count, &written, NULL)) + return set_error(errInfo); + buf += written; + count -= written; + } + return 1; +} + +int +efile_writev(Efile_error* errInfo, /* Where to return error codes */ + int flags, /* Flags given when file was + * opened */ + int fd, /* File descriptor to write to */ + SysIOVec* iov, /* Vector of buffer structs. + * The structs are unchanged + * after the call */ + int iovcnt, /* Number of structs in vector */ + size_t size) /* Number of bytes to write */ +{ + int cnt; /* Buffers so far written */ + + ASSERT(iovcnt >= 0); + + if (flags & EFILE_MODE_APPEND) { + (void) SetFilePointer((HANDLE) fd, 0, NULL, FILE_END); + } + for (cnt = 0; cnt < iovcnt; cnt++) { + if (iov[cnt].iov_base && iov[cnt].iov_len > 0) { + /* Non-empty buffer */ + int p; /* Position in buffer */ + int w = iov[cnt].iov_len;/* Bytes written in this call */ + for (p = 0; p < iov[cnt].iov_len; p += w) { + if (!WriteFile((HANDLE) fd, + iov[cnt].iov_base + p, + iov[cnt].iov_len - p, + &w, + NULL)) + return set_error(errInfo); + } + } + } + return 1; +} + +int +efile_read(errInfo, flags, fd, buf, count, pBytesRead) +Efile_error* errInfo; /* Where to return error codes. */ +int flags; /* Flags given when file was opened. */ +int fd; /* File descriptor to read from. */ +char* buf; /* Buffer to read into. */ +size_t count; /* Number of bytes to read. */ +size_t* pBytesRead; /* Where to return number of bytes read. */ +{ + if (!ReadFile((HANDLE) fd, buf, count, (DWORD *) pBytesRead, NULL)) + return set_error(errInfo); + return 1; +} + +int +efile_seek(errInfo, fd, offset, origin, new_location) +Efile_error* errInfo; /* Where to return error codes. */ +int fd; /* File descriptor to do the seek on. */ +Sint64 offset; /* Offset in bytes from the given origin. */ +int origin; /* Origin of seek (SEEK_SET, SEEK_CUR, + * SEEK_END). + */ +Sint64* new_location; /* Resulting new location in file. */ +{ + LARGE_INTEGER off, new_loc; + + switch (origin) { + case EFILE_SEEK_SET: origin = FILE_BEGIN; break; + case EFILE_SEEK_CUR: origin = FILE_CURRENT; break; + case EFILE_SEEK_END: origin = FILE_END; break; + default: + errno = EINVAL; + check_error(-1, errInfo); + break; + } + + off.QuadPart = offset; + if (! SetFilePointerEx((HANDLE) fd, off, + new_location ? &new_loc : NULL, origin)) { + return set_error(errInfo); + } + if (new_location) { + *new_location = new_loc.QuadPart; + DEBUGF(("efile_seek(offset=%ld, origin=%d) -> %ld\n", + (long) offset, origin, (long) *new_location)); + } else { + DEBUGF(("efile_seek(offset=%ld, origin=%d)\n", (long) offset, origin)); + } + return 1; +} + +int +efile_truncate_file(errInfo, fd, flags) +Efile_error* errInfo; /* Where to return error codes. */ +int *fd; /* File descriptor for file to truncate. */ +int flags; +{ + if (!SetEndOfFile((HANDLE) (*fd))) + return set_error(errInfo); + return 1; +} + + +/* + * IsRootUNCName - returns TRUE if the argument is a UNC name specifying + * a root share. That is, if it is of the form \\server\share\. + * This routine will also return true if the argument is of the + * form \\server\share (no trailing slash) but Win32 currently + * does not like that form. + * + * Forward slashes ('/') may be used instead of backslashes ('\'). + */ + +static int +IsRootUNCName(const char* path) +{ + /* + * If a root UNC name, path will start with 2 (but not 3) slashes + */ + + if ((strlen(path) >= 5) /* minimum string is "//x/y" */ + && ISSLASH(path[0]) && ISSLASH(path[1])) + { + const char * p = path + 2 ; + + /* + * find the slash between the server name and share name + */ + while ( * ++ p ) + if ( ISSLASH(*p) ) + break ; + + if ( *p && p[1] ) + { + /* + * is there a further slash? + */ + while ( * ++ p ) + if ( ISSLASH(*p) ) + break ; + + /* + * just final slash (or no final slash) + */ + if ( !*p || !p[1]) + return 1; + } + } + + return 0 ; +} + +/* + * Extracts the root part of an absolute filename (by modifying the string + * pointed to by the name argument). The name can start + * with either a driver letter (for example, C:\), or a UNC name + * (for example, \\guinness\bjorn). + * + * If the name is invalid, the buffer will be modified to point to + * an empty string. + * + * Returns: 1 if the name consists of just the root part, 0 if + * the name was longer. + */ + +static int +extract_root(char* name) +{ + int len = strlen(name); + + if (isalpha(name[0]) && name[1] == ':' && ISSLASH(name[2])) { + int c = name[3]; + name[3] = '\0'; + return c == '\0'; + } else if (len < 5 || !ISSLASH(name[0]) || !ISSLASH(name[1])) { + goto error; + } else { /* Try to find the end of the UNC name. */ + char* p; + int c; + + /* + * Find the slash between the server name and share name. + */ + + for (p = name + 2; *p; p++) + if (ISSLASH(*p)) + break; + if (*p == '\0') + goto error; + + /* + * Find the slash after the share name. + */ + + for (p++; *p; p++) + if (ISSLASH(*p)) + break; + c = *p; + *p = '\0'; + return c == '\0' || p[1] == '\0'; + } + + error: + *name = '\0'; + return 1; +} + +static unsigned short +dos_to_posix_mode(int attr, const char *name) +{ + register unsigned short uxmode; + unsigned dosmode; + register const char *p; + + dosmode = attr & 0xff; + if ((p = name)[1] == ':') + p += 2; + + /* check to see if this is a directory - note we must make a special + * check for the root, which DOS thinks is not a directory + */ + + uxmode = (unsigned short) + (((ISSLASH(*p) && !p[1]) || (dosmode & FILE_ATTRIBUTE_DIRECTORY) || + *p == '\0') ? _S_IFDIR|_S_IEXEC : _S_IFREG); + + /* If attribute byte does not have read-only bit, it is read-write */ + + uxmode |= (dosmode & FILE_ATTRIBUTE_READONLY) ? + _S_IREAD : (_S_IREAD|_S_IWRITE); + + /* see if file appears to be executable - check extension of name */ + + if (p = strrchr(name, '.')) { + if (!stricmp(p, ".exe") || + !stricmp(p, ".cmd") || + !stricmp(p, ".bat") || + !stricmp(p, ".com")) + uxmode |= _S_IEXEC; + } + + /* propagate user read/write/execute bits to group/other fields */ + + uxmode |= (uxmode & 0700) >> 3; + uxmode |= (uxmode & 0700) >> 6; + + return uxmode; +} + +int +efile_readlink(Efile_error* errInfo, char* name, char* buffer, size_t size) +{ + errno = ENOTSUP; + return check_error(-1, errInfo); +} + + +int +efile_altname(Efile_error* errInfo, char* orig_name, char* buffer, size_t size) +{ + WIN32_FIND_DATA wfd; + HANDLE fh; + char name[_MAX_PATH]; + int name_len; + char* path; + char pathbuf[_MAX_PATH]; + int drive; /* Drive for filename (1 = A:, 2 = B: etc). */ + + /* Don't allow wildcards to be interpreted by system */ + + if (strpbrk(orig_name, "?*")) { + enoent: + errInfo->posix_errno = ENOENT; + errInfo->os_errno = ERROR_FILE_NOT_FOUND; + return 0; + } + + /* + * Move the name to a buffer and make sure to remove a trailing + * slash, because it causes FindFirstFile() to fail on Win95. + */ + + if ((name_len = strlen(orig_name)) >= _MAX_PATH) { + goto enoent; + } else { + strcpy(name, orig_name); + if (name_len > 2 && ISSLASH(name[name_len-1]) && + name[name_len-2] != ':') { + name[name_len-1] = '\0'; + } + } + + /* Try to get disk from name. If none, get current disk. */ + + if (name[1] != ':') { + drive = 0; + if (GetCurrentDirectory(sizeof(pathbuf), pathbuf) && + pathbuf[1] == ':') { + drive = tolower(pathbuf[0]) - 'a' + 1; + } + } else if (*name && name[2] == '\0') { + /* + * X: and nothing more is an error. + */ + goto enoent; + } else { + drive = tolower(*name) - 'a' + 1; + } + fh = FindFirstFile(name,&wfd); + if (fh == INVALID_HANDLE_VALUE) { + if (!(strpbrk(name, "./\\") && + (path = _fullpath(pathbuf, name, _MAX_PATH)) && + /* root dir. ('C:\') or UNC root dir. ('\\server\share\') */ + ((strlen(path) == 3) || IsRootUNCName(path)) && + (GetDriveType(path) > 1) ) ) { + errno = errno_map(GetLastError()); + return check_error(-1, errInfo); + } + /* + * Root directories (such as C:\ or \\server\share\ are fabricated. + */ + strcpy(buffer,name); + return 1; + } + + strcpy(buffer,wfd.cAlternateFileName); + if (!*buffer) { + strcpy(buffer,wfd.cFileName); + } + + return 1; +} + +int +efile_link(Efile_error* errInfo, char* old, char* new) +{ + errno = ENOTSUP; + return check_error(-1, errInfo); +} + +int +efile_symlink(Efile_error* errInfo, char* old, char* new) +{ + errno = ENOTSUP; + return check_error(-1, errInfo); +} |