xref: /haiku/src/bin/package/command_extract.cpp (revision 5fb1c6ff1f55fe4094a761b653041b3a0b9abf1d)
1 /*
2  * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include <ctype.h>
8 #include <fcntl.h>
9 #include <errno.h>
10 #include <getopt.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <sys/stat.h>
15 #include <unistd.h>
16 
17 #include <algorithm>
18 #include <new>
19 
20 #include <fs_attr.h>
21 
22 #include <AutoDeleter.h>
23 
24 #include <package/hpkg/FDCloser.h>
25 #include <package/hpkg/PackageContentHandler.h>
26 #include <package/hpkg/PackageDataReader.h>
27 #include <package/hpkg/PackageEntry.h>
28 #include <package/hpkg/PackageEntryAttribute.h>
29 #include <package/hpkg/PackageReader.h>
30 
31 #include "BlockBufferCacheNoLock.h"
32 #include "package.h"
33 #include "StandardErrorOutput.h"
34 
35 
36 using namespace BPackageKit::BHPKG;
37 
38 
39 struct PackageContentExtractHandler : BPackageContentHandler {
40 	PackageContentExtractHandler(int packageFileFD)
41 		:
42 		fBufferCache(B_HPKG_DEFAULT_DATA_CHUNK_SIZE_ZLIB, 2),
43 		fPackageFileReader(packageFileFD),
44 		fDataBuffer(NULL),
45 		fDataBufferSize(0),
46 		fErrorOccurred(false)
47 	{
48 	}
49 
50 	~PackageContentExtractHandler()
51 	{
52 		free(fDataBuffer);
53 	}
54 
55 	status_t Init()
56 	{
57 		status_t error = fBufferCache.Init();
58 		if (error != B_OK)
59 			return error;
60 
61 		fDataBufferSize = 64 * 1024;
62 		fDataBuffer = malloc(fDataBufferSize);
63 		if (fDataBuffer == NULL)
64 			return B_NO_MEMORY;
65 
66 		return B_OK;
67 	}
68 
69 	virtual status_t HandleEntry(BPackageEntry* entry)
70 	{
71 		// create a token
72 		Token* token = new(std::nothrow) Token;
73 		if (token == NULL)
74 			return B_NO_MEMORY;
75 		ObjectDeleter<Token> tokenDeleter(token);
76 
77 		// get parent FD
78 		int parentFD = AT_FDCWD;
79 		if (entry->Parent() != NULL)
80 			parentFD = ((Token*)entry->Parent()->UserToken())->fd;
81 
82 		// check whether something is in the way
83 		struct stat st;
84 		bool entryExists = fstatat(parentFD, entry->Name(), &st,
85 			AT_SYMLINK_NOFOLLOW) == 0;
86 		if (entryExists) {
87 			if (S_ISREG(entry->Mode()) || S_ISLNK(entry->Mode())) {
88 				// If the entry in the way is a regular file or a symlink,
89 				// remove it, otherwise fail.
90 				if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) {
91 					fprintf(stderr, "Error: Can't create entry \"%s\", since "
92 						"something is in the way\n", entry->Name());
93 					return B_FILE_EXISTS;
94 				}
95 
96 				if (unlinkat(parentFD, entry->Name(), 0) != 0) {
97 					fprintf(stderr, "Error: Failed to unlink entry \"%s\": %s\n",
98 						entry->Name(), strerror(errno));
99 					return errno;
100 				}
101 
102 				entryExists = false;
103 			} else if (S_ISDIR(entry->Mode())) {
104 				// If the entry in the way is a directory, merge, otherwise
105 				// fail.
106 				if (!S_ISDIR(st.st_mode)) {
107 					fprintf(stderr, "Error: Can't create directory \"%s\", "
108 						"since something is in the way\n", entry->Name());
109 					return B_FILE_EXISTS;
110 				}
111 			}
112 		}
113 
114 		// create the entry
115 		int fd = -1;
116 		if (S_ISREG(entry->Mode())) {
117 			// create the file
118 			fd = openat(parentFD, entry->Name(), O_RDWR | O_CREAT | O_EXCL,
119 				entry->Mode() & ALLPERMS);
120 			if (fd < 0) {
121 				fprintf(stderr, "Error: Failed to create file \"%s\": %s\n",
122 					entry->Name(), strerror(errno));
123 				return errno;
124 			}
125 
126 			// write data
127 			status_t error;
128 			const BPackageData& data = entry->Data();
129 			if (data.IsEncodedInline()) {
130 				BBufferDataReader dataReader(data.InlineData(),
131 					data.CompressedSize());
132 				error = _ExtractFileData(&dataReader, data, fd);
133 			} else
134 				error = _ExtractFileData(&fPackageFileReader, data, fd);
135 
136 			if (error != B_OK)
137 				return error;
138 		} else if (S_ISLNK(entry->Mode())) {
139 			// create the symlink
140 			const char* symlinkPath = entry->SymlinkPath();
141 			if (symlinkat(symlinkPath != NULL ? symlinkPath : "", parentFD,
142 					entry->Name()) != 0) {
143 				fprintf(stderr, "Error: Failed to create symlink \"%s\": %s\n",
144 					entry->Name(), strerror(errno));
145 				return errno;
146 			}
147 // TODO: Set symlink permissions?
148  		} else if (S_ISDIR(entry->Mode())) {
149 			// create the directory, if necessary
150 			if (!entryExists
151 				&& mkdirat(parentFD, entry->Name(), entry->Mode() & ALLPERMS)
152 					!= 0) {
153 				fprintf(stderr, "Error: Failed to create directory \"%s\": "
154 					"%s\n", entry->Name(), strerror(errno));
155 				return errno;
156 			}
157 		} else {
158 			fprintf(stderr, "Error: Invalid file type for entry \"%s\"\n",
159 				entry->Name());
160 			return B_BAD_DATA;
161 		}
162 
163 		// If not done yet (symlink, dir), open the node -- we need the FD.
164 		if (fd < 0) {
165 			fd = openat(parentFD, entry->Name(), O_RDONLY | O_NOTRAVERSE);
166 			if (fd < 0) {
167 				fprintf(stderr, "Error: Failed to open entry \"%s\": %s\n",
168 					entry->Name(), strerror(errno));
169 				return errno;
170 			}
171 		}
172 		token->fd = fd;
173 
174 		// set the file times
175 		if (!entryExists) {
176 			timespec times[2] = {entry->AccessTime(), entry->ModifiedTime()};
177 			futimens(fd, times);
178 
179 			// set user/group
180 			// TODO:...
181 		}
182 
183 		entry->SetUserToken(tokenDeleter.Detach());
184 		return B_OK;
185 	}
186 
187 	virtual status_t HandleEntryAttribute(BPackageEntry* entry,
188 		BPackageEntryAttribute* attribute)
189 	{
190 		int entryFD = ((Token*)entry->UserToken())->fd;
191 
192 		// create the attribute
193 		int fd = fs_fopen_attr(entryFD, attribute->Name(), attribute->Type(),
194 			O_WRONLY | O_CREAT | O_TRUNC);
195 		if (fd < 0) {
196 				fprintf(stderr, "Error: Failed to create attribute \"%s\" of "
197 					"file \"%s\": %s\n", attribute->Name(), entry->Name(),
198 					strerror(errno));
199 				return errno;
200 		}
201 		FDCloser fdCloser(fd);
202 
203 		// write data
204 		status_t error;
205 		const BPackageData& data = attribute->Data();
206 		if (data.IsEncodedInline()) {
207 			BBufferDataReader dataReader(data.InlineData(),
208 				data.CompressedSize());
209 			error = _ExtractFileData(&dataReader, data, fd);
210 		} else
211 			error = _ExtractFileData(&fPackageFileReader, data, fd);
212 
213 		if (error != B_OK)
214 			return error;
215 
216 		return B_OK;
217 	}
218 
219 	virtual status_t HandleEntryDone(BPackageEntry* entry)
220 	{
221 		if (Token* token = (Token*)entry->UserToken()) {
222 			delete token;
223 			entry->SetUserToken(NULL);
224 		}
225 
226 		return B_OK;
227 	}
228 
229 	virtual void HandleErrorOccurred()
230 	{
231 		fErrorOccurred = true;
232 	}
233 
234 private:
235 	struct Token {
236 		int	fd;
237 
238 		Token()
239 			:
240 			fd(-1)
241 		{
242 		}
243 
244 		~Token()
245 		{
246 			if (fd >= 0)
247 				close(fd);
248 		}
249 	};
250 
251 private:
252 	status_t _ExtractFileData(BDataReader* dataReader, const BPackageData& data,
253 		int fd)
254 	{
255 		// create a BPackageDataReader
256 		BPackageDataReader* reader;
257 		status_t error = BPackageDataReaderFactory(&fBufferCache)
258 			.CreatePackageDataReader(dataReader, data, reader);
259 		if (error != B_OK)
260 			return error;
261 		ObjectDeleter<BPackageDataReader> readerDeleter(reader);
262 
263 		// write the data
264 		off_t bytesRemaining = data.UncompressedSize();
265 		off_t offset = 0;
266 		while (bytesRemaining > 0) {
267 			// read
268 			size_t toCopy = std::min((off_t)fDataBufferSize, bytesRemaining);
269 			error = reader->ReadData(offset, fDataBuffer, toCopy);
270 			if (error != B_OK) {
271 				fprintf(stderr, "Error: Failed to read data: %s\n",
272 					strerror(error));
273 				return error;
274 			}
275 
276 			// write
277 			ssize_t bytesWritten = pwrite(fd, fDataBuffer, toCopy, offset);
278 			if (bytesWritten < 0) {
279 				fprintf(stderr, "Error: Failed to write data: %s\n",
280 					strerror(errno));
281 				return errno;
282 			}
283 			if ((size_t)bytesWritten != toCopy) {
284 				fprintf(stderr, "Error: Failed to write all data\n");
285 				return B_ERROR;
286 			}
287 
288 			offset += toCopy;
289 			bytesRemaining -= toCopy;
290 		}
291 
292 		return B_OK;
293 	}
294 
295 private:
296 	BlockBufferCacheNoLock	fBufferCache;
297 	BFDDataReader			fPackageFileReader;
298 	void*					fDataBuffer;
299 	size_t					fDataBufferSize;
300 	bool					fErrorOccurred;
301 };
302 
303 
304 int
305 command_extract(int argc, const char* const* argv)
306 {
307 	const char* changeToDirectory = NULL;
308 
309 	while (true) {
310 		static struct option sLongOptions[] = {
311 			{ "help", no_argument, 0, 'h' },
312 			{ 0, 0, 0, 0 }
313 		};
314 
315 		opterr = 0; // don't print errors
316 		int c = getopt_long(argc, (char**)argv, "+C:h", sLongOptions, NULL);
317 		if (c == -1)
318 			break;
319 
320 		switch (c) {
321 			case 'C':
322 				changeToDirectory = optarg;
323 				break;
324 
325 			case 'h':
326 				print_usage_and_exit(false);
327 				break;
328 
329 			default:
330 				print_usage_and_exit(true);
331 				break;
332 		}
333 	}
334 
335 	// One argument should remain -- the package file name.
336 	if (optind + 1 != argc)
337 		print_usage_and_exit(true);
338 
339 	const char* packageFileName = argv[optind++];
340 
341 	// open package
342 	StandardErrorOutput errorOutput;
343 	BPackageReader packageReader(&errorOutput);
344 	status_t error = packageReader.Init(packageFileName);
345 printf("Init(): %s\n", strerror(error));
346 	if (error != B_OK)
347 		return 1;
348 
349 	// change directory, if requested
350 	if (changeToDirectory != NULL) {
351 		if (chdir(changeToDirectory) != 0) {
352 			fprintf(stderr, "Error: Failed to change the current working "
353 				"directory to \"%s\": %s\n", changeToDirectory,
354 				strerror(errno));
355 		}
356 	}
357 
358 	// extract
359 	PackageContentExtractHandler handler(packageReader.PackageFileFD());
360 	error = handler.Init();
361 	if (error != B_OK)
362 		return 1;
363 
364 	error = packageReader.ParseContent(&handler);
365 printf("ParseContent(): %s\n", strerror(error));
366 	if (error != B_OK)
367 		return 1;
368 
369 	return 0;
370 }
371