Index: src/OFFileURLHandler.m ================================================================== --- src/OFFileURLHandler.m +++ src/OFFileURLHandler.m @@ -22,12 +22,17 @@ #ifdef HAVE_DIRENT_H # include #endif #include "unistd_wrapper.h" +#import "platform.h" #ifdef HAVE_SYS_STAT_H # include +#endif +#include +#ifdef OF_WINDOWS +# include #endif #ifdef HAVE_PWD_H # include #endif @@ -107,10 +112,11 @@ #if !defined(HAVE_READDIR_R) && defined(OF_HAVE_THREADS) && !defined(OF_WINDOWS) static OFMutex *readdirMutex; #endif #ifdef OF_WINDOWS +static int (*func__wutime64)(const wchar_t *, struct __utimbuf64 *); static WINAPI BOOLEAN (*func_CreateSymbolicLinkW)(LPCWSTR, LPCWSTR, DWORD); static WINAPI BOOLEAN (*func_CreateHardLinkW)(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES); #endif @@ -511,10 +517,14 @@ #if !defined(HAVE_READDIR_R) && !defined(OF_WINDOWS) && defined(OF_HAVE_THREADS) readdirMutex = [[OFMutex alloc] init]; #endif #ifdef OF_WINDOWS + if ((module = LoadLibrary("msvcrt.dll")) != NULL) + func__wutime64 = (int (*)(const wchar_t *, + struct __utimbuf64 *))GetProcAddress(module, "_wutime64"); + if ((module = LoadLibrary("kernel32.dll")) != NULL) { func_CreateSymbolicLinkW = (WINAPI BOOLEAN (*)(LPCWSTR, LPCWSTR, DWORD)) GetProcAddress(module, "CreateSymbolicLinkW"); func_CreateHardLinkW = @@ -595,10 +605,89 @@ objc_autoreleasePoolPop(pool); return ret; } + +- (void)of_setLastAccessDate: (OFDate *)lastAccessDate + andModificationDate: (OFDate *)modificationDate + ofItemAtURL: (OFURL *)URL + attributes: (of_file_attributes_t)attributes +{ + OFString *path = URL.fileSystemRepresentation; + of_file_attribute_key_t attributeKey = (modificationDate != nil + ? of_file_attribute_key_modification_date + : of_file_attribute_key_last_access_date); + + if (lastAccessDate == nil) + lastAccessDate = modificationDate; + if (modificationDate == nil) + modificationDate = lastAccessDate; + +#ifdef OF_WINDOWS + if (func__wutime64 != NULL) { + struct __utimbuf64 times = { + .actime = + (__time64_t)lastAccessDate.timeIntervalSince1970, + .modtime = + (__time64_t)modificationDate.timeIntervalSince1970 + }; + + if (func__wutime64([path UTF16String], ×) != 0) + @throw [OFSetItemAttributesFailedException + exceptionWithURL: URL + attributes: attributes + failedAttribute: attributeKey + errNo: errno]; + } else { + struct _utimbuf times = { + .actime = (time_t)lastAccessDate.timeIntervalSince1970, + .modtime = + (time_t)modificationDate.timeIntervalSince1970 + }; + int status; + + if ([OFSystemInfo isWindowsNT]) + status = _wutime([path UTF16String], ×); + else + status = _utime( + [path cStringWithEncoding: [OFLocale encoding]], + ×); + + if (status != 0) + @throw [OFSetItemAttributesFailedException + exceptionWithURL: URL + attributes: attributes + failedAttribute: attributeKey + errNo: errno]; + } +#else + of_time_interval_t lastAccessTime = + lastAccessDate.timeIntervalSince1970; + of_time_interval_t modificationTime = + modificationDate.timeIntervalSince1970; + struct timeval times[2] = { + { + .tv_sec = (time_t)lastAccessTime, + .tv_usec = + (int)((lastAccessTime - times[0].tv_sec) * 1000) + }, + { + .tv_sec = (time_t)modificationTime, + .tv_usec = + (int)((modificationTime - times[1].tv_sec) * 1000) + }, + }; + + if (utimes([path cStringWithEncoding: [OFLocale encoding]], times) != 0) + @throw [OFSetItemAttributesFailedException + exceptionWithURL: URL + attributes: attributes + failedAttribute: attributeKey + errNo: errno]; +#endif +} - (void)of_setPOSIXPermissions: (OFNumber *)permissions ofItemAtURL: (OFURL *)URL attributes: (of_file_attributes_t)attributes { @@ -697,10 +786,11 @@ void *pool = objc_autoreleasePoolPush(); OFEnumerator OF_GENERIC(of_file_attribute_key_t) *keyEnumerator; OFEnumerator *objectEnumerator; of_file_attribute_key_t key; id object; + OFDate *lastAccessDate, *modificationDate; if (URL == nil) @throw [OFInvalidArgumentException exception]; if (![URL.scheme isEqual: _scheme]) @@ -709,11 +799,14 @@ keyEnumerator = [attributes keyEnumerator]; objectEnumerator = [attributes objectEnumerator]; while ((key = [keyEnumerator nextObject]) != nil && (object = [objectEnumerator nextObject]) != nil) { - if ([key isEqual: of_file_attribute_key_posix_permissions]) + if ([key isEqual: of_file_attribute_key_modification_date] || + [key isEqual: of_file_attribute_key_last_access_date]) + continue; + else if ([key isEqual: of_file_attribute_key_posix_permissions]) [self of_setPOSIXPermissions: object ofItemAtURL: URL attributes: attributes]; else if ([key isEqual: of_file_attribute_key_owner]) [self of_setOwner: object @@ -730,10 +823,21 @@ else @throw [OFNotImplementedException exceptionWithSelector: _cmd object: self]; } + + lastAccessDate = [attributes + objectForKey: of_file_attribute_key_last_access_date]; + modificationDate = [attributes + objectForKey: of_file_attribute_key_modification_date]; + + if (lastAccessDate != nil || modificationDate != nil) + [self of_setLastAccessDate: lastAccessDate + andModificationDate: modificationDate + ofItemAtURL: URL + attributes: attributes]; objc_autoreleasePoolPop(pool); } - (bool)fileExistsAtURL: (OFURL *)URL Index: src/OFGZIPStream.h ================================================================== --- src/OFGZIPStream.h +++ src/OFGZIPStream.h @@ -38,11 +38,11 @@ OF_GZIP_STREAM_ID2, OF_GZIP_STREAM_COMPRESSION_METHOD, OF_GZIP_STREAM_FLAGS, OF_GZIP_STREAM_MODIFICATION_TIME, OF_GZIP_STREAM_EXTRA_FLAGS, - OF_GZIP_STREAM_OS, + OF_GZIP_STREAM_OPERATING_SYSTEM, OF_GZIP_STREAM_EXTRA_LENGTH, OF_GZIP_STREAM_EXTRA, OF_GZIP_STREAM_NAME, OF_GZIP_STREAM_COMMENT, OF_GZIP_STREAM_HEADER_CRC16, @@ -56,34 +56,51 @@ OF_GZIP_STREAM_FLAG_EXTRA = 0x04, OF_GZIP_STREAM_FLAG_NAME = 0x08, OF_GZIP_STREAM_FLAG_COMMENT = 0x10 } _flags; uint8_t _extraFlags; - enum of_gzip_stream_os { - OF_GZIP_STREAM_OS_FAT = 0, - OF_GZIP_STREAM_OS_AMIGA = 1, - OF_GZIP_STREAM_OS_VMS = 2, - OF_GZIP_STREAM_OS_UNIX = 3, - OF_GZIP_STREAM_OS_VM_CMS = 4, - OF_GZIP_STREAM_OS_ATARI_TOS = 5, - OF_GZIP_STREAM_OS_HPFS = 6, - OF_GZIP_STREAM_OS_MACINTOSH = 7, - OF_GZIP_STREAM_OS_Z_SYSTEM = 8, - OF_GZIP_STREAM_OS_CP_M = 9, - OF_GZIP_STREAM_OS_TOPS_20 = 10, - OF_GZIP_STREAM_OS_NTFS = 11, - OF_GZIP_STREAM_OS_QDO = 12, - OF_GZIP_STREAM_OS_ACORN_RISC_OS = 13, - OF_GZIP_STREAM_OS_UNKNOWN = 255 - } _OS; + enum of_gzip_stream_operating_system { + OF_GZIP_STREAM_OPERATING_SYSTEM_FAT = 0, + OF_GZIP_STREAM_OPERATING_SYSTEM_AMIGA = 1, + OF_GZIP_STREAM_OPERATING_SYSTEM_VMS = 2, + OF_GZIP_STREAM_OPERATING_SYSTEM_UNIX = 3, + OF_GZIP_STREAM_OPERATING_SYSTEM_VM_CMS = 4, + OF_GZIP_STREAM_OPERATING_SYSTEM_ATARI_TOS = 5, + OF_GZIP_STREAM_OPERATING_SYSTEM_HPFS = 6, + OF_GZIP_STREAM_OPERATING_SYSTEM_MACINTOSH = 7, + OF_GZIP_STREAM_OPERATING_SYSTEM_Z_SYSTEM = 8, + OF_GZIP_STREAM_OPERATING_SYSTEM_CP_M = 9, + OF_GZIP_STREAM_OPERATING_SYSTEM_TOPS_20 = 10, + OF_GZIP_STREAM_OPERATING_SYSTEM_NTFS = 11, + OF_GZIP_STREAM_OPERATING_SYSTEM_QDO = 12, + OF_GZIP_STREAM_OPERATING_SYSTEM_ACORN_RISC_OS = 13, + OF_GZIP_STREAM_OPERATING_SYSTEM_UNKNOWN = 255 + } _operatingSystemMadeOn; size_t _bytesRead; uint8_t _buffer[4]; OFDate *_Nullable _modificationDate; uint16_t _extraLength; uint32_t _CRC32, _uncompressedSize; } +/*! + * @brief The operating system on which the data was compressed. + * + * This property is only guaranteed to be available once @ref atEndOfStream is + * true. + */ +@property (readonly, nonatomic) + enum of_gzip_stream_operating_system operatingSystemMadeOn; + +/*! + * @brief The modification date of the original file. + * + * This property is only guaranteed to be available once @ref atEndOfStream is + * true. + */ +@property OF_NULLABLE_PROPERTY (readonly, nonatomic) OFDate *modificationDate; + /*! * @brief Creates a new OFGZIPStream with the specified underlying stream. * * @param stream The underlying stream for the OFGZIPStream * @param mode The mode for the OFGZIPStream. Valid modes are "r" for reading Index: src/OFGZIPStream.m ================================================================== --- src/OFGZIPStream.m +++ src/OFGZIPStream.m @@ -28,10 +28,13 @@ #import "OFNotImplementedException.h" #import "OFNotOpenException.h" #import "OFTruncatedDataException.h" @implementation OFGZIPStream +@synthesize operatingSystemMadeOn = _operatingSystemMadeOn; +@synthesize modificationDate = _modificationDate; + + (instancetype)streamWithStream: (OFStream *)stream mode: (OFString *)mode { return [[[self alloc] initWithStream: stream mode: mode] autorelease]; @@ -52,10 +55,12 @@ @throw [OFNotImplementedException exceptionWithSelector: _cmd object: nil]; _stream = [stream retain]; + _operatingSystemMadeOn = + OF_GZIP_STREAM_OPERATING_SYSTEM_UNKNOWN; _CRC32 = ~0; } @catch (id e) { [self release]; @throw e; } @@ -140,16 +145,16 @@ return 0; _extraFlags = byte; _state++; break; - case OF_GZIP_STREAM_OS: + case OF_GZIP_STREAM_OPERATING_SYSTEM: if ([_stream readIntoBuffer: &byte length: 1] < 1) return 0; - _OS = byte; + _operatingSystemMadeOn = byte; _state++; break; case OF_GZIP_STREAM_EXTRA_LENGTH: if (!(_flags & OF_GZIP_STREAM_FLAG_EXTRA)) { _state += 2; Index: utils/ofarc/GZIPArchive.m ================================================================== --- utils/ofarc/GZIPArchive.m +++ utils/ofarc/GZIPArchive.m @@ -41,10 +41,26 @@ [fileManager setAttributes: destinationAttributes ofItemAtPath: destination]; #endif } + +static void +setModificationDate(OFString *path, OFGZIPStream *stream) +{ + OFDate *modificationDate = stream.modificationDate; + of_file_attributes_t attributes; + + if (modificationDate == nil) + return; + + attributes = [OFDictionary + dictionaryWithObject: modificationDate + forKey: of_file_attribute_key_modification_date]; + [[OFFileManager defaultManager] setAttributes: attributes + ofItemAtPath: path]; +} @implementation GZIPArchive + (void)initialize { if (self == [GZIPArchive class]) @@ -129,10 +145,13 @@ app->_exitStatus = 1; return; } } + [output close]; + setModificationDate(fileName, _stream); + if (app->_outputLevel >= 0) { [of_stdout writeString: @"\r"]; [of_stdout writeLine: OF_LOCALIZED(@"extracting_file_done", @"Extracting %[file]... done", @"file", fileName)]; Index: utils/ofarc/LHAArchive.m ================================================================== --- utils/ofarc/LHAArchive.m +++ utils/ofarc/LHAArchive.m @@ -53,10 +53,34 @@ [[OFFileManager defaultManager] setAttributes: attributes ofItemAtPath: path]; #endif } + +static void +setModificationDate(OFString *path, OFLHAArchiveEntry *entry) +{ + OFDate *modificationDate = entry.modificationDate; + of_file_attributes_t attributes; + + if (modificationDate == nil) { + /* + * Fall back to the original date if we have no modification + * date, as the modification date is a UNIX extension. + */ + modificationDate = entry.date; + + if (modificationDate == nil) + return; + } + + attributes = [OFDictionary + dictionaryWithObject: modificationDate + forKey: of_file_attribute_key_modification_date]; + [[OFFileManager defaultManager] setAttributes: attributes + ofItemAtPath: path]; +} @implementation LHAArchive + (void)initialize { if (self == [LHAArchive class]) @@ -283,10 +307,11 @@ if ([fileName hasSuffix: @"/"]) { [fileManager createDirectoryAtPath: outFileName createParents: true]; setPermissions(outFileName, entry); + setModificationDate(outFileName, entry); if (app->_outputLevel >= 0) { [of_stdout writeString: @"\r"]; [of_stdout writeLine: OF_LOCALIZED( @"extracting_file_done", @@ -339,10 +364,13 @@ @"file", fileName, @"percent", percentString)]; } } + [output close]; + setModificationDate(outFileName, entry); + if (app->_outputLevel >= 0) { [of_stdout writeString: @"\r"]; [of_stdout writeLine: OF_LOCALIZED( @"extracting_file_done", @"Extracting %[file]... done", Index: utils/ofarc/TarArchive.m ================================================================== --- utils/ofarc/TarArchive.m +++ utils/ofarc/TarArchive.m @@ -41,10 +41,26 @@ [[OFFileManager defaultManager] setAttributes: attributes ofItemAtPath: path]; #endif } + +static void +setModificationDate(OFString *path, OFTarArchiveEntry *entry) +{ + OFDate *modificationDate = entry.modificationDate; + of_file_attributes_t attributes; + + if (modificationDate == nil) + return; + + attributes = [OFDictionary + dictionaryWithObject: modificationDate + forKey: of_file_attribute_key_modification_date]; + [[OFFileManager defaultManager] setAttributes: attributes + ofItemAtPath: path]; +} @implementation TarArchive + (void)initialize { if (self == [TarArchive class]) @@ -304,10 +320,11 @@ (type == OF_TAR_ARCHIVE_ENTRY_TYPE_FILE && [fileName hasSuffix: @"/"])) { [fileManager createDirectoryAtPath: outFileName createParents: true]; setPermissions(outFileName, entry); + setModificationDate(outFileName, entry); if (app->_outputLevel >= 0) { [of_stdout writeString: @"\r"]; [of_stdout writeLine: OF_LOCALIZED( @"extracting_file_done", @@ -360,10 +377,13 @@ @"file", fileName, @"percent", percentString)]; } } + [output close]; + setModificationDate(outFileName, entry); + if (app->_outputLevel >= 0) { [of_stdout writeString: @"\r"]; [of_stdout writeLine: OF_LOCALIZED( @"extracting_file_done", @"Extracting %[file]... done", Index: utils/ofarc/ZIPArchive.m ================================================================== --- utils/ofarc/ZIPArchive.m +++ utils/ofarc/ZIPArchive.m @@ -54,10 +54,26 @@ [[OFFileManager defaultManager] setAttributes: attributes ofItemAtPath: path]; } #endif } + +static void +setModificationDate(OFString *path, OFZIPArchiveEntry *entry) +{ + OFDate *modificationDate = entry.modificationDate; + of_file_attributes_t attributes; + + if (modificationDate == nil) + return; + + attributes = [OFDictionary + dictionaryWithObject: modificationDate + forKey: of_file_attribute_key_modification_date]; + [[OFFileManager defaultManager] setAttributes: attributes + ofItemAtPath: path]; +} @implementation ZIPArchive + (void)initialize { if (self == [ZIPArchive class]) @@ -260,10 +276,11 @@ if ([fileName hasSuffix: @"/"]) { [fileManager createDirectoryAtPath: outFileName createParents: true]; setPermissions(outFileName, entry); + setModificationDate(outFileName, entry); if (app->_outputLevel >= 0) { [of_stdout writeString: @"\r"]; [of_stdout writeLine: OF_LOCALIZED( @"extracting_file_done", @@ -316,10 +333,13 @@ @"file", fileName, @"percent", percentString)]; } } + [output close]; + setModificationDate(outFileName, entry); + if (app->_outputLevel >= 0) { [of_stdout writeString: @"\r"]; [of_stdout writeLine: OF_LOCALIZED( @"extracting_file_done", @"Extracting %[file]... done",