1 /* 2 * Copyright 2017-2020, Andrew Lindesay <apl@lindesay.co.nz>. 3 * All rights reserved. Distributed under the terms of the MIT License. 4 */ 5 #include "TarArchiveService.h" 6 7 #include <AutoDeleter.h> 8 #include <Directory.h> 9 #include <File.h> 10 #include <StringList.h> 11 12 #include "DataIOUtils.h" 13 #include "Logger.h" 14 #include "StorageUtils.h" 15 16 17 #define OFFSET_FILENAME 0 18 #define OFFSET_LENGTH 124 19 #define OFFSET_CHECKSUM 148 20 #define OFFSET_FILETYPE 156 21 22 #define LENGTH_FILENAME 100 23 #define LENGTH_LENGTH 12 24 #define LENGTH_CHECKSUM 8 25 #define LENGTH_BLOCK 512 26 27 28 /*! This method will parse the header that is located at the current position of 29 the supplied tarIo. Upon success, the tarIo will point to the start of the 30 data for the parsed entry. 31 */ 32 33 /*static*/ status_t 34 TarArchiveService::GetEntry(BPositionIO& tarIo, TarArchiveHeader& header) 35 { 36 uint8 buffer[LENGTH_BLOCK]; 37 status_t result = tarIo.ReadExactly(buffer, LENGTH_BLOCK); 38 if (result == B_OK) 39 result = _ReadHeader(buffer, header); 40 return result; 41 } 42 43 44 /*! For each entry in the tar file, this method will call the listener. If the 45 listener does not return B_OK then the process will stop. This method is 46 useful for obtaining a catalog of items in the tar file for example. 47 */ 48 49 /*status*/ status_t 50 TarArchiveService::ForEachEntry(BPositionIO& tarIo, TarEntryListener* listener) 51 { 52 uint8 buffer[LENGTH_BLOCK]; 53 uint8 zero_buffer[LENGTH_BLOCK]; 54 status_t result = B_OK; 55 uint32 countItemsRead = 0; 56 off_t offset = 0; 57 58 memset(zero_buffer, 0, sizeof zero_buffer); 59 tarIo.Seek(offset, SEEK_SET); 60 61 while (result == B_OK 62 && B_OK == (result = tarIo.ReadExactly( 63 buffer, LENGTH_BLOCK))) { 64 65 if (memcmp(zero_buffer, buffer, sizeof zero_buffer) == 0) { 66 HDDEBUG("detected end of tar-ball after %" B_PRIu32 " items", 67 countItemsRead); 68 return B_OK; // end of tar-ball. 69 } 70 71 TarArchiveHeader header; 72 result = _ReadHeader(buffer, header); 73 74 HDTRACE("did read tar entry header for [%s]", 75 header.FileName().String()); 76 77 if (result == B_OK) { 78 countItemsRead++; 79 80 // call out to the listener to read the data from the entry 81 // and/or just process the header information. 82 83 if (listener != NULL) { 84 BDataIO *entryData = new ConstraintedDataIO(&tarIo, 85 header.Length()); 86 ObjectDeleter<BDataIO> entryDataDeleter(entryData); 87 result = listener->Handle(header, offset, entryData); 88 } 89 } 90 91 offset += LENGTH_BLOCK; 92 offset += _BytesRoundedToBlocks(header.Length()); 93 94 if (result == B_OK) 95 tarIo.Seek(offset, SEEK_SET); 96 } 97 98 if (result == B_OK || result == B_CANCELED) 99 HDINFO("did list %" B_PRIu32 " tar items", countItemsRead); 100 else 101 HDERROR("error occurred listing tar items; %s", strerror(result)); 102 103 return result; 104 } 105 106 107 tar_file_type 108 TarArchiveService::_ReadHeaderFileType(unsigned char data) { 109 switch (data) { 110 case 0: 111 case '0': 112 return TAR_FILE_TYPE_NORMAL; 113 default: 114 return TAR_FILE_TYPE_OTHER; 115 } 116 } 117 118 119 /*static*/ int32 120 TarArchiveService::_ReadHeaderStringLength(const uint8* data, 121 size_t maxStringLength) 122 { 123 int32 actualLength = 0; 124 while (actualLength < (int32) maxStringLength && data[actualLength] != 0) 125 actualLength++; 126 return actualLength; 127 } 128 129 130 void 131 TarArchiveService::_ReadHeaderString(const uint8 *data, size_t maxStringLength, 132 BString& result) 133 { 134 result.SetTo((const char *) data, 135 _ReadHeaderStringLength(data, maxStringLength)); 136 } 137 138 139 /*! This function will return true if the character supplied is a valid 140 character in an number expressed in octal. 141 */ 142 143 static bool tar_is_octal_digit(unsigned char c) 144 { 145 switch (c) { 146 case '0': 147 case '1': 148 case '2': 149 case '3': 150 case '4': 151 case '5': 152 case '6': 153 case '7': 154 return true; 155 default: 156 return false; 157 } 158 } 159 160 161 /*static*/ uint32 162 TarArchiveService::_ReadHeaderNumeric(const uint8 *data, size_t dataLength) 163 { 164 uint32 actualLength = 0; 165 166 while (actualLength < dataLength && tar_is_octal_digit(data[actualLength])) 167 actualLength++; 168 169 uint32 factor = 1; 170 uint32 result = 0; 171 172 for (uint32 i = 0; i < actualLength; i++) { 173 result += (data[actualLength - (1 + i)] - '0') * factor; 174 factor *= 8; 175 } 176 177 return result; 178 } 179 180 181 /*static*/ uint32 182 TarArchiveService::_CalculateBlockChecksum(const uint8* block) 183 { 184 uint32 result = 0; 185 186 for (uint32 i = 0; i < LENGTH_BLOCK; i++) { 187 if (i >= OFFSET_CHECKSUM && i < OFFSET_CHECKSUM + LENGTH_CHECKSUM) 188 result += 32; 189 else 190 result += (uint32) block[i]; 191 } 192 193 return result; 194 } 195 196 197 /*static*/ status_t 198 TarArchiveService::_ReadHeader(const uint8* block, TarArchiveHeader& header) 199 { 200 uint32 actualChecksum = _CalculateBlockChecksum(block); 201 uint32 expectedChecksum = _ReadHeaderNumeric(&block[OFFSET_CHECKSUM], 202 LENGTH_CHECKSUM); 203 204 if(actualChecksum != expectedChecksum) { 205 HDERROR("tar archive header has bad checksum;" 206 "expected %" B_PRIu32 " actual %" B_PRIu32, 207 expectedChecksum, actualChecksum); 208 return B_BAD_DATA; 209 } 210 211 BString fileName; 212 _ReadHeaderString(&block[OFFSET_FILENAME], LENGTH_FILENAME, fileName); 213 214 header.SetFileName(fileName); 215 header.SetLength( 216 _ReadHeaderNumeric(&block[OFFSET_LENGTH], LENGTH_LENGTH)); 217 header.SetFileType( 218 _ReadHeaderFileType(block[OFFSET_FILETYPE])); 219 220 return B_OK; 221 } 222 223 224 /*static*/ off_t 225 TarArchiveService::_BytesRoundedToBlocks(off_t value) 226 { 227 if (0 != value % LENGTH_BLOCK) 228 return ((value / LENGTH_BLOCK) + 1) * LENGTH_BLOCK; 229 return (value / LENGTH_BLOCK) * LENGTH_BLOCK; 230 } 231