@@ -13,35 +13,66 @@ * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ #include "config.h" + +#include #import "OFTarArchive.h" #import "OFTarArchiveEntry.h" #import "OFTarArchiveEntry+Private.h" #import "OFStream.h" +#import "OFDate.h" #ifdef OF_HAVE_FILES # import "OFFile.h" #endif #import "OFInvalidArgumentException.h" #import "OFInvalidFormatException.h" #import "OFNotOpenException.h" +#import "OFOutOfRangeException.h" +#import "OFTruncatedDataException.h" +#import "OFWriteFailedException.h" @interface OFTarArchive_FileReadStream: OFStream { - OFStream *_stream; OFTarArchiveEntry *_entry; - size_t _toRead; + OFStream *_stream; + uint64_t _toRead; bool _atEndOfStream; } - initWithEntry: (OFTarArchiveEntry *)entry stream: (OFStream *)stream; - (void)of_skip; @end + +@interface OFTarArchive_FileWriteStream: OFStream +{ + OFTarArchiveEntry *_entry; + OFStream *_stream; + uint64_t _toWrite; +} + +- initWithEntry: (OFTarArchiveEntry *)entry + stream: (OFStream *)stream; +@end + +static void +stringToBuffer(unsigned char *buffer, OFString *string, size_t length) +{ + size_t UTF8StringLength = [string UTF8StringLength]; + + if (UTF8StringLength > length) + @throw [OFOutOfRangeException exception]; + + memcpy(buffer, [string UTF8String], UTF8StringLength); + + for (size_t i = UTF8StringLength; i < length; i++) + buffer[i] = '\0'; +} @implementation OFTarArchive: OFObject + (instancetype)archiveWithStream: (OFStream *)stream mode: (OFString *)mode { @@ -66,12 +97,42 @@ @try { _stream = [stream retain]; if ([mode isEqual: @"r"]) _mode = OF_TAR_ARCHIVE_MODE_READ; + else if ([mode isEqual: @"w"]) + _mode = OF_TAR_ARCHIVE_MODE_WRITE; + else if ([mode isEqual: @"a"]) + _mode = OF_TAR_ARCHIVE_MODE_APPEND; else @throw [OFInvalidArgumentException exception]; + + if (_mode == OF_TAR_ARCHIVE_MODE_APPEND) { + union { + char c[1024]; + uint32_t u32[1024 / sizeof(uint32_t)]; + } buffer; + bool empty = true; + + if (![_stream isKindOfClass: [OFSeekableStream class]]) + @throw [OFInvalidArgumentException exception]; + + [(OFSeekableStream *)stream seekToOffset: -1024 + whence: SEEK_END]; + [stream readIntoBuffer: buffer.c + exactLength: 1024]; + + for (size_t i = 0; i < 1024 / sizeof(uint32_t); i++) + if (buffer.u32[i] != 0) + empty = false; + + if (!empty) + @throw [OFInvalidFormatException exception]; + + [(OFSeekableStream *)stream seekToOffset: -1024 + whence: SEEK_END]; + } } @catch (id e) { [self release]; @throw e; } @@ -80,12 +141,19 @@ #ifdef OF_HAVE_FILES - initWithPath: (OFString *)path mode: (OFString *)mode { - OFFile *file = [[OFFile alloc] initWithPath: path + OFFile *file; + + if ([mode isEqual: @"a"]) + file = [[OFFile alloc] initWithPath: path + mode: @"r+"]; + else + file = [[OFFile alloc] initWithPath: path mode: mode]; + @try { self = [self initWithStream: file mode: mode]; } @finally { [file release]; @@ -95,12 +163,11 @@ } #endif - (void)dealloc { - [_stream release]; - [_lastReturnedStream release]; + [self close]; [super dealloc]; } - (OFTarArchiveEntry *)nextEntry @@ -151,12 +218,100 @@ return entry; } - (OFStream *)streamForReadingCurrentEntry { + if (_mode != OF_TAR_ARCHIVE_MODE_READ) + @throw [OFInvalidArgumentException exception]; + + return [[_lastReturnedStream retain] autorelease]; +} + +- (OFStream *)streamForWritingEntry: (OFTarArchiveEntry *)entry +{ + void *pool; + uint64_t modificationDate; + unsigned char buffer[512]; + uint16_t checksum = 0; + + if (_mode != OF_TAR_ARCHIVE_MODE_WRITE && + _mode != OF_TAR_ARCHIVE_MODE_APPEND) + @throw [OFInvalidArgumentException exception]; + + pool = objc_autoreleasePoolPush(); + + [_lastReturnedStream close]; + [_lastReturnedStream release]; + _lastReturnedStream = nil; + + stringToBuffer(buffer, [entry fileName], 100); + stringToBuffer(buffer + 100, + [OFString stringWithFormat: @"%06" PRIo32 " ", [entry mode]], 8); + memcpy(buffer + 108, "000000 \0" "000000 \0", 16); + stringToBuffer(buffer + 124, + [OFString stringWithFormat: @"%011" PRIo64 " ", [entry size]], 12); + modificationDate = [[entry modificationDate] timeIntervalSince1970]; + stringToBuffer(buffer + 136, + [OFString stringWithFormat: @"%011" PRIo64 " ", modificationDate], + 12); + + /* + * During checksumming, the checksum field is expected to be set to 8 + * spaces. + */ + memset(buffer + 148, ' ', 8); + + buffer[156] = [entry type]; + stringToBuffer(buffer + 157, [entry targetFileName], 100); + + /* ustar */ + memcpy(buffer + 257, "ustar\0" "00", 8); + stringToBuffer(buffer + 265, [entry owner], 32); + stringToBuffer(buffer + 297, [entry group], 32); + stringToBuffer(buffer + 329, + [OFString stringWithFormat: @"%06" PRIo32 " ", [entry deviceMajor]], + 8); + stringToBuffer(buffer + 337, + [OFString stringWithFormat: @"%06" PRIo32 " ", [entry deviceMinor]], + 8); + memset(buffer + 345, '\0', 155 + 12); + + /* Fill in the checksum */ + for (size_t i = 0; i < 500; i++) + checksum += buffer[i]; + stringToBuffer(buffer + 148, + [OFString stringWithFormat: @"%06" PRIo16, checksum], 7); + + [_stream writeBuffer: buffer + length: sizeof(buffer)]; + + _lastReturnedStream = [[OFTarArchive_FileWriteStream alloc] + initWithEntry: entry + stream: _stream]; + + objc_autoreleasePoolPop(pool); + return [[_lastReturnedStream retain] autorelease]; } + +- (void)close +{ + [_lastReturnedStream close]; + [_lastReturnedStream release]; + _lastReturnedStream = nil; + + if (_mode == OF_TAR_ARCHIVE_MODE_WRITE || + _mode == OF_TAR_ARCHIVE_MODE_APPEND) { + char buffer[1024]; + memset(buffer, '\0', 1024); + [_stream writeBuffer: buffer + length: 1024]; + } + + [_stream release]; + _stream = nil; +} @end @implementation OFTarArchive_FileReadStream - initWithEntry: (OFTarArchiveEntry *)entry stream: (OFStream *)stream @@ -219,10 +374,15 @@ - (bool)hasDataInReadBuffer { return ([super hasDataInReadBuffer] || [_stream hasDataInReadBuffer]); } + +- (int)fileDescriptorForReading +{ + return [_stream fileDescriptorForReading]; +} - (void)close { [_stream release]; _stream = nil; @@ -252,5 +412,91 @@ if (size % 512 != 0) [_stream readIntoBuffer: buffer exactLength: 512 - ((size_t)size % 512)]; } @end + +@implementation OFTarArchive_FileWriteStream +- initWithEntry: (OFTarArchiveEntry *)entry + stream: (OFStream *)stream +{ + self = [super init]; + + @try { + _entry = [entry copy]; + _stream = [stream retain]; + _toWrite = [entry size]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [self close]; + + [_entry release]; + + [super dealloc]; +} + +- (void)lowlevelWriteBuffer: (const void *)buffer + length: (size_t)length +{ + if (_stream == nil) + @throw [OFNotOpenException exceptionWithObject: self]; + + if ((uint64_t)length > _toWrite) + @throw [OFOutOfRangeException exception]; + + @try { + [_stream writeBuffer: buffer + length: length]; + } @catch (OFWriteFailedException *e) { + _toWrite -= [e bytesWritten]; + @throw e; + } + + _toWrite -= length; +} + +- (bool)lowlevelIsAtEndOfStream +{ + if (_stream == nil) + @throw [OFNotOpenException exceptionWithObject: self]; + + return (_toWrite == 0); +} + +- (int)fileDescriptorForWriting +{ + return [_stream fileDescriptorForWriting]; +} + +- (void)close +{ + uint64_t remainder = 512 - [_entry size] % 512; + + if (_toWrite > 0) + @throw [OFTruncatedDataException exception]; + + if (remainder != 512) { + bool wasWriteBuffered = [_stream isWriteBuffered]; + + [_stream setWriteBuffered: true]; + + while (remainder--) + [_stream writeInt8: 0]; + + [_stream flushWriteBuffer]; + [_stream setWriteBuffered: wasWriteBuffered]; + } + + [_stream release]; + _stream = nil; + + [super close]; +} +@end