Artifact c2a222bc2259588701d045b165f702c65283d24e996eb89218205d15b204dbe6:
- File
src/OFFileManager.m
— part of check-in
[06bcb21fc7]
at
2017-01-09 06:26:04
on branch trunk
— Add OFLocalization
This singleton gives access to all things locale, including the ability
to get localized strings.This also adds the OF_LOCALIZED() macro. Its first argument is an ID for
the string to be localized and its second argument is the fallback
string to be used if it cannot retrieve the localized string. Following
that are variable name / value pairs to be replaced in the localized
string.Getting translated strings is not implemented yet: Instead, it always
uses the fallback string.This also switches ofhttp to localized strings. (user: js, size: 25129) [annotate] [blame] [check-ins using]
/* * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 * Jonathan Schleifer <js@heap.zone> * * All rights reserved. * * This file is part of ObjFW. It may be distributed under the terms of the * Q Public License 1.0, which can be found in the file LICENSE.QPL included in * the packaging of this file. * * Alternatively, it may be distributed under the terms of the GNU General * Public License, either version 2 or 3, which can be found in the file * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ #include "config.h" #include <errno.h> #include <dirent.h> #include <unistd.h> #ifdef HAVE_PWD_H # include <pwd.h> #endif #ifdef HAVE_GRP_H # include <grp.h> #endif #import "OFFileManager.h" #import "OFFile.h" #import "OFString.h" #import "OFArray.h" #import "OFDate.h" #import "OFSystemInfo.h" #import "OFLocalization.h" #ifdef OF_HAVE_THREADS # import "OFMutex.h" #endif #import "OFChangeCurrentDirectoryPathFailedException.h" #import "OFChangeOwnerFailedException.h" #import "OFChangePermissionsFailedException.h" #import "OFCopyItemFailedException.h" #import "OFCreateDirectoryFailedException.h" #import "OFCreateSymbolicLinkFailedException.h" #import "OFInitializationFailedException.h" #import "OFInvalidArgumentException.h" #import "OFLinkFailedException.h" #import "OFLockFailedException.h" #import "OFMoveItemFailedException.h" #import "OFNotImplementedException.h" #import "OFOpenItemFailedException.h" #import "OFOutOfMemoryException.h" #import "OFReadFailedException.h" #import "OFRemoveItemFailedException.h" #import "OFStatItemFailedException.h" #import "OFUnlockFailedException.h" #ifdef OF_WINDOWS # include <windows.h> # include <direct.h> # include <ntdef.h> #endif #ifndef S_IRGRP # define S_IRGRP 0 #endif #ifndef S_IROTH # define S_IROTH 0 #endif #ifndef S_IWGRP # define S_IWGRP 0 #endif #ifndef S_IWOTH # define S_IWOTH 0 #endif #define DEFAULT_MODE S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH #define DIR_MODE DEFAULT_MODE | S_IXUSR | S_IXGRP | S_IXOTH static OFFileManager *defaultManager; #if defined(OF_HAVE_CHOWN) && defined(OF_HAVE_THREADS) static OFMutex *passwdMutex; #endif #if !defined(HAVE_READDIR_R) && !defined(OF_WINDOWS) && defined(OF_HAVE_THREADS) static OFMutex *readdirMutex; #endif #ifdef OF_WINDOWS static WINAPI BOOLEAN (*func_CreateSymbolicLinkW)(LPCWSTR, LPCWSTR, DWORD); #endif int of_stat(OFString *path, of_stat_t *buffer) { #if defined(OF_WINDOWS) return _wstat64([path UTF16String], buffer); #elif defined(OF_HAVE_OFF64_T) return stat64([path cStringWithEncoding: [OFLocalization encoding]], buffer); #else return stat([path cStringWithEncoding: [OFLocalization encoding]], buffer); #endif } int of_lstat(OFString *path, of_stat_t *buffer) { #if defined(OF_WINDOWS) return _wstat64([path UTF16String], buffer); #elif defined(HAVE_LSTAT) # ifdef OF_HAVE_OFF64_T return lstat64([path cStringWithEncoding: [OFLocalization encoding]], buffer); # else return lstat([path cStringWithEncoding: [OFLocalization encoding]], buffer); # endif #else # ifdef OF_HAVE_OFF64_T return stat64([path cStringWithEncoding: [OFLocalization encoding]], buffer); # else return stat([path cStringWithEncoding: [OFLocalization encoding]], buffer); # endif #endif } @implementation OFFileManager + (void)initialize { #ifdef OF_WINDOWS HMODULE module; #endif if (self != [OFFileManager class]) return; /* * Make sure OFFile is initialized. * On some systems, this is needed to initialize the file system driver. */ [OFFile class]; #if defined(OF_HAVE_CHOWN) && defined(OF_HAVE_THREADS) passwdMutex = [[OFMutex alloc] init]; #endif #if !defined(HAVE_READDIR_R) && !defined(OF_WINDOWS) && defined(OF_HAVE_THREADS) readdirMutex = [[OFMutex alloc] init]; #endif #ifdef OF_WINDOWS if ((module = LoadLibrary("kernel32.dll")) != NULL) func_CreateSymbolicLinkW = (WINAPI BOOLEAN (*)(LPCWSTR, LPCWSTR, DWORD)) GetProcAddress(module, "CreateSymbolicLinkW"); #endif defaultManager = [[OFFileManager alloc] init]; } + (OFFileManager*)defaultManager { return defaultManager; } - (OFString*)currentDirectoryPath { OFString *ret; #ifndef OF_WINDOWS char *buffer = getcwd(NULL, 0); #else wchar_t *buffer = _wgetcwd(NULL, 0); #endif @try { #ifndef OF_WINDOWS ret = [OFString stringWithCString: buffer encoding: [OFLocalization encoding]]; #else ret = [OFString stringWithUTF16String: buffer]; #endif } @finally { free(buffer); } return ret; } - (bool)fileExistsAtPath: (OFString*)path { of_stat_t s; if (path == nil) @throw [OFInvalidArgumentException exception]; if (of_stat(path, &s) == -1) return false; if (S_ISREG(s.st_mode)) return true; return false; } - (bool)directoryExistsAtPath: (OFString*)path { of_stat_t s; if (path == nil) @throw [OFInvalidArgumentException exception]; if (of_stat(path, &s) == -1) return false; if (S_ISDIR(s.st_mode)) return true; return false; } #if defined(OF_HAVE_SYMLINK) - (bool)symbolicLinkExistsAtPath: (OFString*)path { of_stat_t s; if (path == nil) @throw [OFInvalidArgumentException exception]; if (of_lstat(path, &s) == -1) return false; if (S_ISLNK(s.st_mode)) return true; return false; } #elif defined(OF_WINDOWS) - (bool)symbolicLinkExistsAtPath: (OFString*)path { WIN32_FIND_DATAW data; if (!FindFirstFileW([path UTF16String], &data)) return false; if ((data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) && data.dwReserved0 == IO_REPARSE_TAG_SYMLINK) return true; return false; } #endif - (void)createDirectoryAtPath: (OFString*)path { if (path == nil) @throw [OFInvalidArgumentException exception]; #ifndef OF_WINDOWS if (mkdir([path cStringWithEncoding: [OFLocalization encoding]], DIR_MODE) != 0) #else if (_wmkdir([path UTF16String]) != 0) #endif @throw [OFCreateDirectoryFailedException exceptionWithPath: path errNo: errno]; } - (void)createDirectoryAtPath: (OFString*)path createParents: (bool)createParents { OFString *currentPath = nil; if (!createParents) { [self createDirectoryAtPath: path]; return; } if (path == nil) @throw [OFInvalidArgumentException exception]; for (OFString *component in [path pathComponents]) { void *pool = objc_autoreleasePoolPush(); if (currentPath != nil) currentPath = [currentPath stringByAppendingPathComponent: component]; else currentPath = component; if ([currentPath length] > 0 && ![self directoryExistsAtPath: currentPath]) [self createDirectoryAtPath: currentPath]; [currentPath retain]; objc_autoreleasePoolPop(pool); [currentPath autorelease]; } } - (OFArray*)contentsOfDirectoryAtPath: (OFString*)path { OFMutableArray *files; #ifndef OF_WINDOWS of_string_encoding_t encoding; #endif if (path == nil) @throw [OFInvalidArgumentException exception]; files = [OFMutableArray array]; #ifndef OF_WINDOWS DIR *dir; encoding = [OFLocalization encoding]; if ((dir = opendir([path cStringWithEncoding: encoding])) == NULL) @throw [OFOpenItemFailedException exceptionWithPath: path errNo: errno]; # if !defined(HAVE_READDIR_R) && defined(OF_HAVE_THREADS) [readdirMutex lock]; # endif @try { for (;;) { struct dirent *dirent; # ifdef HAVE_READDIR_R struct dirent buffer; # endif void *pool; OFString *file; # ifdef HAVE_READDIR_R if (readdir_r(dir, &buffer, &dirent) != 0) @throw [OFReadFailedException exceptionWithObject: self requestedLength: 0 errNo: errno]; if (dirent == NULL) break; # else if ((dirent = readdir(dir)) == NULL) { if (errno == 0) break; else @throw [OFReadFailedException exceptionWithObject: self requestedLength: 0 errNo: errno]; } # endif if (strcmp(dirent->d_name, ".") == 0 || strcmp(dirent->d_name, "..") == 0) continue; pool = objc_autoreleasePoolPush(); file = [OFString stringWithCString: dirent->d_name encoding: encoding]; [files addObject: file]; objc_autoreleasePoolPop(pool); } } @finally { closedir(dir); # if !defined(HAVE_READDIR_R) && defined(OF_HAVE_THREADS) [readdirMutex unlock]; # endif } #else void *pool = objc_autoreleasePoolPush(); HANDLE handle; WIN32_FIND_DATAW fd; path = [path stringByAppendingString: @"\\*"]; if ((handle = FindFirstFileW([path UTF16String], &fd)) == INVALID_HANDLE_VALUE) { int errNo = 0; if (GetLastError() == ERROR_FILE_NOT_FOUND) errNo = ENOENT; @throw [OFOpenItemFailedException exceptionWithPath: path errNo: errNo]; } @try { do { void *pool2 = objc_autoreleasePoolPush(); OFString *file; if (!wcscmp(fd.cFileName, L".") || !wcscmp(fd.cFileName, L"..")) continue; file = [OFString stringWithUTF16String: fd.cFileName]; [files addObject: file]; objc_autoreleasePoolPop(pool2); } while (FindNextFileW(handle, &fd)); if (GetLastError() != ERROR_NO_MORE_FILES) @throw [OFReadFailedException exceptionWithObject: self requestedLength: 0]; } @finally { FindClose(handle); } objc_autoreleasePoolPop(pool); #endif [files makeImmutable]; return files; } - (void)changeCurrentDirectoryPath: (OFString*)path { if (path == nil) @throw [OFInvalidArgumentException exception]; #ifndef OF_WINDOWS if (chdir([path cStringWithEncoding: [OFLocalization encoding]]) != 0) #else if (_wchdir([path UTF16String]) != 0) #endif @throw [OFChangeCurrentDirectoryPathFailedException exceptionWithPath: path errNo: errno]; } - (of_offset_t)sizeOfFileAtPath: (OFString*)path { of_stat_t s; if (path == nil) @throw [OFInvalidArgumentException exception]; if (of_stat(path, &s) != 0) @throw [OFStatItemFailedException exceptionWithPath: path errNo: errno]; return s.st_size; } - (OFDate*)accessTimeOfItemAtPath: (OFString*)path { of_stat_t s; if (path == nil) @throw [OFInvalidArgumentException exception]; if (of_stat(path, &s) != 0) @throw [OFStatItemFailedException exceptionWithPath: path errNo: errno]; /* FIXME: We could be more precise on some OSes */ return [OFDate dateWithTimeIntervalSince1970: s.st_atime]; } - (OFDate*)modificationTimeOfItemAtPath: (OFString*)path { of_stat_t s; if (path == nil) @throw [OFInvalidArgumentException exception]; if (of_stat(path, &s) != 0) @throw [OFStatItemFailedException exceptionWithPath: path errNo: errno]; /* FIXME: We could be more precise on some OSes */ return [OFDate dateWithTimeIntervalSince1970: s.st_mtime]; } - (OFDate*)statusChangeTimeOfItemAtPath: (OFString*)path { of_stat_t s; if (path == nil) @throw [OFInvalidArgumentException exception]; if (of_stat(path, &s) != 0) @throw [OFStatItemFailedException exceptionWithPath: path errNo: errno]; /* FIXME: We could be more precise on some OSes */ return [OFDate dateWithTimeIntervalSince1970: s.st_ctime]; } #ifdef OF_HAVE_CHMOD - (mode_t)permissionsOfItemAtPath: (OFString*)path { of_stat_t s; if (path == nil) @throw [OFInvalidArgumentException exception]; if (of_stat(path, &s) != 0) @throw [OFStatItemFailedException exceptionWithPath: path errNo: errno]; return s.st_mode; } - (void)changePermissionsOfItemAtPath: (OFString*)path permissions: (mode_t)permissions { if (path == nil) @throw [OFInvalidArgumentException exception]; # ifndef OF_WINDOWS if (chmod([path cStringWithEncoding: [OFLocalization encoding]], permissions) != 0) # else if (_wchmod([path UTF16String], permissions) != 0) # endif @throw [OFChangePermissionsFailedException exceptionWithPath: path permissions: permissions errNo: errno]; } #endif #ifdef OF_HAVE_CHOWN - (void)getOwner: (OFString**)owner group: (OFString**)group ofItemAtPath: (OFString*)path { of_stat_t s; if (path == nil) @throw [OFInvalidArgumentException exception]; if (of_stat(path, &s) != 0) @throw [OFStatItemFailedException exceptionWithPath: path errNo: errno]; # ifdef OF_HAVE_THREADS [passwdMutex lock]; @try { # endif of_string_encoding_t encoding = [OFLocalization encoding]; if (owner != NULL) { struct passwd *passwd = getpwuid(s.st_uid); *owner = [OFString stringWithCString: passwd->pw_name encoding: encoding]; } if (group != NULL) { struct group *group_ = getgrgid(s.st_gid); *group = [OFString stringWithCString: group_->gr_name encoding: encoding]; } # ifdef OF_HAVE_THREADS } @finally { [passwdMutex unlock]; } #endif } - (void)changeOwnerOfItemAtPath: (OFString*)path owner: (OFString*)owner group: (OFString*)group { uid_t uid = -1; gid_t gid = -1; of_string_encoding_t encoding; if (path == nil || (owner == nil && group == nil)) @throw [OFInvalidArgumentException exception]; encoding = [OFLocalization encoding]; # ifdef OF_HAVE_THREADS [passwdMutex lock]; @try { # endif if (owner != nil) { struct passwd *passwd; if ((passwd = getpwnam([owner cStringWithEncoding: encoding])) == NULL) @throw [OFChangeOwnerFailedException exceptionWithPath: path owner: owner group: group errNo: errno]; uid = passwd->pw_uid; } if (group != nil) { struct group *group_; if ((group_ = getgrnam([group cStringWithEncoding: encoding])) == NULL) @throw [OFChangeOwnerFailedException exceptionWithPath: path owner: owner group: group errNo: errno]; gid = group_->gr_gid; } # ifdef OF_HAVE_THREADS } @finally { [passwdMutex unlock]; } # endif if (chown([path cStringWithEncoding: encoding], uid, gid) != 0) @throw [OFChangeOwnerFailedException exceptionWithPath: path owner: owner group: group errNo: errno]; } #endif - (void)copyItemAtPath: (OFString*)source toPath: (OFString*)destination { void *pool; of_stat_t s; if (source == nil || destination == nil) @throw [OFInvalidArgumentException exception]; pool = objc_autoreleasePoolPush(); if (of_lstat(destination, &s) == 0) @throw [OFCopyItemFailedException exceptionWithSourcePath: source destinationPath: destination errNo: EEXIST]; if (of_lstat(source, &s) != 0) @throw [OFCopyItemFailedException exceptionWithSourcePath: source destinationPath: destination errNo: errno]; if (S_ISDIR(s.st_mode)) { OFArray *contents; @try { [self createDirectoryAtPath: destination]; #ifdef OF_HAVE_CHMOD [self changePermissionsOfItemAtPath: destination permissions: s.st_mode]; #endif contents = [self contentsOfDirectoryAtPath: source]; } @catch (id e) { /* * Only convert exceptions to OFCopyItemFailedException * that have an errNo property. This covers all I/O * related exceptions from the operations used to copy * an item, all others should be left as is. */ if ([e respondsToSelector: @selector(errNo)]) @throw [OFCopyItemFailedException exceptionWithSourcePath: source destinationPath: destination errNo: [e errNo]]; @throw e; } for (OFString *item in contents) { void *pool2 = objc_autoreleasePoolPush(); OFString *sourcePath, *destinationPath; sourcePath = [source stringByAppendingPathComponent: item]; destinationPath = [destination stringByAppendingPathComponent: item]; [self copyItemAtPath: sourcePath toPath: destinationPath]; objc_autoreleasePoolPop(pool2); } } else if (S_ISREG(s.st_mode)) { size_t pageSize = [OFSystemInfo pageSize]; OFFile *sourceFile = nil; OFFile *destinationFile = nil; char *buffer; if ((buffer = malloc(pageSize)) == NULL) @throw [OFOutOfMemoryException exceptionWithRequestedSize: pageSize]; @try { sourceFile = [OFFile fileWithPath: source mode: @"rb"]; destinationFile = [OFFile fileWithPath: destination mode: @"wb"]; while (![sourceFile isAtEndOfStream]) { size_t length; length = [sourceFile readIntoBuffer: buffer length: pageSize]; [destinationFile writeBuffer: buffer length: length]; } #ifdef OF_HAVE_CHMOD [self changePermissionsOfItemAtPath: destination permissions: s.st_mode]; #endif } @catch (id e) { /* * Only convert exceptions to OFCopyItemFailedException * that have an errNo property. This covers all I/O * related exceptions from the operations used to copy * an item, all others should be left as is. */ if ([e respondsToSelector: @selector(errNo)]) @throw [OFCopyItemFailedException exceptionWithSourcePath: source destinationPath: destination errNo: [e errNo]]; @throw e; } @finally { [sourceFile close]; [destinationFile close]; free(buffer); } #ifdef OF_HAVE_SYMLINK } else if (S_ISLNK(s.st_mode)) { @try { source = [self destinationOfSymbolicLinkAtPath: source]; [self createSymbolicLinkAtPath: destination withDestinationPath: source]; } @catch (id e) { /* * Only convert exceptions to OFCopyItemFailedException * that have an errNo property. This covers all I/O * related exceptions from the operations used to copy * an item, all others should be left as is. */ if ([e respondsToSelector: @selector(errNo)]) @throw [OFCopyItemFailedException exceptionWithSourcePath: source destinationPath: destination errNo: [e errNo]]; @throw e; } #endif } else @throw [OFCopyItemFailedException exceptionWithSourcePath: source destinationPath: destination errNo: ENOTSUP]; objc_autoreleasePoolPop(pool); } - (void)moveItemAtPath: (OFString*)source toPath: (OFString*)destination { void *pool; of_stat_t s; #ifndef OF_WINDOWS of_string_encoding_t encoding; #endif if (source == nil || destination == nil) @throw [OFInvalidArgumentException exception]; pool = objc_autoreleasePoolPush(); if (of_lstat(destination, &s) == 0) @throw [OFCopyItemFailedException exceptionWithSourcePath: source destinationPath: destination errNo: EEXIST]; #ifndef OF_WINDOWS encoding = [OFLocalization encoding]; if (rename([source cStringWithEncoding: encoding], [destination cStringWithEncoding: encoding]) != 0) { #else if (_wrename([source UTF16String], [destination UTF16String]) != 0) { #endif if (errno != EXDEV) @throw [OFMoveItemFailedException exceptionWithSourcePath: source destinationPath: destination errNo: errno]; @try { [self copyItemAtPath: source toPath: destination]; } @catch (OFCopyItemFailedException *e) { [self removeItemAtPath: destination]; @throw [OFMoveItemFailedException exceptionWithSourcePath: source destinationPath: destination errNo: [e errNo]]; } @try { [self removeItemAtPath: source]; } @catch (OFRemoveItemFailedException *e) { @throw [OFMoveItemFailedException exceptionWithSourcePath: source destinationPath: destination errNo: [e errNo]]; } } objc_autoreleasePoolPop(pool); } - (void)removeItemAtPath: (OFString*)path { void *pool; of_stat_t s; if (path == nil) @throw [OFInvalidArgumentException exception]; pool = objc_autoreleasePoolPush(); if (of_lstat(path, &s) != 0) @throw [OFRemoveItemFailedException exceptionWithPath: path errNo: errno]; if (S_ISDIR(s.st_mode)) { OFArray *contents; @try { contents = [self contentsOfDirectoryAtPath: path]; } @catch (id e) { /* * Only convert exceptions to * OFRemoveItemFailedException that have an errNo * property. This covers all I/O related exceptions * from the operations used to remove an item, all * others should be left as is. */ if ([e respondsToSelector: @selector(errNo)]) @throw [OFRemoveItemFailedException exceptionWithPath: path errNo: [e errNo]]; @throw e; } for (OFString *item in contents) { void *pool2 = objc_autoreleasePoolPush(); [self removeItemAtPath: [path stringByAppendingPathComponent: item]]; objc_autoreleasePoolPop(pool2); } #ifndef OF_WINDOWS if (rmdir([path cStringWithEncoding: [OFLocalization encoding]]) != 0) #else if (_wrmdir([path UTF16String]) != 0) #endif @throw [OFRemoveItemFailedException exceptionWithPath: path errNo: errno]; } else { #ifndef OF_WINDOWS if (unlink([path cStringWithEncoding: [OFLocalization encoding]]) != 0) #else if (_wunlink([path UTF16String]) != 0) #endif @throw [OFRemoveItemFailedException exceptionWithPath: path errNo: errno]; } objc_autoreleasePoolPop(pool); } #if defined(OF_HAVE_LINK) - (void)linkItemAtPath: (OFString*)source toPath: (OFString*)destination { void *pool; of_string_encoding_t encoding; if (source == nil || destination == nil) @throw [OFInvalidArgumentException exception]; pool = objc_autoreleasePoolPush(); encoding = [OFLocalization encoding]; if (link([source cStringWithEncoding: encoding], [destination cStringWithEncoding: encoding]) != 0) @throw [OFLinkFailedException exceptionWithSourcePath: source destinationPath: destination errNo: errno]; objc_autoreleasePoolPop(pool); } #else - (void)linkItemAtPath: (OFString*)source toPath: (OFString*)destination { void *pool; if (source == nil || destination == nil) @throw [OFInvalidArgumentException exception]; pool = objc_autoreleasePoolPush(); if (!CreateHardLinkW([destination UTF16String], [source UTF16String], NULL)) @throw [OFLinkFailedException exceptionWithSourcePath: source destinationPath: destination]; objc_autoreleasePoolPop(pool); } #endif #if defined(OF_HAVE_SYMLINK) - (void)createSymbolicLinkAtPath: (OFString*)destination withDestinationPath: (OFString*)source { void *pool; of_string_encoding_t encoding; if (source == nil || destination == nil) @throw [OFInvalidArgumentException exception]; pool = objc_autoreleasePoolPush(); encoding = [OFLocalization encoding]; if (symlink([source cStringWithEncoding: encoding], [destination cStringWithEncoding: encoding]) != 0) @throw [OFCreateSymbolicLinkFailedException exceptionWithSourcePath: source destinationPath: destination errNo: errno]; objc_autoreleasePoolPop(pool); } #elif defined(OF_WINDOWS) - (void)createSymbolicLinkAtPath: (OFString*)destination withDestinationPath: (OFString*)source { void *pool; if (func_CreateSymbolicLinkW == NULL) @throw [OFNotImplementedException exceptionWithSelector: _cmd object: self]; if (source == nil || destination == nil) @throw [OFInvalidArgumentException exception]; pool = objc_autoreleasePoolPush(); if (!func_CreateSymbolicLinkW([destination UTF16String], [source UTF16String], 0)) @throw [OFCreateSymbolicLinkFailedException exceptionWithSourcePath: source destinationPath: destination]; objc_autoreleasePoolPop(pool); } #endif #ifdef OF_HAVE_READLINK - (OFString*)destinationOfSymbolicLinkAtPath: (OFString*)path { char destination[PATH_MAX]; ssize_t length; of_string_encoding_t encoding; if (path == nil) @throw [OFInvalidArgumentException exception]; encoding = [OFLocalization encoding]; length = readlink([path cStringWithEncoding: encoding], destination, PATH_MAX); if (length < 0) @throw [OFStatItemFailedException exceptionWithPath: path errNo: errno]; return [OFString stringWithCString: destination encoding: encoding length: length]; } #elif defined(OF_WINDOWS) - (OFString*)destinationOfSymbolicLinkAtPath: (OFString*)path { HANDLE handle; /* Check if we're on a version that actually supports symlinks. */ if (func_CreateSymbolicLinkW == NULL) @throw [OFNotImplementedException exceptionWithSelector: _cmd object: self]; if (path == nil) @throw [OFInvalidArgumentException exception]; if ((handle = CreateFileW([path UTF16String], 0, (FILE_SHARE_READ | FILE_SHARE_WRITE), NULL, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT, NULL)) == INVALID_HANDLE_VALUE) @throw [OFStatItemFailedException exceptionWithPath: path]; @try { union { char bytes[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; REPARSE_DATA_BUFFER data; } buffer; DWORD size; wchar_t *tmp; if (!DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0, buffer.bytes, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &size, NULL)) @throw [OFStatItemFailedException exceptionWithPath: path]; if (buffer.data.ReparseTag != IO_REPARSE_TAG_SYMLINK) @throw [OFStatItemFailedException exceptionWithPath: path]; #define slrb buffer.data.SymbolicLinkReparseBuffer tmp = slrb.PathBuffer + (slrb.SubstituteNameOffset / sizeof(wchar_t)); return [OFString stringWithUTF16String: tmp length: slrb.SubstituteNameLength / sizeof(wchar_t)]; #undef slrb } @finally { CloseHandle(handle); } } #endif @end