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