Index: configure.ac ================================================================== --- configure.ac +++ configure.ac @@ -806,10 +806,13 @@ AC_DEFINE(OF_HAVE_LINK, 1, [Whether we have link()]) ]) AC_CHECK_FUNC(symlink, [ AC_DEFINE(OF_HAVE_SYMLINK, 1, [Whether we have symlink()]) ]) + AC_CHECK_FUNC(readlink, [ + AC_DEFINE(OF_HAVE_READLINK, 1, [Whether we have readlink()]) + ]) AC_CHECK_FUNCS([lstat readdir_r]) ]) AC_CHECK_FUNCS([sysconf gmtime_r localtime_r nanosleep fcntl]) Index: src/OFFileManager.h ================================================================== --- src/OFFileManager.h +++ src/OFFileManager.h @@ -225,43 +225,48 @@ * * @param path The path to the item which should be removed */ - (void)removeItemAtPath: (OFString*)path; -#ifdef OF_HAVE_LINK +#if defined(OF_HAVE_LINK) || defined(OF_WINDOWS) /*! * @brief Creates a hard link for the specified item. * * The destination path must be a full path, which means it must include the * name of the item. * - * This method is not available on some systems, most notably Windows. + * This method is not available on some systems. * * @param source The path to the item for which a link should be created * @param destination The path to the item which should link to the source */ - (void)linkItemAtPath: (OFString*)source toPath: (OFString*)destination; #endif -#ifdef OF_HAVE_SYMLINK +#if defined(OF_HAVE_SYMLINK) || defined(OF_WINDOWS) /*! * @brief Creates a symbolic link for an item. * * The destination path must be a full path, which means it must include the * name of the item. * - * This method is not available on some systems, most notably Windows. + * This method is not available on some systems. + * + * @note On Windows, this requires at least Windows Vista and administrator + * privileges! * * @param destination The path to the item which should symbolically link to the * source * @param source The path to the item for which a symbolic link should be * created */ - (void)createSymbolicLinkAtPath: (OFString*)destination withDestinationPath: (OFString*)source; +#endif +#if defined(OF_HAVE_READLINK) /*! * @brief Returns the destination of the symbolic link at the specified path. * * @param path The path to the symbolic link * Index: src/OFFileManager.m ================================================================== --- src/OFFileManager.m +++ src/OFFileManager.m @@ -48,10 +48,11 @@ #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" @@ -84,10 +85,14 @@ 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) @@ -126,10 +131,14 @@ } @implementation OFFileManager + (void)initialize { +#ifdef OF_WINDOWS + HMODULE module; +#endif + if (self != [OFFileManager class]) return; /* * Make sure OFFile is initialized. @@ -141,10 +150,17 @@ 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 @@ -873,11 +889,11 @@ } objc_autoreleasePoolPop(pool); } -#ifdef OF_HAVE_LINK +#if defined(OF_HAVE_LINK) - (void)linkItemAtPath: (OFString*)source toPath: (OFString*)destination { void *pool; of_string_encoding_t encoding; @@ -895,13 +911,32 @@ 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 -#ifdef OF_HAVE_SYMLINK +#if defined(OF_HAVE_SYMLINK) - (void)createSymbolicLinkAtPath: (OFString*)destination withDestinationPath: (OFString*)source { void *pool; of_string_encoding_t encoding; @@ -919,11 +954,36 @@ 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; Index: src/exceptions/OFCreateSymbolicLinkFailedException.h ================================================================== --- src/exceptions/OFCreateSymbolicLinkFailedException.h +++ src/exceptions/OFCreateSymbolicLinkFailedException.h @@ -14,11 +14,11 @@ * file. */ #import "OFException.h" -#ifdef OF_HAVE_SYMLINK +#if defined(OF_HAVE_SYMLINK) || defined(OF_WINDOWS) /*! * @class OFCreateSymbolicLinkFailedException \ * OFCreateSymbolicLinkFailedException.h \ * ObjFW/OFCreateSymbolicLinkFailedException.h * @@ -48,17 +48,38 @@ /*! * @brief Creates a new, autoreleased create symbolic link failed exception. * * @param sourcePath The source for the symbolic link * @param destinationPath The destination for the symbolic link + * @return A new, autoreleased create symbolic link failed exception + */ ++ (instancetype)exceptionWithSourcePath: (OFString*)sourcePath + destinationPath: (OFString*)destinationPath; + +/*! + * @brief Creates a new, autoreleased create symbolic link failed exception. + * + * @param sourcePath The source for the symbolic link + * @param destinationPath The destination for the symbolic link * @param errNo The errno of the error that occurred * @return A new, autoreleased create symbolic link failed exception */ + (instancetype)exceptionWithSourcePath: (OFString*)sourcePath destinationPath: (OFString*)destinationPath errNo: (int)errNo; +/*! + * @brief Initializes an already allocated create symbolic link failed + * exception. + * + * @param sourcePath The source for the symbolic link + * @param destinationPath The destination for the symbolic link + * @return An initialized create symbolic link failed exception + */ +- initWithSourcePath: (OFString*)sourcePath + destinationPath: (OFString*)destinationPath; + /*! * @brief Initializes an already allocated create symbolic link failed * exception. * * @param sourcePath The source for the symbolic link Index: src/exceptions/OFCreateSymbolicLinkFailedException.m ================================================================== --- src/exceptions/OFCreateSymbolicLinkFailedException.m +++ src/exceptions/OFCreateSymbolicLinkFailedException.m @@ -17,14 +17,21 @@ #include "config.h" #import "OFCreateSymbolicLinkFailedException.h" #import "OFString.h" -#ifdef OF_HAVE_SYMLINK +#if defined(OF_HAVE_SYMLINK) || defined(OF_WINDOWS) @implementation OFCreateSymbolicLinkFailedException @synthesize sourcePath = _sourcePath, destinationPath = _destinationPath; @synthesize errNo = _errNo; + ++ (instancetype)exceptionWithSourcePath: (OFString*)sourcePath + destinationPath: (OFString*)destinationPath +{ + return [[[self alloc] initWithSourcePath: sourcePath + destinationPath: destinationPath] autorelease]; +} + (instancetype)exceptionWithSourcePath: (OFString*)sourcePath destinationPath: (OFString*)destinationPath errNo: (int)errNo { @@ -35,10 +42,26 @@ - init { OF_INVALID_INIT_METHOD } + +- initWithSourcePath: (OFString*)sourcePath + destinationPath: (OFString*)destinationPath +{ + self = [super init]; + + @try { + _sourcePath = [sourcePath copy]; + _destinationPath = [destinationPath copy]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} - initWithSourcePath: (OFString*)sourcePath destinationPath: (OFString*)destinationPath errNo: (int)errNo { @@ -64,11 +87,17 @@ [super dealloc]; } - (OFString*)description { - return [OFString stringWithFormat: - @"Failed to symlink file %@ to %@: %@", - _sourcePath, _destinationPath, of_strerror(_errNo)]; + if (_errNo != 0) + return [OFString stringWithFormat: + @"Failed to create symbolic link %@ with destination " + @"%@: %@", _destinationPath, _sourcePath, + of_strerror(_errNo)]; + else + return [OFString stringWithFormat: + @"Failed to create symbolic link %@ with destination %@!", + _destinationPath, _sourcePath]; } @end #endif Index: src/exceptions/OFLinkFailedException.h ================================================================== --- src/exceptions/OFLinkFailedException.h +++ src/exceptions/OFLinkFailedException.h @@ -14,11 +14,11 @@ * file. */ #import "OFException.h" -#ifdef OF_HAVE_LINK +#if defined(OF_HAVE_LINK) || defined(OF_WINDOWS) /*! * @class OFLinkFailedException \ * OFLinkFailedException.h ObjFW/OFLinkFailedException.h * * @brief An exception indicating that creating a link failed. @@ -45,19 +45,39 @@ @property (readonly) int errNo; /*! * @brief Creates a new, autoreleased link failed exception. * + * @param sourcePath The source for the link + * @param destinationPath The destination for the link + * @return A new, autoreleased link failed exception + */ ++ (instancetype)exceptionWithSourcePath: (OFString*)sourcePath + destinationPath: (OFString*)destinationPath; + +/*! + * @brief Creates a new, autoreleased link failed exception. + * * @param sourcePath The source for the link * @param destinationPath The destination for the link * @param errNo The errno of the error that occurred * @return A new, autoreleased link failed exception */ + (instancetype)exceptionWithSourcePath: (OFString*)sourcePath destinationPath: (OFString*)destinationPath errNo: (int)errNo; +/*! + * @brief Initializes an already allocated link failed exception. + * + * @param sourcePath The source for the link + * @param destinationPath The destination for the link + * @return An initialized link failed exception + */ +- initWithSourcePath: (OFString*)sourcePath + destinationPath: (OFString*)destinationPath; + /*! * @brief Initializes an already allocated link failed exception. * * @param sourcePath The source for the link * @param destinationPath The destination for the link Index: src/exceptions/OFLinkFailedException.m ================================================================== --- src/exceptions/OFLinkFailedException.m +++ src/exceptions/OFLinkFailedException.m @@ -17,14 +17,21 @@ #include "config.h" #import "OFLinkFailedException.h" #import "OFString.h" -#ifdef OF_HAVE_LINK +#if defined(OF_HAVE_LINK) || defined(OF_WINDOWS) @implementation OFLinkFailedException @synthesize sourcePath = _sourcePath, destinationPath = _destinationPath; @synthesize errNo = _errNo; + ++ (instancetype)exceptionWithSourcePath: (OFString*)sourcePath + destinationPath: (OFString*)destinationPath +{ + return [[[self alloc] initWithSourcePath: sourcePath + destinationPath: destinationPath] autorelease]; +} + (instancetype)exceptionWithSourcePath: (OFString*)sourcePath destinationPath: (OFString*)destinationPath errNo: (int)errNo { @@ -35,10 +42,26 @@ - init { OF_INVALID_INIT_METHOD } + +- initWithSourcePath: (OFString*)sourcePath + destinationPath: (OFString*)destinationPath +{ + self = [super init]; + + @try { + _sourcePath = [sourcePath copy]; + _destinationPath = [destinationPath copy]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} - initWithSourcePath: (OFString*)sourcePath destinationPath: (OFString*)destinationPath errNo: (int)errNo { @@ -64,11 +87,16 @@ [super dealloc]; } - (OFString*)description { - return [OFString stringWithFormat: - @"Failed to link file %@ to %@: %@", - _sourcePath, _destinationPath, of_strerror(_errNo)]; + if (_errNo != 0) + return [OFString stringWithFormat: + @"Failed to link file %@ to %@: %@", + _sourcePath, _destinationPath, of_strerror(_errNo)]; + else + return [OFString stringWithFormat: + @"Failed to link file %@ to %@!", + _sourcePath, _destinationPath]; } @end #endif Index: src/objfw-defs.h.in ================================================================== --- src/objfw-defs.h.in +++ src/objfw-defs.h.in @@ -20,10 +20,11 @@ #undef OF_HAVE_PLUGINS #undef OF_HAVE_PROCESSES #undef OF_HAVE_PTHREADS #undef OF_HAVE_PTHREAD_SPINLOCKS #undef OF_HAVE_RECURSIVE_PTHREAD_MUTEXES +#undef OF_HAVE_READLINK #undef OF_HAVE_SCHED_YIELD #undef OF_HAVE_SOCKETS #undef OF_HAVE_STDNORETURN #undef OF_HAVE_SYMLINK #undef OF_HAVE_SYS_SOCKET_H