/* ** Copyright (c) 2006 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the Simplified BSD License (also ** known as the "2-Clause License" or "FreeBSD License".) ** 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. ** ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** ** This file implements several non-trivial file handling wrapper functions ** on Windows using the Win32 API. */ #include "config.h" #ifdef _WIN32 /* This code is for win32 only */ #include #include #include #include "winfile.h" #ifndef LABEL_SECURITY_INFORMATION # define LABEL_SECURITY_INFORMATION (0x00000010L) #endif /* a couple defines to make the borrowed struct below compile */ #define _ANONYMOUS_UNION #define DUMMYUNIONNAME /* ** this structure copied on 20 Sept 2014 from ** https://reactos-mirror.googlecode.com/svn-history/r54752/branches/usb-bringup/include/ddk/ntifs.h ** which is a public domain file from the ReactOS DDK package. */ typedef struct _REPARSE_DATA_BUFFER { ULONG ReparseTag; USHORT ReparseDataLength; USHORT Reserved; _ANONYMOUS_UNION union { struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; ULONG Flags; WCHAR PathBuffer[1]; } SymbolicLinkReparseBuffer; struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; WCHAR PathBuffer[1]; } MountPointReparseBuffer; struct { UCHAR DataBuffer[1]; } GenericReparseBuffer; } DUMMYUNIONNAME; } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; #define LINK_BUFFER_SIZE 1024 /* ** Fill stat buf with information received from GetFileAttributesExW(). ** Does not follow symbolic links, returning instead information about ** the link itself. ** Returns 0 on success, 1 on failure. */ int win32_lstat(const wchar_t *zFilename, struct fossilStat *buf){ WIN32_FILE_ATTRIBUTE_DATA attr; int rc = GetFileAttributesExW(zFilename, GetFileExInfoStandard, &attr); if( rc ){ ssize_t tlen = 0; /* assume it is not a symbolic link */ /* if it is a reparse point it *might* be a symbolic link */ /* so defer to win32_readlink to actually check */ if (attr.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT){ char *tname = fossil_filename_to_utf8(zFilename); char tlink[LINK_BUFFER_SIZE]; tlen = win32_readlink(tname, tlink, sizeof(tlink)); fossil_filename_free(tname); } ULARGE_INTEGER ull; /* if a link was retrieved, it is a symlink, otherwise a dir or file */ buf->st_mode = (tlen > 0) ? S_IFLNK : ((attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? S_IFDIR : S_IFREG); buf->st_size = (((i64)attr.nFileSizeHigh)<<32) | attr.nFileSizeLow; ull.LowPart = attr.ftLastWriteTime.dwLowDateTime; ull.HighPart = attr.ftLastWriteTime.dwHighDateTime; buf->st_mtime = ull.QuadPart / 10000000ULL - 11644473600ULL; } return !rc; } /* ** Fill stat buf with information received from win32_lstat(). ** If a symbolic link is found, follow it and return information about ** the target, repeating until an actual target is found. ** Limit the number of loop iterations so as to avoid an infinite loop ** due to circular links. This should never happen because ** GetFinalPathNameByHandleW() should always preclude that need, but being ** prepared to loop seems prudent, or at least not harmful. ** Returns 0 on success, 1 on failure. */ int win32_stat(const wchar_t *zFilename, struct fossilStat *buf){ int rc; HANDLE file; wchar_t nextFilename[LINK_BUFFER_SIZE]; DWORD len; int iterationsRemaining = 8; /* 8 is arbitrary, can be modified as needed */ while (iterationsRemaining-- > 0){ rc = win32_lstat(zFilename, buf); /* exit on error or not link */ if ((rc != 0) || (buf->st_mode != S_IFLNK)) break; /* it is a link, so open the linked file */ file = CreateFileW(zFilename, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); if ((file == NULL) || (file == INVALID_HANDLE_VALUE)){ rc = 1; break; } /* get the final path name and close the handle */ len = GetFinalPathNameByHandleW(file, nextFilename, LINK_BUFFER_SIZE - 1, 0); CloseHandle(file); /* if any problems getting the final path name error so exit */ if ((len <= 0) || (len > LINK_BUFFER_SIZE - 1)){ rc = 1; break; } /* prepare to try again just in case we have a chain to follow */ /* this shouldn't happen, but just trying to be safe */ zFilename = nextFilename; } return rc; } /* ** An implementation of a posix-like readlink function for win32. ** Copies the target of a symbolic link to buf if possible. ** Returns the length of the link copied to buf on success, -1 on failure. */ ssize_t win32_readlink(const char *path, char *buf, size_t bufsiz){ /* assume we're going to fail */ ssize_t rv = -1; /* does path reference a reparse point? */ WIN32_FILE_ATTRIBUTE_DATA attr; int rc = GetFileAttributesEx(path, GetFileExInfoStandard, &attr); if (rc && (attr.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)){ /* since it is a reparse point, open it */ HANDLE file = CreateFile(path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT, NULL); if ((file != NULL) && (file != INVALID_HANDLE_VALUE)){ /* use DeviceIoControl to get the reparse point data */ int data_size = sizeof(REPARSE_DATA_BUFFER) + LINK_BUFFER_SIZE * sizeof(wchar_t); REPARSE_DATA_BUFFER* data = fossil_malloc(data_size); DWORD data_used; data->ReparseTag = IO_REPARSE_TAG_SYMLINK; data->ReparseDataLength = 0; data->Reserved = 0; int rc = DeviceIoControl(file, FSCTL_GET_REPARSE_POINT, NULL, 0, data, data_size, &data_used, NULL); /* did the reparse point data fit into the desired buffer? */ if (rc && (data_used < data_size)){ /* it fit, so setup the print name for further processing */ USHORT offset = data->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(wchar_t), length = data->SymbolicLinkReparseBuffer.PrintNameLength / sizeof(wchar_t); char *temp; data->SymbolicLinkReparseBuffer.PathBuffer[offset + length] = 0; /* convert the filename to utf8, copy it, and discard the converted copy */ temp = fossil_filename_to_utf8(data->SymbolicLinkReparseBuffer.PathBuffer + offset); rv = strlen(temp); if (rv >= bufsiz) rv = bufsiz; memcpy(buf, temp, rv); fossil_filename_free(temp); } fossil_free(data); /* all done, close the reparse point */ CloseHandle(file); } } return rv; } /* ** Either unlink a file or remove a directory on win32 systems. ** To delete a symlink on a posix system, you simply unlink the entry. ** Unfortunately for our purposes, win32 differentiates between symlinks for ** files and for directories. Thus you must unlink a file symlink or rmdir a ** directory symlink. This is a convenience function used when we know we're ** deleting a symlink of some type. ** Returns 0 on success, 1 on failure. */ int win32_unlink_rmdir(const wchar_t *zFilename){ int rc = 0; fossilStat stat; if (win32_stat(zFilename, &stat) == 0){ if (stat.st_mode == S_IFDIR) rc = RemoveDirectoryW(zFilename); else rc = DeleteFileW(zFilename); } return !rc; } /* ** An implementation of a posix-like symlink function for win32. ** Attempts to create a file or directory symlink based on the target. ** Defaults to a file symlink if the target does not exist / can't be checked. ** Finally, if the symlink cannot be created for whatever reason (perhaps ** newpath is on a network share or a FAT derived file system), default to ** creation of a text file with the context of the link. ** Returns 0 on success, 1 on failure. */ int win32_symlink(const char *oldpath, const char *newpath){ fossilStat stat; int created = 0; DWORD flags = 0; wchar_t *zMbcs; /* does oldpath exist? is it a dir or a file? */ zMbcs = fossil_utf8_to_filename(oldpath); if (win32_stat(zMbcs, &stat) == 0){ if (stat.st_mode == S_IFDIR) flags = SYMBOLIC_LINK_FLAG_DIRECTORY; } fossil_filename_free(zMbcs); /* remove newpath before creating the symlink */ zMbcs = fossil_utf8_to_filename(newpath); win32_unlink_rmdir(zMbcs); fossil_filename_free(zMbcs); created = CreateSymbolicLink(newpath, oldpath, flags); /* if the symlink was not created, create a plain text file */ if (!created){ Blob content; blob_set(&content, oldpath); blob_write_to_file(&content, newpath); blob_reset(&content); created = 1; } return !created; } /* ** Check if symlinks are potentially supported on the current OS. ** Theoretically this code should work on any NT based version of windows ** but I have no way of testing that. The initial check for ** IsWindowsVistaOrGreater() should in theory eliminate any system prior to ** Windows Vista, but I have no way to test that at this time. ** Return 1 if supported, 0 if not. */ int win32_symlinks_supported(){ TOKEN_PRIVILEGES tp; LUID luid; HANDLE process, token; DWORD status; /* symlinks only supported on vista or greater */ if (!IsWindowsVistaOrGreater()) return 0; /* next we need to check to see if the privilege is available */ /* can't check privilege if we can't lookup its value */ if (!LookupPrivilegeValue(NULL, SE_CREATE_SYMBOLIC_LINK_NAME, &luid)) return 0; /* can't check privilege if we can't open the process token */ process = GetCurrentProcess(); if (!OpenProcessToken(process, TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &token)) return 0; /* by this point, we have a process token and the privilege value */ /* try to enable the privilege then close the token */ tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; AdjustTokenPrivileges(token, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL); status = GetLastError(); CloseHandle(token); /* any error means we failed to enable the privilege, symlinks not supported */ if (status != ERROR_SUCCESS) return 0; /* we made it this far, symlinks must be supported */ return 1; } /* ** Wrapper around the access() system call. This code was copied from Tcl ** 8.6 and then modified. */ int win32_access(const wchar_t *zFilename, int flags){ int rc = 0; PSECURITY_DESCRIPTOR pSd = NULL; unsigned long size = 0; PSID pSid = NULL; BOOL sidDefaulted; BOOL impersonated = FALSE; SID_IDENTIFIER_AUTHORITY unmapped = {{0, 0, 0, 0, 0, 22}}; GENERIC_MAPPING genMap; HANDLE hToken = NULL; DWORD desiredAccess = 0, grantedAccess = 0; BOOL accessYesNo = FALSE; PPRIVILEGE_SET pPrivSet = NULL; DWORD privSetSize = 0; DWORD attr = GetFileAttributesW(zFilename); if( attr==INVALID_FILE_ATTRIBUTES ){ /* * File might not exist. */ if( GetLastError()!=ERROR_SHARING_VIOLATION ){ rc = -1; goto done; } } if( flags==F_OK ){ /* * File exists, nothing else to check. */ goto done; } if( (flags & W_OK) && (attr & FILE_ATTRIBUTE_READONLY) && !(attr & FILE_ATTRIBUTE_DIRECTORY) ){ /* * The attributes say the file is not writable. If the file is a * regular file (i.e., not a directory), then the file is not * writable, full stop. For directories, the read-only bit is * (mostly) ignored by Windows, so we can't ascertain anything about * directory access from the attrib data. */ rc = -1; goto done; } /* * It looks as if the permissions are ok, but if we are on NT, 2000 or XP, * we have a more complex permissions structure so we try to check that. * The code below is remarkably complex for such a simple thing as finding * what permissions the OS has set for a file. */ /* * First find out how big the buffer needs to be. */ GetFileSecurityW(zFilename, OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION, 0, 0, &size); /* * Should have failed with ERROR_INSUFFICIENT_BUFFER */ if( GetLastError()!=ERROR_INSUFFICIENT_BUFFER ){ /* * Most likely case is ERROR_ACCESS_DENIED, which we will convert to * EACCES - just what we want! */ rc = -1; goto done; } /* * Now size contains the size of buffer needed. */ pSd = (PSECURITY_DESCRIPTOR)HeapAlloc(GetProcessHeap(), 0, size); if( pSd==NULL ){ rc = -1; goto done; } /* * Call GetFileSecurity() for real. */ if( !GetFileSecurityW(zFilename, OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION, pSd, size, &size) ){ /* * Error getting owner SD */ rc = -1; goto done; } /* * As of Samba 3.0.23 (10-Jul-2006), unmapped users and groups are * assigned to SID domains S-1-22-1 and S-1-22-2, where "22" is the * top-level authority. If the file owner and group is unmapped then * the ACL access check below will only test against world access, * which is likely to be more restrictive than the actual access * restrictions. Since the ACL tests are more likely wrong than * right, skip them. Moreover, the unix owner access permissions are * usually mapped to the Windows attributes, so if the user is the * file owner then the attrib checks above are correct (as far as they * go). */ if( !GetSecurityDescriptorOwner(pSd, &pSid, &sidDefaulted) || memcmp(GetSidIdentifierAuthority(pSid), &unmapped, sizeof(SID_IDENTIFIER_AUTHORITY))==0 ){ goto done; /* Attrib tests say access allowed. */ } /* * Perform security impersonation of the user and open the resulting * thread token. */ if( !ImpersonateSelf(SecurityImpersonation) ){ /* * Unable to perform security impersonation. */ rc = -1; goto done; } impersonated = TRUE; if( !OpenThreadToken(GetCurrentThread(), TOKEN_DUPLICATE | TOKEN_QUERY, FALSE, &hToken) ){ /* * Unable to get current thread's token. */ rc = -1; goto done; } /* * Setup desiredAccess according to the access priveleges we are * checking. */ if( flags & R_OK ){ desiredAccess |= FILE_GENERIC_READ; } if( flags & W_OK){ desiredAccess |= FILE_GENERIC_WRITE; } memset(&genMap, 0, sizeof(GENERIC_MAPPING)); genMap.GenericRead = FILE_GENERIC_READ; genMap.GenericWrite = FILE_GENERIC_WRITE; genMap.GenericExecute = FILE_GENERIC_EXECUTE; genMap.GenericAll = FILE_ALL_ACCESS; AccessCheck(pSd, hToken, desiredAccess, &genMap, 0, &privSetSize, &grantedAccess, &accessYesNo); /* * Should have failed with ERROR_INSUFFICIENT_BUFFER */ if( GetLastError()!=ERROR_INSUFFICIENT_BUFFER ){ rc = -1; goto done; } pPrivSet = (PPRIVILEGE_SET)HeapAlloc(GetProcessHeap(), 0, privSetSize); if( pPrivSet==NULL ){ rc = -1; goto done; } /* * Perform access check using the token. */ if( !AccessCheck(pSd, hToken, desiredAccess, &genMap, pPrivSet, &privSetSize, &grantedAccess, &accessYesNo) ){ /* * Unable to perform access check. */ rc = -1; goto done; } if( !accessYesNo ) rc = -1; done: if( hToken != NULL ){ CloseHandle(hToken); } if( impersonated ){ RevertToSelf(); impersonated = FALSE; } if( pPrivSet!=NULL ){ HeapFree(GetProcessHeap(), 0, pPrivSet); } if( pSd!=NULL ){ HeapFree(GetProcessHeap(), 0, pSd); } return rc; } /* ** Wrapper around the chdir() system call. */ int win32_chdir(const wchar_t *zChDir, int bChroot){ int rc = (int)!SetCurrentDirectoryW(zChDir); return rc; } /* ** Get the current working directory. ** ** On windows, the name is converted from unicode to UTF8 and all '\\' ** characters are converted to '/'. */ void win32_getcwd(char *zBuf, int nBuf){ int i; char *zUtf8; wchar_t *zWide = fossil_malloc( sizeof(wchar_t)*nBuf ); if( GetCurrentDirectoryW(nBuf, zWide)==0 ){ fossil_fatal("cannot find current working directory."); } zUtf8 = fossil_filename_to_utf8(zWide); fossil_free(zWide); for(i=0; zUtf8[i]; i++) if( zUtf8[i]=='\\' ) zUtf8[i] = '/'; strncpy(zBuf, zUtf8, nBuf); fossil_filename_free(zUtf8); } #endif /* _WIN32 -- This code is for win32 only */