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
34TarArchiveService::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
50TarArchiveService::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
107tar_file_type
108TarArchiveService::_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
120TarArchiveService::_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
130void
131TarArchiveService::_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
143static 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
162TarArchiveService::_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
182TarArchiveService::_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
198TarArchiveService::_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
225TarArchiveService::_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