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
39using namespace BPackageKit::BHPKG;
40using BPackageKit::BBlockBufferCacheNoLock;
41
42
43struct 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
128private:
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
156private:
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
167public:
168	Entry*		fHashTableNext;
169
170private:
171	Entry*		fParent;
172	char*		fName;
173	bool		fImplicit;
174	bool		fSeen;
175	ChildTable	fChildren;
176};
177
178
179struct 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
532private:
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
553private:
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
636private:
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
648int
649command_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