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 <errno.h>
9#include <getopt.h>
10#include <stdio.h>
11#include <stdlib.h>
12#include <string.h>
13#include <time.h>
14
15#include <Entry.h>
16#include <package/hpkg/PackageContentHandler.h>
17#include <package/hpkg/PackageEntry.h>
18#include <package/hpkg/PackageEntryAttribute.h>
19#include <package/hpkg/PackageInfoAttributeValue.h>
20#include <package/hpkg/PackageReader.h>
21#include <package/hpkg/StandardErrorOutput.h>
22#include <package/hpkg/v1/PackageContentHandler.h>
23#include <package/hpkg/v1/PackageEntry.h>
24#include <package/hpkg/v1/PackageEntryAttribute.h>
25#include <package/hpkg/v1//PackageReader.h>
26
27#include <package/PackageInfo.h>
28
29#include "package.h"
30#include "PackageInfoPrinter.h"
31
32
33using namespace BPackageKit;
34using BPackageKit::BHPKG::BErrorOutput;
35using BPackageKit::BHPKG::BPackageInfoAttributeValue;
36using BPackageKit::BHPKG::BStandardErrorOutput;
37
38
39struct VersionPolicyV1 {
40	typedef BPackageKit::BHPKG::V1::BPackageContentHandler
41		PackageContentHandler;
42	typedef BPackageKit::BHPKG::V1::BPackageEntry PackageEntry;
43	typedef BPackageKit::BHPKG::V1::BPackageEntryAttribute
44		PackageEntryAttribute;
45	typedef BPackageKit::BHPKG::V1::BPackageReader PackageReader;
46
47	static inline uint64 PackageDataSize(
48		const BPackageKit::BHPKG::V1::BPackageData& data)
49	{
50		return data.UncompressedSize();
51	}
52
53	static inline status_t InitReader(PackageReader& packageReader,
54		const char* fileName)
55	{
56		return packageReader.Init(fileName);
57	}
58};
59
60struct VersionPolicyV2 {
61	typedef BPackageKit::BHPKG::BPackageContentHandler PackageContentHandler;
62	typedef BPackageKit::BHPKG::BPackageEntry PackageEntry;
63	typedef BPackageKit::BHPKG::BPackageEntryAttribute PackageEntryAttribute;
64	typedef BPackageKit::BHPKG::BPackageReader PackageReader;
65
66	static inline uint64 PackageDataSize(
67		const BPackageKit::BHPKG::BPackageData& data)
68	{
69		return data.Size();
70	}
71
72	static inline status_t InitReader(PackageReader& packageReader,
73		const char* fileName)
74	{
75		return packageReader.Init(fileName,
76			BPackageKit::BHPKG
77				::B_HPKG_READER_DONT_PRINT_VERSION_MISMATCH_MESSAGE);
78	}
79};
80
81
82enum ListMode {
83	LIST_ALL,
84	LIST_PATHS_ONLY,
85	LIST_META_INFO_ONLY
86};
87
88
89template<typename VersionPolicy>
90struct PackageContentListHandler : VersionPolicy::PackageContentHandler {
91	PackageContentListHandler(bool listEntries, bool listAttributes)
92		:
93		fPrinter(),
94		fLevel(0),
95		fListEntries(listEntries),
96		fListAttribute(listEntries && listAttributes)
97	{
98	}
99
100	virtual status_t HandleEntry(typename VersionPolicy::PackageEntry* entry)
101	{
102		if (!fListEntries)
103			return B_OK;
104
105		fLevel++;
106
107		int indentation = (fLevel - 1) * 2;
108		printf("%*s", indentation, "");
109
110		// name and size
111		printf("%-*s", indentation < 32 ? 32 - indentation : 0, entry->Name());
112		printf("  %8llu",
113			(unsigned long long)VersionPolicy::PackageDataSize(entry->Data()));
114
115		// time
116		struct tm* time = localtime(&entry->ModifiedTime().tv_sec);
117		printf("  %04d-%02d-%02d %02d:%02d:%02d",
118			1900 + time->tm_year, time->tm_mon + 1, time->tm_mday,
119			time->tm_hour, time->tm_min, time->tm_sec);
120
121		// file type
122		mode_t mode = entry->Mode();
123		if (S_ISREG(mode))
124			printf("  -");
125		else if (S_ISDIR(mode))
126			printf("  d");
127		else if (S_ISLNK(mode))
128			printf("  l");
129		else
130			printf("  ?");
131
132		// permissions
133		char buffer[4];
134		printf("%s", _PermissionString(buffer, mode >> 6,
135			(mode & S_ISUID) != 0));
136		printf("%s", _PermissionString(buffer, mode >> 3,
137			(mode & S_ISGID) != 0));
138		printf("%s", _PermissionString(buffer, mode, false));
139
140		// print the symlink path
141		if (S_ISLNK(mode))
142			printf("  -> %s", entry->SymlinkPath());
143
144		printf("\n");
145		return B_OK;
146	}
147
148	virtual status_t HandleEntryAttribute(
149		typename VersionPolicy::PackageEntry* entry,
150		typename VersionPolicy::PackageEntryAttribute* attribute)
151	{
152		if (!fListAttribute)
153			return B_OK;
154
155		int indentation = fLevel * 2;
156		printf("%*s<", indentation, "");
157		printf("%-*s  %8llu", indentation < 31 ? 31 - indentation : 0,
158			attribute->Name(),
159			(unsigned long long)VersionPolicy::PackageDataSize(
160				attribute->Data()));
161
162		uint32 type = attribute->Type();
163		if (isprint(type & 0xff) && isprint((type >> 8) & 0xff)
164			 && isprint((type >> 16) & 0xff) && isprint(type >> 24)) {
165			printf("  '%c%c%c%c'", int(type >> 24), int((type >> 16) & 0xff),
166				int((type >> 8) & 0xff), int(type & 0xff));
167		} else
168			printf("  %#" B_PRIx32, type);
169
170		printf(">\n");
171		return B_OK;
172	}
173
174	virtual status_t HandleEntryDone(
175		typename VersionPolicy::PackageEntry* entry)
176	{
177		if (!fListEntries)
178			return B_OK;
179
180		fLevel--;
181		return B_OK;
182	}
183
184	virtual status_t HandlePackageAttribute(
185		const BPackageInfoAttributeValue& value)
186	{
187		if (value.attributeID == B_PACKAGE_INFO_NAME)
188			printf("package-attributes:\n");
189
190		if (!fPrinter.PrintAttribute(value)) {
191			printf("*** Invalid package attribute section: unexpected "
192				"package attribute id %d encountered\n", value.attributeID);
193			return B_BAD_DATA;
194		}
195
196		return B_OK;
197	}
198
199	virtual void HandleErrorOccurred()
200	{
201	}
202
203private:
204	static const char* _PermissionString(char* buffer, uint32 mode, bool sticky)
205	{
206		buffer[0] = (mode & 0x4) != 0 ? 'r' : '-';
207		buffer[1] = (mode & 0x2) != 0 ? 'w' : '-';
208
209		if ((mode & 0x1) != 0)
210			buffer[2] = sticky ? 's' : 'x';
211		else
212			buffer[2] = '-';
213
214		buffer[3] = '\0';
215		return buffer;
216	}
217
218	static void _PrintPackageVersion(const BPackageVersionData& version)
219	{
220		printf("%s", BPackageVersion(version).ToString().String());
221	}
222
223private:
224	PackageInfoPrinter	fPrinter;
225	int					fLevel;
226	bool				fListEntries;
227	bool				fListAttribute;
228};
229
230
231template<typename VersionPolicy>
232struct PackageContentListPathsHandler : VersionPolicy::PackageContentHandler {
233	PackageContentListPathsHandler()
234		:
235		fPathComponents()
236	{
237	}
238
239	virtual status_t HandleEntry(typename VersionPolicy::PackageEntry* entry)
240	{
241		fPathComponents.Add(entry->Name());
242		printf("%s\n", fPathComponents.Join("/").String());
243		return B_OK;
244	}
245
246	virtual status_t HandleEntryAttribute(
247		typename VersionPolicy::PackageEntry* entry,
248		typename VersionPolicy::PackageEntryAttribute* attribute)
249	{
250		return B_OK;
251	}
252
253	virtual status_t HandleEntryDone(
254		typename VersionPolicy::PackageEntry* entry)
255	{
256		fPathComponents.Remove(fPathComponents.CountStrings() - 1);
257		return B_OK;
258	}
259
260	virtual status_t HandlePackageAttribute(
261		const BPackageInfoAttributeValue& value)
262	{
263		return B_OK;
264	}
265
266	virtual void HandleErrorOccurred()
267	{
268	}
269
270private:
271	BStringList	fPathComponents;
272};
273
274
275template<typename VersionPolicy>
276static void
277do_list(const char* packageFileName, bool listAttributes, ListMode listMode,
278	bool ignoreVersionError)
279{
280	// open package
281	BStandardErrorOutput errorOutput;
282	typename VersionPolicy::PackageReader packageReader(&errorOutput);
283	status_t error = VersionPolicy::InitReader(packageReader, packageFileName);
284	if (error != B_OK) {
285		if (ignoreVersionError && error == B_MISMATCHED_VALUES)
286			return;
287		exit(1);
288	}
289
290	// list
291	switch (listMode) {
292		case LIST_PATHS_ONLY:
293		{
294			PackageContentListPathsHandler<VersionPolicy> handler;
295			error = packageReader.ParseContent(&handler);
296			break;
297		}
298
299		case LIST_ALL:
300		case LIST_META_INFO_ONLY:
301		{
302			PackageContentListHandler<VersionPolicy> handler(
303				listMode != LIST_META_INFO_ONLY, listAttributes);
304			error = packageReader.ParseContent(&handler);
305		}
306	}
307
308	if (error != B_OK)
309		exit(1);
310
311	exit(0);
312}
313
314
315int
316command_list(int argc, const char* const* argv)
317{
318	ListMode listMode = LIST_ALL;
319	bool listAttributes = false;
320
321	while (true) {
322		static struct option sLongOptions[] = {
323			{ "help", no_argument, 0, 'h' },
324			{ 0, 0, 0, 0 }
325		};
326
327		opterr = 0; // don't print errors
328		int c = getopt_long(argc, (char**)argv, "+ahip", sLongOptions, NULL);
329		if (c == -1)
330			break;
331
332		switch (c) {
333			case 'a':
334				listAttributes = true;
335				break;
336
337			case 'i':
338				listMode = LIST_META_INFO_ONLY;
339				break;
340
341			case 'h':
342				print_usage_and_exit(false);
343				break;
344
345			case 'p':
346				listMode = LIST_PATHS_ONLY;
347				break;
348
349			default:
350				print_usage_and_exit(true);
351				break;
352		}
353	}
354
355	// One argument should remain -- the package file name.
356	if (optind + 1 != argc)
357		print_usage_and_exit(true);
358
359	const char* packageFileName = argv[optind++];
360
361	// If the file doesn't look like a package file, try to load it as a
362	// package info file.
363	if (!BString(packageFileName).EndsWith(".hpkg")) {
364		struct ErrorListener : BPackageInfo::ParseErrorListener {
365			virtual void OnError(const BString& msg, int line, int col)
366			{
367				fprintf(stderr, "%s:%d:%d: %s\n", fPath, line, col,
368					msg.String());
369			}
370
371			const char*	fPath;
372		} errorListener;
373		errorListener.fPath = packageFileName;
374
375		BPackageInfo info;
376		if (info.ReadFromConfigFile(BEntry(packageFileName), &errorListener)
377				!= B_OK) {
378			return 1;
379		}
380
381		printf("package-attributes:\n");
382		PackageInfoPrinter().PrintPackageInfo(info);
383		return 0;
384	}
385
386	BHPKG::BStandardErrorOutput errorOutput;
387
388	// current package file format version
389	do_list<VersionPolicyV2>(packageFileName, listAttributes, listMode, true);
390	do_list<VersionPolicyV1>(packageFileName, listAttributes, listMode, false);
391
392	return 0;
393}
394