xref: /haiku/src/apps/haikudepot/tar/TarArchiveService.cpp (revision dd2a1e350b303b855a50fd64e6cb55618be1ae6a)
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