xref: /haiku/src/bin/package/command_extract.cpp (revision d3ff06683af390a4c2e83b69177e0a2eb76679bc)
1 /*
2  * Copyright 2009-2011, 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 #include <String.h>
22 
23 #include <AutoDeleter.h>
24 #include <HashString.h>
25 
26 #include <util/OpenHashTable.h>
27 
28 #include <package/hpkg/PackageContentHandler.h>
29 #include <package/hpkg/PackageDataReader.h>
30 #include <package/hpkg/PackageEntry.h>
31 #include <package/hpkg/PackageEntryAttribute.h>
32 #include <package/hpkg/PackageReader.h>
33 #include <package/BlockBufferCacheNoLock.h>
34 
35 #include "package.h"
36 #include "StandardErrorOutput.h"
37 
38 
39 using namespace BPackageKit::BHPKG;
40 using BPackageKit::BBlockBufferCacheNoLock;
41 
42 
43 struct Entry {
44 	Entry(Entry* parent, char* name, bool implicit)
45 		:
46 		fParent(parent),
47 		fName(name),
48 		fImplicit(implicit),
49 		fSeen(false)
50 	{
51 	}
52 
53 	~Entry()
54 	{
55 		_DeleteChildren();
56 
57 		free(fName);
58 	}
59 
60 	status_t Init()
61 	{
62 		return fChildren.Init();
63 	}
64 
65 	static status_t Create(Entry* parent, const char* name, bool implicit,
66 		Entry*& _entry)
67 	{
68 		char* clonedName = strdup(name);
69 		if (clonedName == NULL)
70 			return B_NO_MEMORY;
71 
72 		Entry* entry = new(std::nothrow) Entry(parent, clonedName, implicit);
73 		if (entry == NULL) {
74 			free(clonedName);
75 			return B_NO_MEMORY;
76 		}
77 
78 		status_t error = entry->Init();
79 		if (error != B_OK) {
80 			delete entry;
81 			return error;
82 		}
83 
84 		if (parent != NULL)
85 			parent->fChildren.Insert(entry);
86 
87 		_entry = entry;
88 		return B_OK;
89 	}
90 
91 	Entry* Parent() const
92 	{
93 		return fParent;
94 	}
95 
96 	const char* Name() const
97 	{
98 		return fName;
99 	}
100 
101 	bool IsImplicit() const
102 	{
103 		return fImplicit;
104 	}
105 
106 	void SetExplicit()
107 	{
108 		// remove all children and set this entry non-implicit
109 		_DeleteChildren();
110 		fImplicit = false;
111 	}
112 
113 	void SetSeen()
114 	{
115 		fSeen = true;
116 	}
117 
118 	bool Seen() const
119 	{
120 		return fSeen;
121 	}
122 
123 	Entry* FindChild(const char* name) const
124 	{
125 		return fChildren.Lookup(name);
126 	}
127 
128 private:
129 	struct ChildHashDefinition {
130 		typedef const char*		KeyType;
131 		typedef	Entry			ValueType;
132 
133 		size_t HashKey(const char* key) const
134 		{
135 			return string_hash(key);
136 		}
137 
138 		size_t Hash(const Entry* value) const
139 		{
140 			return HashKey(value->Name());
141 		}
142 
143 		bool Compare(const char* key, const Entry* value) const
144 		{
145 			return strcmp(value->Name(), key) == 0;
146 		}
147 
148 		Entry*& GetLink(Entry* value) const
149 		{
150 			return value->fHashTableNext;
151 		}
152 	};
153 
154 	typedef BOpenHashTable<ChildHashDefinition> ChildTable;
155 
156 private:
157 	void _DeleteChildren()
158 	{
159 		Entry* child = fChildren.Clear(true);
160 		while (child != NULL) {
161 			Entry* next = child->fHashTableNext;
162 			delete child;
163 			child = next;
164 		}
165 	}
166 
167 public:
168 	Entry*		fHashTableNext;
169 
170 private:
171 	Entry*		fParent;
172 	char*		fName;
173 	bool		fImplicit;
174 	bool		fSeen;
175 	ChildTable	fChildren;
176 };
177 
178 
179 struct PackageContentExtractHandler : BPackageContentHandler {
180 	PackageContentExtractHandler(int packageFileFD)
181 		:
182 		fBufferCache(B_HPKG_DEFAULT_DATA_CHUNK_SIZE_ZLIB, 2),
183 		fPackageFileReader(packageFileFD),
184 		fDataBuffer(NULL),
185 		fDataBufferSize(0),
186 		fRootFilterEntry(NULL, NULL, true),
187 		fBaseDirectory(AT_FDCWD),
188 		fInfoFileName(NULL),
189 		fErrorOccurred(false)
190 	{
191 	}
192 
193 	~PackageContentExtractHandler()
194 	{
195 		free(fDataBuffer);
196 	}
197 
198 	status_t Init()
199 	{
200 		status_t error = fBufferCache.Init();
201 		if (error != B_OK)
202 			return error;
203 
204 		error = fRootFilterEntry.Init();
205 		if (error != B_OK)
206 			return error;
207 
208 		fDataBufferSize = 64 * 1024;
209 		fDataBuffer = malloc(fDataBufferSize);
210 		if (fDataBuffer == NULL)
211 			return B_NO_MEMORY;
212 
213 		return B_OK;
214 	}
215 
216 	void SetBaseDirectory(int fd)
217 	{
218 		fBaseDirectory = fd;
219 	}
220 
221 	void SetPackageInfoFile(const char* infoFileName)
222 	{
223 		fInfoFileName = infoFileName;
224 	}
225 
226 	void SetExtractAll()
227 	{
228 		fRootFilterEntry.SetExplicit();
229 	}
230 
231 	status_t AddFilterEntry(const char* fileName)
232 	{
233 		// add all components of the path
234 		Entry* entry = &fRootFilterEntry;
235 		while (*fileName != 0) {
236 			const char* nextSlash = strchr(fileName, '/');
237 			// no slash, just add the file name
238 			if (nextSlash == NULL) {
239 				return _AddFilterEntry(entry, fileName, strlen(fileName),
240 					false, entry);
241 			}
242 
243 			// find the start of the next component, skipping slashes
244 			const char* nextComponent = nextSlash + 1;
245 			while (*nextComponent == '/')
246 				nextComponent++;
247 
248 			status_t error = _AddFilterEntry(entry, fileName,
249 				nextSlash - fileName, *nextComponent != '\0', entry);
250 			if (error != B_OK)
251 				return error;
252 
253 			fileName = nextComponent;
254 		}
255 
256 		return B_OK;
257 	}
258 
259 	Entry* FindFilterEntry(const char* fileName)
260 	{
261 		// add all components of the path
262 		Entry* entry = &fRootFilterEntry;
263 		while (entry != NULL && *fileName != 0) {
264 			const char* nextSlash = strchr(fileName, '/');
265 			// no slash, just add the file name
266 			if (nextSlash == NULL)
267 				return entry->FindChild(fileName);
268 
269 			// find the start of the next component, skipping slashes
270 			const char* nextComponent = nextSlash + 1;
271 			while (*nextComponent == '/')
272 				nextComponent++;
273 
274 			BString componentName(fileName, nextSlash - fileName);
275 			entry = entry->FindChild(componentName);
276 
277 			fileName = nextComponent;
278 		}
279 
280 		return entry;
281 	}
282 
283 	virtual status_t HandleEntry(BPackageEntry* entry)
284 	{
285 		// create a token
286 		Token* token = new(std::nothrow) Token;
287 		if (token == NULL)
288 			return B_NO_MEMORY;
289 		ObjectDeleter<Token> tokenDeleter(token);
290 
291 		// check whether this entry shall be ignored or is implicit
292 		Entry* parentFilterEntry;
293 		bool implicit;
294 		if (entry->Parent() != NULL) {
295 			Token* parentToken = (Token*)entry->Parent()->UserToken();
296 			if (parentToken == NULL) {
297 				// parent is ignored, so ignore this entry, too
298 				return B_OK;
299 			}
300 
301 			parentFilterEntry = parentToken->filterEntry;
302 			implicit = parentToken->implicit;
303 		} else {
304 			parentFilterEntry = &fRootFilterEntry;
305 			implicit = fRootFilterEntry.IsImplicit();
306 		}
307 
308 		Entry* filterEntry = parentFilterEntry != NULL
309 			? parentFilterEntry->FindChild(entry->Name()) : NULL;
310 
311 		if (implicit && filterEntry == NULL) {
312 			// parent is implicit and the filter doesn't include this entry
313 			// -- ignore it
314 			return B_OK;
315 		}
316 
317 		// If the entry is in the filter, get its implicit flag.
318 		if (filterEntry != NULL) {
319 			implicit = filterEntry->IsImplicit();
320 			filterEntry->SetSeen();
321 		}
322 
323 		token->filterEntry = filterEntry;
324 		token->implicit = implicit;
325 
326 		// get parent FD and the entry name
327 		int parentFD;
328 		const char* entryName;
329 		_GetParentFDAndEntryName(entry, parentFD, entryName);
330 
331 		// check whether something is in the way
332 		struct stat st;
333 		bool entryExists = fstatat(parentFD, entryName, &st,
334 			AT_SYMLINK_NOFOLLOW) == 0;
335 		if (entryExists) {
336 			if (S_ISREG(entry->Mode()) || S_ISLNK(entry->Mode())) {
337 				// If the entry in the way is a regular file or a symlink,
338 				// remove it, otherwise fail.
339 				if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) {
340 					fprintf(stderr, "Error: Can't create entry \"%s\", since "
341 						"something is in the way\n",
342 						_EntryPath(entry).String());
343 					return B_FILE_EXISTS;
344 				}
345 
346 				if (unlinkat(parentFD, entryName, 0) != 0) {
347 					fprintf(stderr, "Error: Failed to unlink entry \"%s\": %s\n",
348 						_EntryPath(entry).String(), strerror(errno));
349 					return errno;
350 				}
351 
352 				entryExists = false;
353 			} else if (S_ISDIR(entry->Mode())) {
354 				// If the entry in the way is a directory, merge, otherwise
355 				// fail.
356 				if (!S_ISDIR(st.st_mode)) {
357 					fprintf(stderr, "Error: Can't create directory \"%s\", "
358 						"since something is in the way\n",
359 						_EntryPath(entry).String());
360 					return B_FILE_EXISTS;
361 				}
362 			}
363 		}
364 
365 		// create the entry
366 		int fd = -1;
367 		if (S_ISREG(entry->Mode())) {
368 			if (implicit) {
369 				fprintf(stderr, "Error: File \"%s\" was specified as a "
370 					"path directory component.\n", _EntryPath(entry).String());
371 				return B_BAD_VALUE;
372 			}
373 
374 			// create the file
375 			fd = openat(parentFD, entryName, O_RDWR | O_CREAT | O_EXCL,
376 				S_IRUSR | S_IWUSR);
377 				// Note: We use read+write user permissions now -- so write
378 				// operations (e.g. attributes) won't fail, but set them to the
379 				// desired ones in HandleEntryDone().
380 			if (fd < 0) {
381 				fprintf(stderr, "Error: Failed to create file \"%s\": %s\n",
382 					_EntryPath(entry).String(), strerror(errno));
383 				return errno;
384 			}
385 
386 			// write data
387 			status_t error;
388 			const BPackageData& data = entry->Data();
389 			if (data.IsEncodedInline()) {
390 				BBufferDataReader dataReader(data.InlineData(),
391 					data.CompressedSize());
392 				error = _ExtractFileData(&dataReader, data, fd);
393 			} else
394 				error = _ExtractFileData(&fPackageFileReader, data, fd);
395 
396 			if (error != B_OK)
397 				return error;
398 		} else if (S_ISLNK(entry->Mode())) {
399 			if (implicit) {
400 				fprintf(stderr, "Error: Symlink \"%s\" was specified as a "
401 					"path directory component.\n", _EntryPath(entry).String());
402 				return B_BAD_VALUE;
403 			}
404 
405 			// create the symlink
406 			const char* symlinkPath = entry->SymlinkPath();
407 			if (symlinkat(symlinkPath != NULL ? symlinkPath : "", parentFD,
408 					entryName) != 0) {
409 				fprintf(stderr, "Error: Failed to create symlink \"%s\": %s\n",
410 					_EntryPath(entry).String(), strerror(errno));
411 				return errno;
412 			}
413 // TODO: Set symlink permissions?
414  		} else if (S_ISDIR(entry->Mode())) {
415 			// create the directory, if necessary
416 			if (!entryExists
417 				&& mkdirat(parentFD, entryName, S_IRWXU) != 0) {
418 				// Note: We use read+write+exec user permissions now -- so write
419 				// operations (e.g. attributes) won't fail, but set them to the
420 				// desired ones in HandleEntryDone().
421 				fprintf(stderr, "Error: Failed to create directory \"%s\": "
422 					"%s\n", _EntryPath(entry).String(), strerror(errno));
423 				return errno;
424 			}
425 		} else {
426 			fprintf(stderr, "Error: Invalid file type for entry \"%s\"\n",
427 				_EntryPath(entry).String());
428 			return B_BAD_DATA;
429 		}
430 
431 		// If not done yet (symlink, dir), open the node -- we need the FD.
432 		if (fd < 0 && (!implicit || S_ISDIR(entry->Mode()))) {
433 			fd = openat(parentFD, entryName, O_RDONLY | O_NOTRAVERSE);
434 			if (fd < 0) {
435 				fprintf(stderr, "Error: Failed to open entry \"%s\": %s\n",
436 					_EntryPath(entry).String(), strerror(errno));
437 				return errno;
438 			}
439 		}
440 		token->fd = fd;
441 
442 		// set the file times
443 		if (!entryExists && !implicit) {
444 			timespec times[2] = {entry->AccessTime(), entry->ModifiedTime()};
445 			futimens(fd, times);
446 
447 			// set user/group
448 			// TODO:...
449 		}
450 
451 		entry->SetUserToken(tokenDeleter.Detach());
452 		return B_OK;
453 	}
454 
455 	virtual status_t HandleEntryAttribute(BPackageEntry* entry,
456 		BPackageEntryAttribute* attribute)
457 	{
458 		// don't write attributes of ignored or implicit entries
459 		Token* token = (Token*)entry->UserToken();
460 		if (token == NULL || token->implicit)
461 			return B_OK;
462 
463 		int entryFD = token->fd;
464 
465 		// create the attribute
466 		int fd = fs_fopen_attr(entryFD, attribute->Name(), attribute->Type(),
467 			O_WRONLY | O_CREAT | O_TRUNC);
468 		if (fd < 0) {
469 			int parentFD;
470 			const char* entryName;
471 			_GetParentFDAndEntryName(entry, parentFD, entryName);
472 
473 			fprintf(stderr, "Error: Failed to create attribute \"%s\" of "
474 				"file \"%s\": %s\n", attribute->Name(),
475 				_EntryPath(entry).String(), strerror(errno));
476 			return errno;
477 		}
478 
479 		// write data
480 		status_t error;
481 		const BPackageData& data = attribute->Data();
482 		if (data.IsEncodedInline()) {
483 			BBufferDataReader dataReader(data.InlineData(),
484 				data.CompressedSize());
485 			error = _ExtractFileData(&dataReader, data, fd);
486 		} else
487 			error = _ExtractFileData(&fPackageFileReader, data, fd);
488 
489 		fs_close_attr(fd);
490 
491 		return error;
492 	}
493 
494 	virtual status_t HandleEntryDone(BPackageEntry* entry)
495 	{
496 		Token* token = (Token*)entry->UserToken();
497 
498 		// set the node permissions for non-symlinks
499 		if (token != NULL && !S_ISLNK(entry->Mode())) {
500 			// get parent FD and entry name
501 			int parentFD;
502 			const char* entryName;
503 			_GetParentFDAndEntryName(entry, parentFD, entryName);
504 
505 			if (fchmodat(parentFD, entryName, entry->Mode() & ALLPERMS,
506 					/*AT_SYMLINK_NOFOLLOW*/0) != 0) {
507 				fprintf(stderr, "Warning: Failed to set permissions of file "
508 					"\"%s\": %s\n", _EntryPath(entry).String(),
509 					strerror(errno));
510 			}
511 		}
512 
513 		if (token != NULL) {
514 			delete token;
515 			entry->SetUserToken(NULL);
516 		}
517 
518 		return B_OK;
519 	}
520 
521 	virtual status_t HandlePackageAttribute(
522 		const BPackageInfoAttributeValue& value)
523 	{
524 		return B_OK;
525 	}
526 
527 	virtual void HandleErrorOccurred()
528 	{
529 		fErrorOccurred = true;
530 	}
531 
532 private:
533 	struct Token {
534 		Entry*	filterEntry;
535 		int		fd;
536 		bool	implicit;
537 
538 		Token()
539 			:
540 			filterEntry(NULL),
541 			fd(-1),
542 			implicit(true)
543 		{
544 		}
545 
546 		~Token()
547 		{
548 			if (fd >= 0)
549 				close(fd);
550 		}
551 	};
552 
553 private:
554 	status_t _AddFilterEntry(Entry* parentEntry, const char* _name,
555 		size_t nameLength, bool implicit, Entry*& _entry)
556 	{
557 		BString name(_name, nameLength);
558 		if (name.IsEmpty())
559 			return B_NO_MEMORY;
560 
561 		return Entry::Create(parentEntry, name.String(), implicit, _entry);
562 	}
563 
564 	void _GetParentFDAndEntryName(BPackageEntry* entry, int& _parentFD,
565 		const char*& _entryName)
566 	{
567 		_entryName = entry->Name();
568 
569 		if (fInfoFileName != NULL
570 			&& strcmp(_entryName, B_HPKG_PACKAGE_INFO_FILE_NAME) == 0) {
571 			_parentFD = AT_FDCWD;
572 			_entryName = fInfoFileName;
573 		} else {
574 			_parentFD = entry->Parent() != NULL
575 				? ((Token*)entry->Parent()->UserToken())->fd : fBaseDirectory;
576 		}
577 	}
578 
579 	BString _EntryPath(const BPackageEntry* entry)
580 	{
581 		BString path;
582 
583 		if (const BPackageEntry* parent = entry->Parent()) {
584 			path = _EntryPath(parent);
585 			path << '/';
586 		}
587 
588 		path << entry->Name();
589 		return path;
590 	}
591 
592 	status_t _ExtractFileData(BDataReader* dataReader, const BPackageData& data,
593 		int fd)
594 	{
595 		// create a BPackageDataReader
596 		BPackageDataReader* reader;
597 		status_t error = BPackageDataReaderFactory(&fBufferCache)
598 			.CreatePackageDataReader(dataReader, data, reader);
599 		if (error != B_OK)
600 			return error;
601 		ObjectDeleter<BPackageDataReader> readerDeleter(reader);
602 
603 		// write the data
604 		off_t bytesRemaining = data.UncompressedSize();
605 		off_t offset = 0;
606 		while (bytesRemaining > 0) {
607 			// read
608 			size_t toCopy = std::min((off_t)fDataBufferSize, bytesRemaining);
609 			error = reader->ReadData(offset, fDataBuffer, toCopy);
610 			if (error != B_OK) {
611 				fprintf(stderr, "Error: Failed to read data: %s\n",
612 					strerror(error));
613 				return error;
614 			}
615 
616 			// write
617 			ssize_t bytesWritten = write_pos(fd, offset, fDataBuffer, toCopy);
618 			if (bytesWritten < 0) {
619 				fprintf(stderr, "Error: Failed to write data: %s\n",
620 					strerror(errno));
621 				return errno;
622 			}
623 			if ((size_t)bytesWritten != toCopy) {
624 				fprintf(stderr, "Error: Failed to write all data (%zd of "
625 					"%zu)\n", bytesWritten, toCopy);
626 				return B_ERROR;
627 			}
628 
629 			offset += toCopy;
630 			bytesRemaining -= toCopy;
631 		}
632 
633 		return B_OK;
634 	}
635 
636 private:
637 	BBlockBufferCacheNoLock	fBufferCache;
638 	BFDDataReader			fPackageFileReader;
639 	void*					fDataBuffer;
640 	size_t					fDataBufferSize;
641 	Entry					fRootFilterEntry;
642 	int						fBaseDirectory;
643 	const char*				fInfoFileName;
644 	bool					fErrorOccurred;
645 };
646 
647 
648 int
649 command_extract(int argc, const char* const* argv)
650 {
651 	const char* changeToDirectory = NULL;
652 	const char* packageInfoFileName = NULL;
653 
654 	while (true) {
655 		static struct option sLongOptions[] = {
656 			{ "help", no_argument, 0, 'h' },
657 			{ 0, 0, 0, 0 }
658 		};
659 
660 		opterr = 0; // don't print errors
661 		int c = getopt_long(argc, (char**)argv, "+C:hi:", sLongOptions, NULL);
662 		if (c == -1)
663 			break;
664 
665 		switch (c) {
666 			case 'C':
667 				changeToDirectory = optarg;
668 				break;
669 
670 			case 'h':
671 				print_usage_and_exit(false);
672 				break;
673 
674 			case 'i':
675 				packageInfoFileName = optarg;
676 				break;
677 
678 			default:
679 				print_usage_and_exit(true);
680 				break;
681 		}
682 	}
683 
684 	// At least one argument should remain -- the package file name.
685 	if (optind + 1 > argc)
686 		print_usage_and_exit(true);
687 
688 	const char* packageFileName = argv[optind++];
689 
690 	// open package
691 	StandardErrorOutput errorOutput;
692 	BPackageReader packageReader(&errorOutput);
693 	status_t error = packageReader.Init(packageFileName);
694 	if (error != B_OK)
695 		return 1;
696 
697 	PackageContentExtractHandler handler(packageReader.PackageFileFD());
698 	error = handler.Init();
699 	if (error != B_OK)
700 		return 1;
701 
702 	// If entries to extract have been specified explicitly, add those to the
703 	// filtered ones.
704 	int explicitEntriesIndex = optind;
705 	if (optind < argc) {
706 		while (optind < argc) {
707 			const char* entryName = argv[optind++];
708 			if (entryName[0] == '\0' || entryName[0] == '/') {
709 				fprintf(stderr, "Error: Invalid entry name: \"%s\".",
710 					entryName);
711 				return 1;
712 			}
713 
714 			if (handler.AddFilterEntry(entryName) != B_OK)
715 				return 1;
716 		}
717 	} else
718 		handler.SetExtractAll();
719 
720 	// get the target directory, if requested
721 	if (changeToDirectory != NULL) {
722 		int currentDirFD = open(changeToDirectory, O_RDONLY);
723 		if (currentDirFD < 0) {
724 			fprintf(stderr, "Error: Failed to change the current working "
725 				"directory to \"%s\": %s\n", changeToDirectory,
726 				strerror(errno));
727 			return 1;
728 		}
729 
730 		handler.SetBaseDirectory(currentDirFD);
731 	}
732 
733 	// If a package info file name is given, set it.
734 	if (packageInfoFileName != NULL)
735 		handler.SetPackageInfoFile(packageInfoFileName);
736 
737 	// extract
738 	error = packageReader.ParseContent(&handler);
739 	if (error != B_OK)
740 		return 1;
741 
742 	// check whether all explicitly specified entries have been extracted
743 	if (explicitEntriesIndex < argc) {
744 		for (int i = explicitEntriesIndex; i < argc; i++) {
745 			if (Entry* entry = handler.FindFilterEntry(argv[i])) {
746 				if (!entry->Seen()) {
747 					fprintf(stderr, "Warning: Entry \"%s\" not found.\n",
748 						argv[i]);
749 				}
750 			}
751 		}
752 	}
753 
754 	return 0;
755 }
756