xref: /haiku/src/apps/haikudepot/tar/TarArchiveService.cpp (revision 1e60bdeab63fa7a57bc9a55b032052e95a18bd2c)
1 /*
2  * Copyright 2017-2018, Andrew Lindesay <apl@lindesay.co.nz>.
3  * All rights reserved. Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include "TarArchiveService.h"
8 
9 #include <stdio.h>
10 
11 #include <Directory.h>
12 #include <File.h>
13 #include <StringList.h>
14 
15 #include "Logger.h"
16 #include "StorageUtils.h"
17 
18 
19 #define LENGTH_BLOCK 512
20 
21 
22 status_t
23 TarArchiveService::Unpack(BDataIO& tarDataIo, BPath& targetDirectory,
24 	Stoppable* stoppable)
25 {
26 	uint8 buffer[LENGTH_BLOCK];
27 	uint8 zero_buffer[LENGTH_BLOCK];
28 	status_t result = B_OK;
29 	uint32_t count_items_read = 0;
30 
31 	fprintf(stdout, "will unpack to [%s]\n", targetDirectory.Path());
32 
33 	memset(zero_buffer, 0, sizeof zero_buffer);
34 
35 	while (B_OK == result
36 		&& (NULL == stoppable || !stoppable->WasStopped())
37 		&& B_OK == (result = tarDataIo.ReadExactly(buffer, LENGTH_BLOCK))) {
38 
39 		count_items_read++;
40 
41 		if (0 == memcmp(zero_buffer, buffer, sizeof zero_buffer)) {
42 			if (Logger::IsDebugEnabled())
43 				printf("detected end of tar-ball\n");
44 			return B_OK; // end of tar-ball.
45 		} else {
46 			TarArchiveHeader* header = TarArchiveHeader::CreateFromBlock(
47 				buffer);
48 
49 			if (NULL == header) {
50 				fprintf(stderr, "unable to parse a tar header\n");
51 				result = B_ERROR;
52 			}
53 
54 			if (B_OK == result)
55 				result = _UnpackItem(tarDataIo, targetDirectory, *header);
56 
57 			delete header;
58 		}
59 	}
60 
61 	fprintf(stdout, "did unpack %d tar items\n", count_items_read);
62 
63 	if (B_OK != result) {
64 		fprintf(stdout, "error occurred unpacking tar items; %s\n",
65 			strerror(result));
66 	}
67 
68 	return result;
69 }
70 
71 
72 status_t
73 TarArchiveService::_EnsurePathToTarItemFile(
74 	BPath& targetDirectoryPath, BString &tarItemPath)
75 {
76 	if (tarItemPath.Length() == 0)
77 		return B_ERROR;
78 
79 	BStringList components;
80 	tarItemPath.Split("/", false, components);
81 
82 	for (int32 i = 0; i < components.CountStrings(); i++) {
83 		BString component = components.StringAt(i);
84 
85 		if (_ValidatePathComponent(component) != B_OK) {
86 			fprintf(stdout, "malformed component; [%s]\n", component.String());
87 			return B_ERROR;
88 		}
89 	}
90 
91 	BPath parentPath;
92 	BPath assembledPath(targetDirectoryPath);
93 
94 	status_t result = assembledPath.Append(tarItemPath);
95 
96 	if (result == B_OK)
97 		result = assembledPath.GetParent(&parentPath);
98 
99 	if (result == B_OK)
100 		result = create_directory(parentPath.Path(), 0777);
101 
102 	return result;
103 }
104 
105 
106 status_t
107 TarArchiveService::_UnpackItem(BDataIO& tarDataIo,
108 		BPath& targetDirectoryPath,
109 		TarArchiveHeader& header)
110 {
111 	status_t result = B_OK;
112 	BString entryFileName = header.GetFileName();
113 	uint32 entryLength = header.GetLength();
114 
115 	if (Logger::IsDebugEnabled()) {
116 		fprintf(stdout, "will unpack item [%s] length [%" B_PRIu32 "]b\n",
117 			entryFileName.String(), entryLength);
118 	}
119 
120 	// if the path ends in "/" then it is a directory and there's no need to
121 	// unpack it although if there is a length, it will need to be skipped.
122 
123 	if (!entryFileName.EndsWith("/") ||
124 			header.GetFileType() != TAR_FILE_TYPE_NORMAL) {
125 
126 		result = _EnsurePathToTarItemFile(targetDirectoryPath,
127 			entryFileName);
128 
129 		if (result == B_OK) {
130 			BPath targetFilePath(targetDirectoryPath);
131 			targetFilePath.Append(entryFileName, false);
132 			result = _UnpackItemData(tarDataIo, targetFilePath, entryLength);
133 		}
134 	} else {
135 		off_t blocksToSkip = (entryLength / LENGTH_BLOCK);
136 
137 		if (entryLength % LENGTH_BLOCK > 0)
138 			blocksToSkip += 1;
139 
140 		if (0 != blocksToSkip) {
141 			uint8 buffer[LENGTH_BLOCK];
142 
143 			for (uint32 i = 0; B_OK == result && i < blocksToSkip; i++)
144 				tarDataIo.ReadExactly(buffer, LENGTH_BLOCK);
145 		}
146 	}
147 
148 	return result;
149 }
150 
151 
152 status_t
153 TarArchiveService::_UnpackItemData(BDataIO& tarDataIo,
154 	BPath& targetFilePath, uint32 length)
155 {
156 	uint8 buffer[LENGTH_BLOCK];
157 	size_t remainingInItem = length;
158 	status_t result = B_OK;
159 	BFile targetFile(targetFilePath.Path(), O_WRONLY | O_CREAT);
160 
161 	while (remainingInItem > 0 &&
162 		B_OK == result &&
163 		B_OK == (result = tarDataIo.ReadExactly(buffer, LENGTH_BLOCK))) {
164 
165 		size_t writeFromBuffer = LENGTH_BLOCK;
166 
167 		if (remainingInItem < LENGTH_BLOCK)
168 			writeFromBuffer = remainingInItem;
169 
170 		result = targetFile.WriteExactly(buffer, writeFromBuffer);
171 		remainingInItem -= writeFromBuffer;
172 	}
173 
174 	if (result != B_OK)
175 		fprintf(stdout, "unable to unpack item data to; [%s]\n",
176 			targetFilePath.Path());
177 
178 	return result;
179 }
180 
181 
182 status_t
183 TarArchiveService::_ValidatePathComponent(const BString& component)
184 {
185 	if (component.Length() == 0)
186 		return	B_ERROR;
187 
188 	if (component == ".." || component == "." || component == "~")
189 		return B_ERROR;
190 
191 	return B_OK;
192 }
193 
194