Index: configure.ac ================================================================== --- configure.ac +++ configure.ac @@ -1176,10 +1176,13 @@ ]) AC_CHECK_FUNC(symlink, [ AC_DEFINE(OF_HAVE_SYMLINK, 1, [Whether we have symlink()]) ]) AC_CHECK_FUNCS([lstat]) + AC_CHECK_MEMBERS([struct stat.st_birthtime], [], [], [ + #include + ]) old_OBJCFLAGS="$OBJCFLAGS" OBJCFLAGS="$OBJCFLAGS -Werror" AC_MSG_CHECKING(for readdir_r) AC_TRY_COMPILE([ Index: src/OFFileManager.h ================================================================== --- src/OFFileManager.h +++ src/OFFileManager.h @@ -56,10 +56,11 @@ * * @ref of_file_attribute_key_owner * * @ref of_file_attribute_key_group * * @ref of_file_attribute_key_last_access_date * * @ref of_file_attribute_key_modification_date * * @ref of_file_attribute_key_status_change_date + * * @ref of_file_attribute_key_creation_date * * @ref of_file_attribute_key_symbolic_link_destination * * Other URL schemes might not have all keys and might have keys not listed. */ typedef OFConstantString *of_file_attribute_key_t; @@ -178,10 +179,18 @@ * For convenience, a category on @ref OFDictionary is provided to access this * via @ref OFDictionary#fileStatusChangeDate. */ extern const of_file_attribute_key_t of_file_attribute_key_status_change_date; +/*! + * @brief The creation date of the file as an @ref OFDate. + * + * For convenience, a category on @ref OFDictionary is provided to access this + * via @ref OFDictionary#fileCreationDate. + */ +extern const of_file_attribute_key_t of_file_attribute_key_creation_date; + /*! * @brief The destination of a symbolic link as an @ref OFString. * * For convenience, a category on @ref OFDictionary is provided to access this * via @ref OFDictionary#fileSymbolicLinkDestination. @@ -560,84 +569,95 @@ withDestinationPath: (OFString *)target; @end @interface OFDictionary (FileAttributes) /*! - * The @ref of_file_attribute_key_size key from the dictionary. + * @brief The @ref of_file_attribute_key_size key from the dictionary. * * Raises an @ref OFUndefinedKeyException if the key is missing. */ @property (readonly, nonatomic) uintmax_t fileSize; /*! - * The @ref of_file_attribute_key_type key from the dictionary. + * @brief The @ref of_file_attribute_key_type key from the dictionary. * * Raises an @ref OFUndefinedKeyException if the key is missing. */ @property (readonly, nonatomic) of_file_type_t fileType; /*! - * The @ref of_file_attribute_key_posix_permissions key from the dictionary. + * @brief The @ref of_file_attribute_key_posix_permissions key from the + * dictionary. * * Raises an @ref OFUndefinedKeyException if the key is missing. */ @property (readonly, nonatomic) uint16_t filePOSIXPermissions; /*! - * The @ref of_file_attribute_key_posix_uid key from the dictionary. + * @brief The @ref of_file_attribute_key_posix_uid key from the dictionary. * * Raises an @ref OFUndefinedKeyException if the key is missing. */ @property (readonly, nonatomic) uint32_t filePOSIXUID; /*! - * The @ref of_file_attribute_key_posix_gid key from the dictionary. + * @brief The @ref of_file_attribute_key_posix_gid key from the dictionary. * * Raises an @ref OFUndefinedKeyException if the key is missing. */ @property (readonly, nonatomic) uint32_t filePOSIXGID; /*! - * The @ref of_file_attribute_key_owner key from the dictionary. + * @brief The @ref of_file_attribute_key_owner key from the dictionary. * * Raises an @ref OFUndefinedKeyException if the key is missing. */ @property (readonly, nonatomic) OFString *fileOwner; /*! - * The @ref of_file_attribute_key_group key from the dictionary. + * @brief The @ref of_file_attribute_key_group key from the dictionary. * * Raises an @ref OFUndefinedKeyException if the key is missing. */ @property (readonly, nonatomic) OFString *fileGroup; /*! - * The @ref of_file_attribute_key_last_access_date key from the dictionary. + * @brief The @ref of_file_attribute_key_last_access_date key from the + * dictionary. * * Raises an @ref OFUndefinedKeyException if the key is missing. */ @property (readonly, nonatomic) OFDate *fileLastAccessDate; /*! - * The @ref of_file_attribute_key_modification_date key from the dictionary. + * @brief The @ref of_file_attribute_key_modification_date key from the + * dictionary. * * Raises an @ref OFUndefinedKeyException if the key is missing. */ @property (readonly, nonatomic) OFDate *fileModificationDate; /*! - * The @ref of_file_attribute_key_status_change_date key from the dictionary. + * @brief The @ref of_file_attribute_key_status_change_date key from the + * dictionary. * * Raises an @ref OFUndefinedKeyException if the key is missing. */ @property (readonly, nonatomic) OFDate *fileStatusChangeDate; /*! - * The @ref of_file_attribute_key_symbolic_link_destination key from the - * dictionary. + * @brief The @ref of_file_attribute_key_creation_date key from the dictionary. + * + * Raises an @ref OFUndefinedKeyException if the key is missing. + */ +@property (readonly, nonatomic) OFDate *fileCreationDate; + +/*! + * @brief The @ref of_file_attribute_key_symbolic_link_destination key from the + * dictionary. * * Raises an @ref OFUndefinedKeyException if the key is missing. */ @property (readonly, nonatomic) OFString *fileSymbolicLinkDestination; @end OF_ASSUME_NONNULL_END Index: src/OFFileManager.m ================================================================== --- src/OFFileManager.m +++ src/OFFileManager.m @@ -95,10 +95,12 @@ @"of_file_attribute_key_last_access_date"; const of_file_attribute_key_t of_file_attribute_key_modification_date = @"of_file_attribute_key_modification_date"; const of_file_attribute_key_t of_file_attribute_key_status_change_date = @"of_file_attribute_key_status_change_date"; +const of_file_attribute_key_t of_file_attribute_key_creation_date = + @"of_file_attribute_key_creation_date"; const of_file_attribute_key_t of_file_attribute_key_symbolic_link_destination = @"of_file_attribute_key_symbolic_link_destination"; const of_file_type_t of_file_type_regular = @"of_file_type_regular"; const of_file_type_t of_file_type_directory = @"of_file_type_directory"; @@ -967,12 +969,18 @@ - (OFDate *)fileStatusChangeDate { return attributeForKeyOrException(self, of_file_attribute_key_status_change_date); } + +- (OFDate *)fileCreationDate +{ + return attributeForKeyOrException(self, + of_file_attribute_key_creation_date); +} - (OFString *)fileSymbolicLinkDestination { return attributeForKeyOrException(self, of_file_attribute_key_symbolic_link_destination); } @end Index: src/OFURLHandler_file.m ================================================================== --- src/OFURLHandler_file.m +++ src/OFURLHandler_file.m @@ -64,10 +64,11 @@ #ifdef OF_WINDOWS # include # include # include +# include #endif #ifdef OF_AMIGAOS # ifdef OF_AMIGAOS4 # define __USE_INLINE__ @@ -80,26 +81,30 @@ # ifdef OF_AMIGAOS4 # define DeleteFile(path) Delete(path) # endif #endif -#if defined(OF_WINDOWS) -typedef struct __stat64 of_stat_t; -#elif defined(OF_AMIGAOS) +#if defined(OF_WINDOWS) || defined(OF_AMIGAOS) typedef struct { of_offset_t st_size; - mode_t st_mode; + unsigned int st_mode; of_time_interval_t st_atime, st_mtime, st_ctime; +# ifdef OF_WINDOWS +# define HAVE_STRUCT_STAT_ST_BIRTHTIME + of_time_interval_t st_birthtime; + DWORD fileAttributes; +# endif } of_stat_t; #elif defined(OF_HAVE_OFF64_T) typedef struct stat64 of_stat_t; #else typedef struct stat of_stat_t; #endif -#ifndef S_ISLNK -# define S_ISLNK(s) 0 +#ifdef OF_WINDOWS +# define S_IFLNK 0x10000 +# define S_ISLNK(mode) (mode & S_IFLNK) #endif #if defined(OF_HAVE_CHOWN) && defined(OF_HAVE_THREADS) && !defined(OF_AMIGAOS) static OFMutex *passwdMutex; #endif @@ -131,16 +136,97 @@ if (DOSBase != NULL) CloseLibrary(DOSBase); } #endif + +#ifdef OF_WINDOWS +static of_time_interval_t +filetimeToTimeInterval(const FILETIME *filetime) +{ + return (double)((int64_t)filetime->dwHighDateTime << 32 | + filetime->dwLowDateTime) / 10000000.0 - 11644473600.0; +} + +static void +setErrno(void) +{ + switch (GetLastError()) { + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + case ERROR_NO_MORE_FILES: + errno = ENOENT; + return; + case ERROR_ACCESS_DENIED: + errno = EACCES; + return; + case ERROR_DIRECTORY: + errno = ENOTDIR; + return; + case ERROR_NOT_READY: + errno = EBUSY; + return; + } + + errno = 0; +} +#endif static int of_stat(OFString *path, of_stat_t *buffer) { #if defined(OF_WINDOWS) - return _wstat64(path.UTF16String, buffer); + WIN32_FILE_ATTRIBUTE_DATA data; + + if (!GetFileAttributesExW(path.UTF16String, GetFileExInfoStandard, + &data)) { + setErrno(); + return -1; + } + + buffer->st_size = (uint64_t)data.nFileSizeHigh << 32 | + data.nFileSizeLow; + + if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + buffer->st_mode = S_IFDIR; + else if (data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { + WIN32_FIND_DATAW findData; + HANDLE findHandle; + + if ((findHandle = FindFirstFileW(path.UTF16String, + &findData)) == INVALID_HANDLE_VALUE) { + setErrno(); + return -1; + } + + @try { + if (!(findData.dwFileAttributes & + FILE_ATTRIBUTE_REPARSE_POINT)) { + /* Race? Indicate to try again. */ + errno = EAGAIN; + return -1; + } + + buffer->st_mode = + (findData.dwReserved0 == IO_REPARSE_TAG_SYMLINK + ? S_IFLNK : S_IFREG); + } @finally { + FindClose(findHandle); + } + } else + buffer->st_mode = S_IFREG; + + buffer->st_mode |= (data.dwFileAttributes & FILE_ATTRIBUTE_READONLY + ? (S_IRUSR | S_IXUSR) : (S_IRUSR | S_IWUSR | S_IXUSR)); + + buffer->st_atime = filetimeToTimeInterval(&data.ftLastAccessTime); + buffer->st_mtime = filetimeToTimeInterval(&data.ftLastWriteTime); + buffer->st_ctime = buffer->st_birthtime = + filetimeToTimeInterval(&data.ftCreationTime); + buffer->fileAttributes = data.dwFileAttributes; + + return 0; #elif defined(OF_AMIGAOS) BPTR lock; # ifdef OF_AMIGAOS4 struct ExamineData *ed; # else @@ -292,10 +378,15 @@ setObject: [OFDate dateWithTimeIntervalSince1970: s->st_mtime] forKey: of_file_attribute_key_modification_date]; [attributes setObject: [OFDate dateWithTimeIntervalSince1970: s->st_ctime] forKey: of_file_attribute_key_status_change_date]; +#ifdef HAVE_STRUCT_STAT_ST_BIRTHTIME + [attributes + setObject: [OFDate dateWithTimeIntervalSince1970: s->st_birthtime] + forKey: of_file_attribute_key_creation_date]; +#endif } static void setOwnerAndGroupAttributes(of_mutable_file_attributes_t attributes, of_stat_t *s) @@ -337,27 +428,23 @@ } # endif #endif } +#ifdef OF_FILE_MANAGER_SUPPORTS_SYMLINKS static void setSymbolicLinkDestinationAttribute(of_mutable_file_attributes_t attributes, - of_stat_t *s, OFURL *URL) + OFURL *URL) { -#ifdef OF_FILE_MANAGER_SUPPORTS_SYMLINKS OFString *path = URL.fileSystemRepresentation; - # ifndef OF_WINDOWS of_string_encoding_t encoding = [OFLocale encoding]; char destinationC[PATH_MAX]; ssize_t length; OFString *destination; of_file_attribute_key_t key; - if (!S_ISLNK(s->st_mode)) - return; - length = readlink([path cStringWithEncoding: encoding], destinationC, PATH_MAX); if (length < 0) @throw [OFRetrieveItemAttributesFailedException @@ -370,36 +457,19 @@ key = of_file_attribute_key_symbolic_link_destination; [attributes setObject: destination forKey: key]; # else - HANDLE findHandle; - WIN32_FIND_DATAW findData; - HANDLE fileHandle; + HANDLE handle; OFString *destination; if (func_CreateSymbolicLinkW == NULL) return; - findHandle = FindFirstFileW(path.UTF16String, &findData); - if (findHandle == INVALID_HANDLE_VALUE) - return; - - @try { - if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) - return; - - if (findData.dwReserved0 != IO_REPARSE_TAG_SYMLINK) - return; - } @finally { - FindClose(findHandle); - } - - fileHandle = CreateFileW(path.UTF16String, 0, (FILE_SHARE_READ | + if ((handle = CreateFileW(path.UTF16String, 0, (FILE_SHARE_READ | FILE_SHARE_WRITE), NULL, OPEN_EXISTING, - FILE_FLAG_OPEN_REPARSE_POINT, NULL); - if (fileHandle == INVALID_HANDLE_VALUE) + FILE_FLAG_OPEN_REPARSE_POINT, NULL)) == INVALID_HANDLE_VALUE) @throw [OFRetrieveItemAttributesFailedException exceptionWithURL: URL errNo: 0]; @try { @@ -409,12 +479,12 @@ } buffer; DWORD size; wchar_t *tmp; of_file_attribute_key_t key; - if (!DeviceIoControl(fileHandle, FSCTL_GET_REPARSE_POINT, NULL, - 0, buffer.bytes, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &size, + if (!DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0, + buffer.bytes, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &size, NULL)) @throw [OFRetrieveItemAttributesFailedException exceptionWithURL: URL errNo: 0]; @@ -437,15 +507,15 @@ key = of_file_attribute_key_symbolic_link_destination; [attributes setObject: destination forKey: key]; # undef slrb } @finally { - CloseHandle(fileHandle); + CloseHandle(handle); } # endif -#endif } +#endif @implementation OFURLHandler_file + (void)initialize { #ifdef OF_WINDOWS @@ -496,24 +566,16 @@ [OFFile class]; } + (bool)of_directoryExistsAtPath: (OFString *)path { -#ifdef OF_WINDOWS - DWORD attributes = GetFileAttributesW(path.UTF16String); - if (attributes == INVALID_FILE_ATTRIBUTES) - return false; - - return (attributes & FILE_ATTRIBUTE_DIRECTORY); -#else of_stat_t s; if (of_stat(path, &s) == -1) return false; return S_ISDIR(s.st_mode); -#endif } - (OFStream *)openItemAtURL: (OFURL *)URL mode: (OFString *)mode { @@ -558,11 +620,14 @@ [ret setObject: [NSNumber numberWithUInt16: s.st_mode & 07777] forKey: of_file_attribute_key_posix_permissions]; setOwnerAndGroupAttributes(ret, &s); setDateAttributes(ret, &s); - setSymbolicLinkDestinationAttribute(ret, &s, URL); +#ifdef S_ISLNK + if (S_ISLNK(s.st_mode)) + setSymbolicLinkDestinationAttribute(ret, URL); +#endif objc_autoreleasePoolPop(pool); return ret; } @@ -701,71 +766,49 @@ } - (bool)fileExistsAtURL: (OFURL *)URL { void *pool = objc_autoreleasePoolPush(); -#ifndef OF_WINDOWS of_stat_t s; -#endif bool ret; if (URL == nil) @throw [OFInvalidArgumentException exception]; if (![URL.scheme isEqual: _scheme]) @throw [OFInvalidArgumentException exception]; -#ifdef OF_WINDOWS - ret = (GetFileAttributesW(URL.fileSystemRepresentation.UTF16String) != - INVALID_FILE_ATTRIBUTES); -#else if (of_stat(URL.fileSystemRepresentation, &s) == -1) { objc_autoreleasePoolPop(pool); return false; } ret = S_ISREG(s.st_mode); -#endif objc_autoreleasePoolPop(pool); return ret; } - (bool)directoryExistsAtURL: (OFURL *)URL { void *pool = objc_autoreleasePoolPush(); -#ifdef OF_WINDOWS - DWORD attributes; -#else of_stat_t s; -#endif bool ret; if (URL == nil) @throw [OFInvalidArgumentException exception]; if (![URL.scheme isEqual: _scheme]) @throw [OFInvalidArgumentException exception]; -#ifdef OF_WINDOWS - attributes = GetFileAttributesW( - URL.fileSystemRepresentation.UTF16String); - if (attributes == INVALID_FILE_ATTRIBUTES) { - objc_autoreleasePoolPop(pool); - return false; - } - - ret = (attributes & FILE_ATTRIBUTE_DIRECTORY); -#else if (of_stat(URL.fileSystemRepresentation, &s) == -1) { objc_autoreleasePoolPop(pool); return false; } ret = S_ISDIR(s.st_mode); -#endif objc_autoreleasePoolPop(pool); return ret; }