xref: /haiku/src/apps/packageinstaller/PackageItem.cpp (revision 4f2fd49bdc6078128b1391191e4edac647044c3d)
1 /*
2  * Copyright (c) 2007, Haiku, Inc.
3  * Distributed under the terms of the MIT license.
4  *
5  * Author:
6  *		Łukasz 'Sil2100' Zemczak <sil2100@vexillium.org>
7  */
8 
9 
10 #include "PackageItem.h"
11 
12 #include <Alert.h>
13 #include <ByteOrder.h>
14 #include <Directory.h>
15 #include <NodeInfo.h>
16 #include <SymLink.h>
17 #include <Volume.h>
18 
19 #include <fs_info.h>
20 #include "zlib.h"
21 
22 // Macro reserved for later localization
23 #define T(x) x
24 
25 enum {
26 	P_CHUNK_SIZE = 256
27 };
28 
29 static const uint32 kDefaultMode = 0777;
30 static const uint8 padding[7] = { 0, 0, 0, 0, 0, 0, 0 };
31 
32 enum {
33 	P_DATA = 0,
34 	P_ATTRIBUTE
35 };
36 
37 
38 status_t
39 inflate_data(uint8 *in, uint32 in_size, uint8 *out, uint32 out_size)
40 {
41 	z_stream stream;
42 	stream.zalloc = Z_NULL;
43 	stream.zfree = Z_NULL;
44 	stream.opaque = Z_NULL;
45 	stream.avail_in = in_size;
46 	stream.next_in = in;
47 	status_t ret;
48 
49 	ret = inflateInit(&stream);
50 	if (ret != Z_OK)
51 		return B_ERROR;
52 
53 	stream.avail_out = out_size;
54 	stream.next_out = out;
55 
56 	ret = inflate(&stream, Z_NO_FLUSH);
57 	if (ret != Z_STREAM_END) {
58 		parser_debug("Left: %d\n", stream.avail_out);
59 		return B_ERROR; // Uncompressed file size in package info corrupted
60 	}
61 
62 	(void)inflateEnd(&stream);
63 
64 	return B_OK;
65 }
66 
67 
68 static inline int
69 inflate_file_to_file(BFile *in, uint64 in_size, BFile *out, uint64 out_size)
70 {
71 	z_stream stream;
72 	stream.zalloc = Z_NULL;
73 	stream.zfree = Z_NULL;
74 	stream.opaque = Z_NULL;
75 	stream.avail_in = 0;
76 	stream.next_in = Z_NULL;
77 	status_t ret;
78 
79 	uint8 buffer_out[P_CHUNK_SIZE], buffer_in[P_CHUNK_SIZE];
80 	uint64 bytes_read = 0, read = P_CHUNK_SIZE, write = 0;
81 
82 	ret = inflateInit(&stream);
83 	if (ret != Z_OK)
84 		return B_ERROR;
85 
86 	do {
87 		bytes_read += P_CHUNK_SIZE;
88 		if (bytes_read > in_size) {
89 			read = in_size - (bytes_read - P_CHUNK_SIZE);
90 			bytes_read = in_size;
91 		}
92 
93 		stream.avail_in = in->Read(buffer_in, read);
94 		if (stream.avail_in != read) {
95 			(void)inflateEnd(&stream);
96 			return B_ERROR;
97 		}
98 		stream.next_in = buffer_in;
99 
100 		do {
101 			stream.avail_out = P_CHUNK_SIZE;
102 			stream.next_out = buffer_out;
103 
104 			ret = inflate(&stream, Z_NO_FLUSH);
105 			if (ret != Z_OK && ret != Z_STREAM_END && ret != Z_BUF_ERROR) {
106 				(void)inflateEnd(&stream);
107 				return B_ERROR;
108 			}
109 
110 			write = P_CHUNK_SIZE - stream.avail_out;
111 			if (static_cast<uint64>(out->Write(buffer_out, write)) != write) {
112 				(void)inflateEnd(&stream);
113 				return B_ERROR;
114 			}
115 		}
116 		while (stream.avail_out == 0);
117 	}
118 	while (bytes_read != in_size);
119 
120 	(void)inflateEnd(&stream);
121 
122 	return B_OK;
123 }
124 
125 
126 // #pragma mark -
127 
128 
129 PkgDirectory::PkgDirectory(BFile *parent, BString path, uint8 type, uint32 ctime,
130 			uint32 mtime, uint64 offset, uint64 size)
131 	:
132 	fPath(path),
133 	fOffset(offset),
134 	fSize(size),
135 	fPathType(type),
136 	fCreationTime(ctime),
137 	fModificationTime(mtime),
138 	fPackage(parent)
139 {
140 }
141 
142 
143 void
144 PkgDirectory::SetTo(BFile *parent, BString path, uint8 type, uint32 ctime,
145 			uint32 mtime, uint64 offset, uint64 size)
146 {
147 	fPackage = parent;
148 	fPath = path;
149 
150 	fOffset = offset;
151 	fSize = size;
152 	fPathType = type;
153 	fCreationTime = ctime;
154 	fModificationTime = mtime;
155 }
156 
157 
158 PkgDirectory::~PkgDirectory()
159 {
160 }
161 
162 
163 status_t
164 PkgDirectory::WriteToPath(const char *path, BPath *final)
165 {
166 	BPath destination;
167 	status_t ret;
168 	parser_debug("Directory: %s WriteToPath() called!\n", fPath.String());
169 
170 	ret = _InitPath(path, &destination);
171 	if (ret != B_OK)
172 		return ret;
173 
174 	// Since Haiku is single-user right now, we give the newly
175 	// created directory default permissions
176 	ret = create_directory(destination.Path(), kDefaultMode);
177 	if (ret != B_OK)
178 		return ret;
179 	BDirectory dir(destination.Path());
180 	parser_debug("Directory created!\n");
181 
182 	if (fCreationTime)
183 		dir.SetCreationTime(static_cast<time_t>(fCreationTime));
184 
185 	if (fModificationTime)
186 		dir.SetModificationTime(static_cast<time_t>(fModificationTime));
187 
188 	// Since directories can only have attributes in the offset section,
189 	// we can check here whether it is necessary to continue
190 	if (fOffset) {
191 		ret = _HandleAttributes(&destination, &dir, "FoDa");
192 	}
193 
194 	if (final) {
195 		*final = destination;
196 	}
197 
198 	return ret;
199 }
200 
201 
202 int32
203 PkgDirectory::_ItemExists(const char *name)
204 {
205 	BString alertString = T("The file named");
206 	alertString << " \'" << name << "\' ";
207 	alertString << T("already exists in the given path. Should I replace "
208 		"the existing file with the one from this package?");
209 
210 	BAlert *alert = new BAlert(T("file_exists"), alertString.String(),
211 		T("Yes"), T("No"), T("Abort"));
212 
213 	return alert->Go();
214 }
215 
216 
217 status_t
218 PkgDirectory::_InitPath(const char *path, BPath *destination)
219 {
220 	status_t ret = B_OK;
221 
222 	if (fPathType == P_INSTALL_PATH) {
223 		if (!path)
224 			return B_ERROR;
225 		ret = destination->SetTo(path, fPath.String());
226 	}
227 	else if (fPathType == P_SYSTEM_PATH)
228 		ret = destination->SetTo(fPath.String());
229 	else {
230 		if (!path)
231 			return B_ERROR;
232 
233 		BVolume volume(dev_for_path(path));
234 		ret = volume.InitCheck();
235 		if (ret != B_OK)
236 			return ret;
237 
238 		BDirectory temp;
239 		ret = volume.GetRootDirectory(&temp);
240 		if (ret != B_OK)
241 			return ret;
242 
243 		BPath mountPoint(&temp, NULL);
244 		ret = destination->SetTo(mountPoint.Path(), fPath.String());
245 	}
246 
247 	return ret;
248 }
249 
250 
251 status_t
252 PkgDirectory::_HandleAttributes(BPath *destination, BNode *node,
253 		const char *header)
254 {
255 	status_t ret = B_OK;
256 
257 	BVolume volume(dev_for_path(destination->Path()));
258 	if (volume.KnowsAttr()) {
259 		parser_debug("We have an offset\n");
260 		if (!fPackage)
261 			return B_ERROR;
262 
263 		ret = fPackage->InitCheck();
264 		if (ret != B_OK)
265 			return ret;
266 
267 		// We need to parse the data section now
268 		fPackage->Seek(fOffset, SEEK_SET);
269 		uint8 buffer[7];
270 		if (fPackage->Read(buffer, 7) != 7 || memcmp(buffer, header, 5))
271 			return B_ERROR;
272 		parser_debug("Header validated!\n");
273 
274 		char *attrName = 0;
275 		uint32 nameSize = 0;
276 		uint8 *attrData = new uint8[P_CHUNK_SIZE];
277 		uint64 dataSize = P_CHUNK_SIZE;
278 		uint8 *temp = new uint8[P_CHUNK_SIZE];
279 		uint64 tempSize = P_CHUNK_SIZE;
280 
281 		uint64 attrCSize = 0, attrOSize = 0;
282 		uint32 attrType = 0; // type_code type
283 		bool attrStarted = false, done = false;
284 
285 		while (fPackage->Read(buffer, 7) == 7) {
286 			if (!memcmp(buffer, "FBeA", 5))
287 				continue;
288 
289 			ret = _ParseAttribute(buffer, node, &attrName, &nameSize, &attrType,
290 					&attrData, &dataSize, &temp, &tempSize, &attrCSize, &attrOSize,
291 					&attrStarted, &done);
292 			if (ret != B_OK || done)
293 				break;
294 		}
295 
296 		delete[] attrData;
297 		delete[] temp;
298 	}
299 
300 	return ret;
301 }
302 
303 
304 inline status_t
305 PkgDirectory::_ParseAttribute(uint8 *buffer, BNode *node, char **attrName,
306 		uint32 *nameSize, uint32 *attrType, uint8 **attrData, uint64 *dataSize,
307 		uint8 **temp, uint64 *tempSize, uint64 *attrCSize, uint64 *attrOSize,
308 		bool *attrStarted, bool *done)
309 {
310 	status_t ret = B_OK;
311 	uint32 length;
312 
313 	if (!memcmp(buffer, "BeAI", 5)) {
314 		parser_debug(" Attribute started.\n");
315 		if (*attrName)
316 			*attrName[0] = 0;
317 		*attrCSize = 0;
318 		*attrOSize = 0;
319 
320 		*attrStarted = true;
321 	}
322 	else if (!memcmp(buffer, "BeAN", 5)) {
323 		if (!*attrStarted) {
324 			ret = B_ERROR;
325 			return ret;
326 		}
327 
328 		parser_debug(" BeAN.\n");
329 		fPackage->Read(&length, 4);
330 		swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST);
331 
332 		if (*nameSize < (length + 1)) {
333 			delete *attrName;
334 			*nameSize = length + 1;
335 			*attrName = new char[*nameSize];
336 		}
337 		fPackage->Read(*attrName, length);
338 		(*attrName)[length] = 0;
339 
340 		parser_debug(" (%d) = %s\n", length, *attrName);
341 	}
342 	else if (!memcmp(buffer, "BeAT", 5)) {
343 		if (!*attrStarted) {
344 			ret = B_ERROR;
345 			return ret;
346 		}
347 
348 		parser_debug(" BeAT.\n");
349 		fPackage->Read(attrType, 4);
350 		swap_data(B_UINT32_TYPE, attrType, sizeof(*attrType),
351 				B_SWAP_BENDIAN_TO_HOST);
352 	}
353 	else if (!memcmp(buffer, "BeAD", 5)) {
354 		if (!*attrStarted) {
355 			ret = B_ERROR;
356 			return ret;
357 		}
358 
359 		parser_debug(" BeAD.\n");
360 		fPackage->Read(attrCSize, 8);
361 		swap_data(B_UINT64_TYPE, attrCSize, sizeof(*attrCSize),
362 				B_SWAP_BENDIAN_TO_HOST);
363 
364 		fPackage->Read(attrOSize, 8);
365 		swap_data(B_UINT64_TYPE, attrOSize, sizeof(*attrOSize),
366 				B_SWAP_BENDIAN_TO_HOST);
367 
368 		fPackage->Seek(4, SEEK_CUR); // TODO: Check what this means
369 
370 		if (*tempSize < *attrCSize) {
371 			delete *temp;
372 			*tempSize = *attrCSize;
373 			*temp = new uint8[*tempSize];
374 		}
375 		if (*dataSize < *attrOSize) {
376 			delete *attrData;
377 			*dataSize = *attrOSize;
378 			*attrData = new uint8[*dataSize];
379 		}
380 
381 		if (fPackage->Read(*temp, *attrCSize)
382 				!= static_cast<ssize_t>(*attrCSize)) {
383 			ret = B_ERROR;
384 			return ret;
385 		}
386 
387 		parser_debug("  Data read successfuly. Inflating!\n");
388 		ret = inflate_data(*temp, *tempSize, *attrData, *dataSize);
389 		if (ret != B_OK)
390 			return ret;
391 	}
392 	else if (!memcmp(buffer, padding, 7)) {
393 		if (!*attrStarted) {
394 			*done = true;
395 			return ret;
396 		}
397 
398 		parser_debug(" Padding.\n");
399 		ssize_t wrote = node->WriteAttr(*attrName, *attrType, 0, *attrData,
400 			*attrOSize);
401 		if(wrote != static_cast<ssize_t>(*attrOSize)) {
402 			ret = B_ERROR;
403 			return ret;
404 		}
405 
406 		*attrStarted = false;
407 		if (*attrName)
408 			*attrName[0] = 0;
409 		*attrCSize = 0;
410 		*attrOSize = 0;
411 
412 		parser_debug(" > Attribute added.\n");
413 	}
414 	else {
415 		ret = B_ERROR;
416 	}
417 
418 	return ret;
419 }
420 
421 
422 inline status_t
423 PkgDirectory::_ParseData(uint8 *buffer, BFile *file, uint64 originalSize,
424 		bool *done)
425 {
426 	status_t ret = B_OK;
427 
428 	if (!memcmp(buffer, "FiMF", 5)) {
429 		parser_debug(" Found file data.\n");
430 		uint64 compressed, original;
431 		fPackage->Read(&compressed, 8);
432 		swap_data(B_UINT64_TYPE, &compressed, sizeof(uint64),
433 				B_SWAP_BENDIAN_TO_HOST);
434 
435 		fPackage->Read(&original, 8);
436 		swap_data(B_UINT64_TYPE, &original, sizeof(uint64),
437 				B_SWAP_BENDIAN_TO_HOST);
438 		parser_debug(" Still good... (%llu : %llu)\n", original,
439 				originalSize);
440 
441 		if (original != originalSize) {
442 			ret = B_ERROR; // File size missmatch
443 			return ret;
444 		}
445 		parser_debug(" Still good...\n");
446 
447 		if (fPackage->Read(buffer, 4) != 4) {
448 			ret = B_ERROR;
449 			return ret;
450 		}
451 		parser_debug(" Still good...\n");
452 
453 		ret = inflate_file_to_file(fPackage, compressed, file, original);
454 		if (ret != B_OK)
455 			return ret;
456 		parser_debug(" File data inflation complete!\n");
457 	}
458 	else if (!memcmp(buffer, padding, 7)) {
459 		*done = true;
460 		return ret;
461 	}
462 	else {
463 		ret = B_ERROR;
464 	}
465 
466 	return ret;
467 }
468 
469 
470 
471 PkgFile::PkgFile(BFile *parent, BString path, uint8 type, uint32 ctime,
472 			uint32 mtime, uint64 offset, uint64 size, uint64 originalSize,
473 			uint32 platform, BString mime, BString signature, uint32 mode)
474 	:	PkgItem(parent, path, type, ctime, mtime, offset, size),
475 	fOriginalSize(originalSize),
476 	fPlatform(platform),
477 	fMode(mode),
478 	fMimeType(mime),
479 	fSignature(signature)
480 {
481 }
482 
483 
484 PkgFile::~PkgFile()
485 {
486 }
487 
488 
489 status_t
490 PkgFile::WriteToPath(const char *path, BPath *final)
491 {
492 	BPath destination;
493 	status_t ret;
494 	parser_debug("File: %s WriteToPath() called!\n", fPath.String());
495 
496 	ret = _InitPath(path, &destination);
497 	if (ret != B_OK)
498 		return ret;
499 
500 	BFile file(destination.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_FAIL_IF_EXISTS);
501 	ret = file.InitCheck();
502 	if (ret == B_FILE_EXISTS) {
503 		int32 selection = _ItemExists(destination.Leaf());
504 		switch (selection) {
505 			case 0:
506 				ret = file.SetTo(destination.Path(), B_WRITE_ONLY | B_ERASE_FILE);
507 				if (ret != B_OK)
508 					return ret;
509 				break;
510 			case 1:
511 				return B_OK;
512 			default:
513 				return B_FILE_EXISTS;
514 		}
515 	}
516 	else if (ret == B_ENTRY_NOT_FOUND) {
517 		BPath directory;
518 		destination.GetParent(&directory);
519 		if (create_directory(directory.Path(), kDefaultMode) != B_OK)
520 			return B_ERROR;
521 
522 		ret = file.SetTo(destination.Path(), B_WRITE_ONLY | B_CREATE_FILE);
523 		if (ret != B_OK)
524 			return ret;
525 	}
526 	else if (ret != B_OK)
527 		return ret;
528 
529 	parser_debug(" File created!\n");
530 
531 	// Set the file permissions, creation and modification times
532 	ret = file.SetPermissions(static_cast<mode_t>(fMode));
533 	if (fCreationTime)
534 		ret |= file.SetCreationTime(static_cast<time_t>(fCreationTime));
535 	if (fModificationTime)
536 		ret |= file.SetModificationTime(static_cast<time_t>(fModificationTime));
537 
538 	if (ret != B_OK)
539 		return ret;
540 
541 	// Set the mimetype and application signature if present
542 	BNodeInfo info(&file);
543 	if (fMimeType.Length() > 0) {
544 		ret = info.SetType(fMimeType.String());
545 		if (ret != B_OK)
546 			return ret;
547 	}
548 	if (fSignature.Length() > 0) {
549 		ret = info.SetPreferredApp(fSignature.String());
550 		if (ret != B_OK)
551 			return ret;
552 	}
553 
554 	if (fOffset) {
555 		parser_debug("We have an offset\n");
556 		if (!fPackage)
557 			return B_ERROR;
558 
559 		ret = fPackage->InitCheck();
560 		if (ret != B_OK)
561 			return ret;
562 
563 		// We need to parse the data section now
564 		fPackage->Seek(fOffset, SEEK_SET);
565 		uint8 buffer[7];
566 
567 		char *attrName = 0;
568 		uint32 nameSize = 0;
569 		uint8 *attrData = new uint8[P_CHUNK_SIZE];
570 		uint64 dataSize = P_CHUNK_SIZE;
571 		uint8 *temp = new uint8[P_CHUNK_SIZE];
572 		uint64 tempSize = P_CHUNK_SIZE;
573 
574 		uint64 attrCSize = 0, attrOSize = 0;
575 		uint32 attrType = 0; // type_code type
576 		bool attrStarted = false, done = false;
577 
578 		uint8 section = P_ATTRIBUTE;
579 
580 		while (fPackage->Read(buffer, 7) == 7) {
581 			if (!memcmp(buffer, "FBeA", 5)) {
582 				parser_debug("-> Attribute\n");
583 				section = P_ATTRIBUTE;
584 				continue;
585 			}
586 			else if (!memcmp(buffer, "FiDa", 5)) {
587 				parser_debug("-> File data\n");
588 				section = P_DATA;
589 				continue;
590 			}
591 
592 			switch (section) {
593 				case P_ATTRIBUTE:
594 				{
595 					ret = _ParseAttribute(buffer, &file, &attrName, &nameSize, &attrType,
596 						&attrData, &dataSize, &temp, &tempSize, &attrCSize, &attrOSize,
597 						&attrStarted, &done);
598 					break;
599 				}
600 				case P_DATA:
601 				{
602 					ret = _ParseData(buffer, &file, fOriginalSize, &done);
603 					break;
604 				}
605 				default:
606 					return B_ERROR;
607 			}
608 
609 			if (ret != B_OK || done)
610 				break;
611 		}
612 
613 		delete[] attrData;
614 		delete[] temp;
615 	}
616 
617 	if (final) {
618 		*final = destination;
619 	}
620 
621 	return ret;
622 }
623 
624 
625 PkgLink::PkgLink(BFile *parent, BString path, BString link, uint8 type,
626 		uint32 ctime, uint32 mtime, uint32 mode, uint64 offset, uint64 size)
627 	:	PkgItem(parent, path, type, ctime, mtime, offset, size),
628 	fMode(mode),
629 	fLink(link)
630 {
631 }
632 
633 
634 PkgLink::~PkgLink()
635 {
636 }
637 
638 
639 status_t
640 PkgLink::WriteToPath(const char *path, BPath *final)
641 {
642 	BPath destination;
643 	status_t ret;
644 	parser_debug("Symlink: %s WriteToPath() called!\n", fPath.String());
645 
646 	ret = _InitPath(path, &destination);
647 	if (ret != B_OK)
648 		return ret;
649 
650 	BString linkName(destination.Leaf());
651 	parser_debug("%s:%s:%s\n", fPath.String(), destination.Path(), linkName.String());
652 	BPath dirPath;
653 	ret = destination.GetParent(&dirPath);
654 	BDirectory dir(dirPath.Path());
655 
656 	ret = dir.InitCheck();
657 	if (ret == B_ENTRY_NOT_FOUND) {
658 		if (create_directory(destination.Path(), kDefaultMode) != B_OK)
659 			return B_ERROR;
660 	}
661 	if (ret != B_OK)
662 		return ret;
663 
664 	BSymLink symlink;
665 	ret = dir.CreateSymLink(linkName.String(), fLink.String(), &symlink);
666 	if (ret != B_OK)
667 		return ret;
668 
669 	parser_debug(" Symlink created!\n");
670 
671 	ret = symlink.SetPermissions(static_cast<mode_t>(fMode));
672 
673 	if (fCreationTime)
674 		ret |= symlink.SetCreationTime(static_cast<time_t>(fCreationTime));
675 
676 	if (fModificationTime)
677 		ret |= symlink.SetModificationTime(static_cast<time_t>(fModificationTime));
678 
679 	if (ret != B_OK)
680 		return ret;
681 
682 	if (fOffset) {
683 		// Simlinks also seem to have attributes - so parse them
684 		ret = _HandleAttributes(&destination, &dir, "LnDa");
685 	}
686 
687 	if (final) {
688 		*final = destination;
689 	}
690 
691 	return ret;
692 }
693 
694