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
44using BPackageKit::BHPKG::BAbstractBufferedDataReader;
45using BPackageKit::BHPKG::BBlockBufferPoolNoLock;
46using BPackageKit::BHPKG::BBufferDataReader;
47using BPackageKit::BHPKG::BBufferPool;
48using BPackageKit::BHPKG::BDataReader;
49using BPackageKit::BHPKG::BErrorOutput;
50using BPackageKit::BHPKG::BFDDataReader;
51using BPackageKit::BHPKG::BPackageInfoAttributeValue;
52using BPackageKit::BHPKG::BStandardErrorOutput;
53
54
55struct 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
109struct 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
163struct 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
256private:
257	struct ChildHashDefinition {
258		typedef const char*		KeyType;
259		typedef	Entry			ValueType;
260
261		size_t HashKey(const char* key) const
262		{
263			return string_hash(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
284private:
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
295public:
296	Entry*		fHashTableNext;
297
298private:
299	Entry*		fParent;
300	char*		fName;
301	bool		fImplicit;
302	bool		fSeen;
303	ChildTable	fChildren;
304};
305
306
307template<typename VersionPolicy>
308struct 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
647private:
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
668private:
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
753private:
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
765template<typename VersionPolicy>
766static void
767do_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
859int
860command_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