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
GetEntry(BPositionIO & tarIo,TarArchiveHeader & header)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
ForEachEntry(BPositionIO & tarIo,TarEntryListener * listener)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
_ReadHeaderFileType(unsigned char data)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
_ReadHeaderStringLength(const uint8 * data,size_t maxStringLength)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
_ReadHeaderString(const uint8 * data,size_t maxStringLength,BString & result)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
tar_is_octal_digit(unsigned char c)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
_ReadHeaderNumeric(const uint8 * data,size_t dataLength)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
_CalculateBlockChecksum(const uint8 * block)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
_ReadHeader(const uint8 * block,TarArchiveHeader & header)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
_BytesRoundedToBlocks(off_t value)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