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