xref: /haiku/src/apps/packageinstaller/PackageItem.cpp (revision d9cebac2b77547b7064f22497514eecd2d047160)
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 		if (attrData)
297 			delete attrData;
298 		if (temp)
299 			delete temp;
300 	}
301 
302 	return ret;
303 }
304 
305 
306 inline status_t
307 PkgDirectory::_ParseAttribute(uint8 *buffer, BNode *node, char **attrName,
308 		uint32 *nameSize, uint32 *attrType, uint8 **attrData, uint64 *dataSize,
309 		uint8 **temp, uint64 *tempSize, uint64 *attrCSize, uint64 *attrOSize,
310 		bool *attrStarted, bool *done)
311 {
312 	status_t ret = B_OK;
313 	uint32 length;
314 
315 	if (!memcmp(buffer, "BeAI", 5)) {
316 		parser_debug(" Attribute started.\n");
317 		if (*attrName)
318 			*attrName[0] = 0;
319 		*attrCSize = 0;
320 		*attrOSize = 0;
321 
322 		*attrStarted = true;
323 	}
324 	else if (!memcmp(buffer, "BeAN", 5)) {
325 		if (!*attrStarted) {
326 			ret = B_ERROR;
327 			return ret;
328 		}
329 
330 		parser_debug(" BeAN.\n");
331 		fPackage->Read(&length, 4);
332 		swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST);
333 
334 		if (*nameSize < (length + 1)) {
335 			delete *attrName;
336 			*nameSize = length + 1;
337 			*attrName = new char[*nameSize];
338 		}
339 		fPackage->Read(*attrName, length);
340 		(*attrName)[length] = 0;
341 
342 		parser_debug(" (%d) = %s\n", length, *attrName);
343 	}
344 	else if (!memcmp(buffer, "BeAT", 5)) {
345 		if (!*attrStarted) {
346 			ret = B_ERROR;
347 			return ret;
348 		}
349 
350 		parser_debug(" BeAT.\n");
351 		fPackage->Read(attrType, 4);
352 		swap_data(B_UINT32_TYPE, attrType, sizeof(*attrType),
353 				B_SWAP_BENDIAN_TO_HOST);
354 	}
355 	else if (!memcmp(buffer, "BeAD", 5)) {
356 		if (!*attrStarted) {
357 			ret = B_ERROR;
358 			return ret;
359 		}
360 
361 		parser_debug(" BeAD.\n");
362 		fPackage->Read(attrCSize, 8);
363 		swap_data(B_UINT64_TYPE, attrCSize, sizeof(*attrCSize),
364 				B_SWAP_BENDIAN_TO_HOST);
365 
366 		fPackage->Read(attrOSize, 8);
367 		swap_data(B_UINT64_TYPE, attrOSize, sizeof(*attrOSize),
368 				B_SWAP_BENDIAN_TO_HOST);
369 
370 		fPackage->Seek(4, SEEK_CUR); // TODO: Check what this means
371 
372 		if (*tempSize < *attrCSize) {
373 			delete *temp;
374 			*tempSize = *attrCSize;
375 			*temp = new uint8[*tempSize];
376 		}
377 		if (*dataSize < *attrOSize) {
378 			delete *attrData;
379 			*dataSize = *attrOSize;
380 			*attrData = new uint8[*dataSize];
381 		}
382 
383 		if (fPackage->Read(*temp, *attrCSize)
384 				!= static_cast<ssize_t>(*attrCSize)) {
385 			ret = B_ERROR;
386 			return ret;
387 		}
388 
389 		parser_debug("  Data read successfuly. Inflating!\n");
390 		ret = inflate_data(*temp, *tempSize, *attrData, *dataSize);
391 		if (ret != B_OK)
392 			return ret;
393 	}
394 	else if (!memcmp(buffer, padding, 7)) {
395 		if (!*attrStarted) {
396 			*done = true;
397 			return ret;
398 		}
399 
400 		parser_debug(" Padding.\n");
401 		ssize_t wrote = node->WriteAttr(*attrName, *attrType, 0, *attrData,
402 			*attrOSize);
403 		if(wrote != static_cast<ssize_t>(*attrOSize)) {
404 			ret = B_ERROR;
405 			return ret;
406 		}
407 
408 		*attrStarted = false;
409 		if (*attrName)
410 			*attrName[0] = 0;
411 		*attrCSize = 0;
412 		*attrOSize = 0;
413 
414 		parser_debug(" > Attribute added.\n");
415 	}
416 	else {
417 		ret = B_ERROR;
418 	}
419 
420 	return ret;
421 }
422 
423 
424 inline status_t
425 PkgDirectory::_ParseData(uint8 *buffer, BFile *file, uint64 originalSize,
426 		bool *done)
427 {
428 	status_t ret = B_OK;
429 
430 	if (!memcmp(buffer, "FiMF", 5)) {
431 		parser_debug(" Found file data.\n");
432 		uint64 compressed, original;
433 		fPackage->Read(&compressed, 8);
434 		swap_data(B_UINT64_TYPE, &compressed, sizeof(uint64),
435 				B_SWAP_BENDIAN_TO_HOST);
436 
437 		fPackage->Read(&original, 8);
438 		swap_data(B_UINT64_TYPE, &original, sizeof(uint64),
439 				B_SWAP_BENDIAN_TO_HOST);
440 		parser_debug(" Still good... (%llu : %llu)\n", original,
441 				originalSize);
442 
443 		if (original != originalSize) {
444 			ret = B_ERROR; // File size missmatch
445 			return ret;
446 		}
447 		parser_debug(" Still good...\n");
448 
449 		if (fPackage->Read(buffer, 4) != 4) {
450 			ret = B_ERROR;
451 			return ret;
452 		}
453 		parser_debug(" Still good...\n");
454 
455 		ret = inflate_file_to_file(fPackage, compressed, file, original);
456 		if (ret != B_OK)
457 			return ret;
458 		parser_debug(" File data inflation complete!\n");
459 	}
460 	else if (!memcmp(buffer, padding, 7)) {
461 		*done = true;
462 		return ret;
463 	}
464 	else {
465 		ret = B_ERROR;
466 	}
467 
468 	return ret;
469 }
470 
471 
472 
473 PkgFile::PkgFile(BFile *parent, BString path, uint8 type, uint32 ctime,
474 			uint32 mtime, uint64 offset, uint64 size, uint64 originalSize,
475 			uint32 platform, BString mime, BString signature, uint32 mode)
476 	:	PkgItem(parent, path, type, ctime, mtime, offset, size),
477 	fOriginalSize(originalSize),
478 	fPlatform(platform),
479 	fMode(mode),
480 	fMimeType(mime),
481 	fSignature(signature)
482 {
483 }
484 
485 
486 PkgFile::~PkgFile()
487 {
488 }
489 
490 
491 status_t
492 PkgFile::WriteToPath(const char *path, BPath *final)
493 {
494 	BPath destination;
495 	status_t ret;
496 	parser_debug("File: %s WriteToPath() called!\n", fPath.String());
497 
498 	ret = _InitPath(path, &destination);
499 	if (ret != B_OK)
500 		return ret;
501 
502 	BFile file(destination.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_FAIL_IF_EXISTS);
503 	ret = file.InitCheck();
504 	if (ret == B_FILE_EXISTS) {
505 		int32 selection = _ItemExists(destination.Leaf());
506 		switch (selection) {
507 			case 0:
508 				ret = file.SetTo(destination.Path(), B_WRITE_ONLY | B_ERASE_FILE);
509 				if (ret != B_OK)
510 					return ret;
511 				break;
512 			case 1:
513 				return B_OK;
514 			default:
515 				return B_FILE_EXISTS;
516 		}
517 	}
518 	else if (ret == B_ENTRY_NOT_FOUND) {
519 		BPath directory;
520 		destination.GetParent(&directory);
521 		if (create_directory(directory.Path(), kDefaultMode) != B_OK)
522 			return B_ERROR;
523 
524 		ret = file.SetTo(destination.Path(), B_WRITE_ONLY | B_CREATE_FILE);
525 		if (ret != B_OK)
526 			return ret;
527 	}
528 	else if (ret != B_OK)
529 		return ret;
530 
531 	parser_debug(" File created!\n");
532 
533 	// Set the file permissions, creation and modification times
534 	ret = file.SetPermissions(static_cast<mode_t>(fMode));
535 	if (fCreationTime)
536 		ret |= file.SetCreationTime(static_cast<time_t>(fCreationTime));
537 	if (fModificationTime)
538 		ret |= file.SetModificationTime(static_cast<time_t>(fModificationTime));
539 
540 	if (ret != B_OK)
541 		return ret;
542 
543 	// Set the mimetype and application signature if present
544 	BNodeInfo info(&file);
545 	if (fMimeType.Length() > 0) {
546 		ret = info.SetType(fMimeType.String());
547 		if (ret != B_OK)
548 			return ret;
549 	}
550 	if (fSignature.Length() > 0) {
551 		ret = info.SetPreferredApp(fSignature.String());
552 		if (ret != B_OK)
553 			return ret;
554 	}
555 
556 	if (fOffset) {
557 		parser_debug("We have an offset\n");
558 		if (!fPackage)
559 			return B_ERROR;
560 
561 		ret = fPackage->InitCheck();
562 		if (ret != B_OK)
563 			return ret;
564 
565 		// We need to parse the data section now
566 		fPackage->Seek(fOffset, SEEK_SET);
567 		uint8 buffer[7];
568 
569 		char *attrName = 0;
570 		uint32 nameSize = 0;
571 		uint8 *attrData = new uint8[P_CHUNK_SIZE];
572 		uint64 dataSize = P_CHUNK_SIZE;
573 		uint8 *temp = new uint8[P_CHUNK_SIZE];
574 		uint64 tempSize = P_CHUNK_SIZE;
575 
576 		uint64 attrCSize = 0, attrOSize = 0;
577 		uint32 attrType = 0; // type_code type
578 		bool attrStarted = false, done = false;
579 
580 		uint8 section = P_ATTRIBUTE;
581 
582 		while (fPackage->Read(buffer, 7) == 7) {
583 			if (!memcmp(buffer, "FBeA", 5)) {
584 				parser_debug("-> Attribute\n");
585 				section = P_ATTRIBUTE;
586 				continue;
587 			}
588 			else if (!memcmp(buffer, "FiDa", 5)) {
589 				parser_debug("-> File data\n");
590 				section = P_DATA;
591 				continue;
592 			}
593 
594 			switch (section) {
595 				case P_ATTRIBUTE:
596 				{
597 					ret = _ParseAttribute(buffer, &file, &attrName, &nameSize, &attrType,
598 						&attrData, &dataSize, &temp, &tempSize, &attrCSize, &attrOSize,
599 						&attrStarted, &done);
600 					break;
601 				}
602 				case P_DATA:
603 				{
604 					ret = _ParseData(buffer, &file, fOriginalSize, &done);
605 					break;
606 				}
607 				default:
608 					return B_ERROR;
609 			}
610 
611 			if (ret != B_OK || done)
612 				break;
613 		}
614 
615 		if (attrData)
616 			delete attrData;
617 		if (temp)
618 			delete temp;
619 	}
620 
621 	if (final) {
622 		*final = destination;
623 	}
624 
625 	return ret;
626 }
627 
628 
629 PkgLink::PkgLink(BFile *parent, BString path, BString link, uint8 type,
630 		uint32 ctime, uint32 mtime, uint32 mode, uint64 offset, uint64 size)
631 	:	PkgItem(parent, path, type, ctime, mtime, offset, size),
632 	fMode(mode),
633 	fLink(link)
634 {
635 }
636 
637 
638 PkgLink::~PkgLink()
639 {
640 }
641 
642 
643 status_t
644 PkgLink::WriteToPath(const char *path, BPath *final)
645 {
646 	BPath destination;
647 	status_t ret;
648 	parser_debug("Symlink: %s WriteToPath() called!\n", fPath.String());
649 
650 	ret = _InitPath(path, &destination);
651 	if (ret != B_OK)
652 		return ret;
653 
654 	BString linkName(destination.Leaf());
655 	parser_debug("%s:%s:%s\n", fPath.String(), destination.Path(), linkName.String());
656 	BPath dirPath;
657 	ret = destination.GetParent(&dirPath);
658 	BDirectory dir(dirPath.Path());
659 
660 	ret = dir.InitCheck();
661 	if (ret == B_ENTRY_NOT_FOUND) {
662 		if (create_directory(destination.Path(), kDefaultMode) != B_OK)
663 			return B_ERROR;
664 	}
665 	if (ret != B_OK)
666 		return ret;
667 
668 	BSymLink symlink;
669 	ret = dir.CreateSymLink(linkName.String(), fLink.String(), &symlink);
670 	if (ret != B_OK)
671 		return ret;
672 
673 	parser_debug(" Symlink created!\n");
674 
675 	ret = symlink.SetPermissions(static_cast<mode_t>(fMode));
676 
677 	if (fCreationTime)
678 		ret |= symlink.SetCreationTime(static_cast<time_t>(fCreationTime));
679 
680 	if (fModificationTime)
681 		ret |= symlink.SetModificationTime(static_cast<time_t>(fModificationTime));
682 
683 	if (ret != B_OK)
684 		return ret;
685 
686 	if (fOffset) {
687 		// Simlinks also seem to have attributes - so parse them
688 		ret = _HandleAttributes(&destination, &dir, "LnDa");
689 	}
690 
691 	if (final) {
692 		*final = destination;
693 	}
694 
695 	return ret;
696 }
697 
698