xref: /haiku/src/bin/package_repo/command_update.cpp (revision fc7456e9b1ec38c941134ed6d01c438cf289381e)
1 /*
2  * Copyright 2011-2013, Oliver Tappe <zooey@hirschkaefer.de>
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include <dirent.h>
8 #include <errno.h>
9 #include <getopt.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 
14 #include <map>
15 
16 #include <Entry.h>
17 #include <ObjectList.h>
18 #include <Path.h>
19 #include <String.h>
20 
21 #include <package/hpkg/HPKGDefs.h>
22 #include <package/hpkg/PackageInfoAttributeValue.h>
23 #include <package/hpkg/RepositoryContentHandler.h>
24 #include <package/hpkg/RepositoryReader.h>
25 #include <package/hpkg/RepositoryWriter.h>
26 #include <package/hpkg/StandardErrorOutput.h>
27 #include <package/PackageInfo.h>
28 #include <package/PackageInfoContentHandler.h>
29 #include <package/RepositoryInfo.h>
30 
31 #include "package_repo.h"
32 
33 
34 using BPackageKit::BHPKG::BRepositoryWriterListener;
35 using BPackageKit::BHPKG::BRepositoryWriter;
36 using namespace BPackageKit::BHPKG;
37 using namespace BPackageKit;
38 
39 
40 static bool sTrustFilenames = false;
41 
42 
43 bool operator< (const BPackageInfo & left, const BPackageInfo & right)
44 {
45 	if (sTrustFilenames)
46 		return left.FileName().Compare(right.FileName()) < 0;
47 
48 	if (left.Name() != right.Name())
49 		return left.Name() < right.Name();
50 
51 	return left.Version().Compare(right.Version()) < 0;
52 }
53 
54 
55 namespace
56 {
57 
58 
59 typedef std::map<BPackageInfo, bool> PackageInfos;
60 
61 
62 status_t
63 parsePackageListFile(const char* packageListFileName,
64 	BObjectList<BString>* packageFileNames)
65 {
66 	FILE* packageListFile = fopen(packageListFileName, "r");
67 	if (packageListFile == NULL) {
68 		printf("Error: Unable to open %s\n", packageListFileName);
69 		return B_ENTRY_NOT_FOUND;
70 	}
71 	char buffer[128];
72 	while (fgets(buffer, sizeof(buffer), packageListFile) != NULL) {
73 		BString* packageFileName = new(std::nothrow) BString(buffer);
74 		if (packageFileName == NULL) {
75 			printf("Error: Out of memory when reading from %s\n",
76 				packageListFileName);
77 			fclose(packageListFile);
78 			return B_NO_MEMORY;
79 		}
80 		packageFileName->Trim();
81 		packageFileNames->AddItem(packageFileName);
82 	}
83 	fclose(packageListFile);
84 	return B_OK;
85 }
86 
87 
88 struct PackageInfosCollector : BRepositoryContentHandler {
89 	PackageInfosCollector(PackageInfos& packageInfos,
90 		BHPKG::BErrorOutput* errorOutput)
91 		:
92 		fPackageInfos(packageInfos),
93 		fErrorOutput(errorOutput),
94 		fRepositoryInfo(),
95 		fPackageInfo(),
96 		fPackageInfoContentHandler(fPackageInfo, fErrorOutput)
97 	{
98 	}
99 
100 	virtual status_t HandlePackage(const char* packageName)
101 	{
102 		fPackageInfo.Clear();
103 		return B_OK;
104 	}
105 
106 	virtual status_t HandlePackageAttribute(
107 		const BPackageInfoAttributeValue& value)
108 	{
109 		return fPackageInfoContentHandler.HandlePackageAttribute(value);
110 	}
111 
112 	virtual status_t HandlePackageDone(const char* packageName)
113 	{
114 		if (fPackageInfo.InitCheck() != B_OK) {
115 			const BString& name = fPackageInfo.Name();
116 			printf("Error: package-info in repository for '%s' is incomplete\n",
117 				name.IsEmpty() ? "<unknown-package>" : name.String());
118 			return B_BAD_DATA;
119 		}
120 
121 		if (sTrustFilenames) {
122 			// Cache the canonical name to avoid repeated fallbacks
123 			fPackageInfo.SetFileName(fPackageInfo.CanonicalFileName());
124 		}
125 
126 		fPackageInfos[fPackageInfo] = false;
127 		return B_OK;
128 	}
129 
130 	virtual status_t HandleRepositoryInfo(const BRepositoryInfo& repositoryInfo)
131 	{
132 		fRepositoryInfo = repositoryInfo;
133 		return B_OK;
134 	}
135 
136 	virtual void HandleErrorOccurred()
137 	{
138 	}
139 
140 	const BRepositoryInfo& RepositoryInfo() const
141 	{
142 		return fRepositoryInfo;
143 	}
144 
145 private:
146 	PackageInfos& fPackageInfos;
147 	BHPKG::BErrorOutput* fErrorOutput;
148 	BRepositoryInfo fRepositoryInfo;
149 	BPackageInfo fPackageInfo;
150 	BPackageInfoContentHandler fPackageInfoContentHandler;
151 };
152 
153 
154 class RepositoryWriterListener	: public BRepositoryWriterListener {
155 public:
156 	RepositoryWriterListener(bool verbose, bool quiet)
157 		: fVerbose(verbose), fQuiet(quiet)
158 	{
159 	}
160 
161 	virtual void PrintErrorVarArgs(const char* format, va_list args)
162 	{
163 		vfprintf(stderr, format, args);
164 	}
165 
166 	virtual void OnPackageAdded(const BPackageInfo& packageInfo)
167 	{
168 	}
169 
170 	virtual void OnRepositoryInfoSectionDone(uint32 uncompressedSize)
171 	{
172 		if (fQuiet || !fVerbose)
173 			return;
174 
175 		printf("----- Repository Info Section --------------------\n");
176 		printf("repository info size:    %10" B_PRIu32 " (uncompressed)\n",
177 			uncompressedSize);
178 	}
179 
180 	virtual void OnPackageAttributesSectionDone(uint32 stringCount,
181 		uint32 uncompressedSize)
182 	{
183 		if (fQuiet || !fVerbose)
184 			return;
185 
186 		printf("----- Package Attribute Section -------------------\n");
187 		printf("string count:            %10" B_PRIu32 "\n", stringCount);
188 		printf("package attributes size: %10" B_PRIu32 " (uncompressed)\n",
189 			uncompressedSize);
190 	}
191 
192 	virtual void OnRepositoryDone(uint32 headerSize, uint32 repositoryInfoSize,
193 		uint32 licenseCount, uint32 packageCount, uint32 packageAttributesSize,
194 		uint64 totalSize)
195 	{
196 		if (fQuiet || !fVerbose)
197 			return;
198 
199 		printf("----- Package Repository Info -----\n");
200 		if (fVerbose)
201 			printf("embedded license count   %10" B_PRIu32 "\n", licenseCount);
202 		printf("package count            %10" B_PRIu32 "\n", packageCount);
203 		printf("-----------------------------------\n");
204 		printf("header size:             %10" B_PRIu32 "\n", headerSize);
205 		printf("repository header size:  %10" B_PRIu32 "\n",
206 			repositoryInfoSize);
207 		printf("package attributes size: %10" B_PRIu32 "\n",
208 			packageAttributesSize);
209 		printf("total size:              %10" B_PRIu64 "\n", totalSize);
210 		printf("-----------------------------------\n");
211 	}
212 
213 private:
214 	bool fVerbose;
215 	bool fQuiet;
216 };
217 
218 
219 }	// anonymous namespace
220 
221 
222 int
223 command_update(int argc, const char* const* argv)
224 {
225 	const char* changeToDirectory = NULL;
226 	bool quiet = false;
227 	bool verbose = false;
228 
229 	while (true) {
230 		static struct option sLongOptions[] = {
231 			{ "help", no_argument, 0, 'h' },
232 			{ "quiet", no_argument, 0, 'q' },
233 			{ "verbose", no_argument, 0, 'v' },
234 			{ "trust-filenames", no_argument, 0, 't' },
235 			{ 0, 0, 0, 0 }
236 		};
237 
238 		opterr = 0; // don't print errors
239 		int c = getopt_long(argc, (char**)argv, "+C:hqvt", sLongOptions, NULL);
240 		if (c == -1)
241 			break;
242 
243 		switch (c) {
244 			case 'C':
245 				changeToDirectory = optarg;
246 				break;
247 
248 			case 'h':
249 				print_usage_and_exit(false);
250 				break;
251 
252 			case 'q':
253 				quiet = true;
254 				break;
255 
256 			case 'v':
257 				verbose = true;
258 				break;
259 
260 			case 't':
261 				sTrustFilenames = true;
262 				break;
263 
264 			default:
265 				print_usage_and_exit(true);
266 				break;
267 		}
268 	}
269 
270 	// The remaining three arguments are the source and target repository file
271 	// plus the package list file.
272 	if (optind + 3 != argc)
273 		print_usage_and_exit(true);
274 
275 	const char* sourceRepositoryFileName = argv[optind++];
276 	const char* targetRepositoryFileName = argv[optind++];
277 	const char* packageListFileName = argv[optind++];
278 
279 	BStandardErrorOutput errorOutput;
280 	RepositoryWriterListener listener(verbose, quiet);
281 
282 	BEntry sourceRepositoryEntry(sourceRepositoryFileName);
283 	if (!sourceRepositoryEntry.Exists()) {
284 		listener.PrintError(
285 			"Error: given source repository file '%s' doesn't exist!\n",
286 			sourceRepositoryFileName);
287 		return 1;
288 	}
289 	// determine path for 'repo.info' file from given new repository file
290 	BString repositoryInfoFileName(targetRepositoryFileName);
291 	repositoryInfoFileName.Append(".info");
292 	BEntry repositoryInfoEntry(repositoryInfoFileName.String());
293 	BRepositoryInfo repositoryInfo(repositoryInfoEntry);
294 	status_t result = repositoryInfo.InitCheck();
295 	if (result != B_OK) {
296 		listener.PrintError(
297 			"Error: can't parse/read repository-info file %s : %s\n",
298 			repositoryInfoFileName.String(), strerror(result));
299 		return 1;
300 	}
301 
302 	// open source repository
303 	BRepositoryReader repositoryReader(&errorOutput);
304 	result = repositoryReader.Init(sourceRepositoryFileName);
305 	if (result != B_OK) {
306 		listener.PrintError(
307 			"Error: can't read from old repository file : %s\n",
308 			strerror(result));
309 		return 1;
310 	}
311 
312 	// collect package infos from source repository
313 	PackageInfos packageInfos;
314 	PackageInfosCollector packageInfosCollector(packageInfos, &errorOutput);
315 	result = repositoryReader.ParseContent(&packageInfosCollector);
316 	if (result != B_OK) {
317 		listener.PrintError(
318 			"Error: couldn't fetch package infos from old repository : %s\n",
319 			strerror(result));
320 		return 1;
321 	}
322 
323 	// create new repository
324 	BRepositoryWriter repositoryWriter(&listener, &repositoryInfo);
325 	BString tempRepositoryFileName(targetRepositoryFileName);
326 	tempRepositoryFileName += ".___new___";
327 	if ((result = repositoryWriter.Init(tempRepositoryFileName.String()))
328 			!= B_OK) {
329 		listener.PrintError("Error: can't initialize repository-writer : %s\n",
330 			strerror(result));
331 		return 1;
332 	}
333 
334 	BEntry tempRepositoryFile(tempRepositoryFileName.String());
335 	BPath targetRepositoryFilePath(targetRepositoryFileName);
336 
337 	BObjectList<BString> packageNames(100, true);
338 	if ((result = parsePackageListFile(packageListFileName, &packageNames))
339 			!= B_OK) {
340 		listener.PrintError(
341 			"Error: Failed to read package-list-file \"%s\": %s\n",
342 			packageListFileName, strerror(result));
343 		return 1;
344 	}
345 
346 	// change directory, if requested
347 	if (changeToDirectory != NULL) {
348 		if (chdir(changeToDirectory) != 0) {
349 			listener.PrintError(
350 				"Error: Failed to change the current working directory to "
351 				"\"%s\": %s\n", changeToDirectory, strerror(errno));
352 			return 1;
353 		}
354 	}
355 
356 	// add all given package files
357 	for (int i = 0; i < packageNames.CountItems(); ++i) {
358 		BPackageInfo packageInfo;
359 
360 		if (sTrustFilenames)
361 			packageInfo.SetFileName(*packageNames.ItemAt(i));
362 		else {
363 			if ((result = packageInfo.ReadFromPackageFile(
364 					packageNames.ItemAt(i)->String())) != B_OK) {
365 				listener.PrintError(
366 					"Error: Failed to read package-info from \"%s\": %s\n",
367 					packageNames.ItemAt(i)->String(), strerror(result));
368 				return 1;
369 			}
370 		}
371 
372 		PackageInfos::iterator infoIter = packageInfos.find(packageInfo);
373 		if (infoIter != packageInfos.end()) {
374 			infoIter->second = true;
375 			if ((result = repositoryWriter.AddPackageInfo(infoIter->first))
376 					!= B_OK)
377 				return 1;
378 			if (verbose) {
379 				printf("keeping '%s-%s'\n", infoIter->first.Name().String(),
380 					infoIter->first.Version().ToString().String());
381 			}
382 		} else {
383 			BEntry entry(packageNames.ItemAt(i)->String());
384 			if ((result = repositoryWriter.AddPackage(entry)) != B_OK)
385 				return 1;
386 			if (!quiet) {
387 				printf("added '%s' ...\n",
388 					packageNames.ItemAt(i)->String());
389 			}
390 		}
391 	}
392 
393 	// tell about packages dropped from repository
394 	PackageInfos::const_iterator infoIter;
395 	for (infoIter = packageInfos.begin(); infoIter != packageInfos.end();
396 			++infoIter) {
397 		if (!infoIter->second) {
398 			printf("dropped '%s-%s'\n", infoIter->first.Name().String(),
399 				infoIter->first.Version().ToString().String());
400 		}
401 	}
402 
403 
404 	// write the repository
405 	result = repositoryWriter.Finish();
406 	if (result != B_OK)
407 		return 1;
408 
409 	result = tempRepositoryFile.Rename(targetRepositoryFilePath.Leaf(), true);
410 	if (result != B_OK) {
411 		printf("Error: unable to rename repository %s to %s - %s\n",
412 			tempRepositoryFileName.String(), targetRepositoryFileName,
413 			strerror(result));
414 		return 1;
415 	}
416 
417 	if (verbose) {
418 		printf("\nsuccessfully created repository '%s'\n",
419 			targetRepositoryFileName);
420 	}
421 
422 	return 0;
423 }
424