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