xref: /haiku/src/bin/package/command_extract.cpp (revision 0d68f6afb34d8e01421123b216e34771d1c7fbfe)
1 /*
2  * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include <ctype.h>
8 #include <fcntl.h>
9 #include <errno.h>
10 #include <getopt.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <sys/stat.h>
15 #include <unistd.h>
16 
17 #include <algorithm>
18 #include <new>
19 
20 #include <fs_attr.h>
21 
22 #include <AutoDeleter.h>
23 
24 #include <package/hpkg/PackageContentHandler.h>
25 #include <package/hpkg/PackageDataReader.h>
26 #include <package/hpkg/PackageEntry.h>
27 #include <package/hpkg/PackageEntryAttribute.h>
28 #include <package/hpkg/PackageReader.h>
29 
30 #include "BlockBufferCacheNoLock.h"
31 #include "package.h"
32 #include "StandardErrorOutput.h"
33 
34 
35 using namespace BPackageKit::BHPKG;
36 
37 
38 struct PackageContentExtractHandler : BPackageContentHandler {
39 	PackageContentExtractHandler(int packageFileFD)
40 		:
41 		fBufferCache(B_HPKG_DEFAULT_DATA_CHUNK_SIZE_ZLIB, 2),
42 		fPackageFileReader(packageFileFD),
43 		fDataBuffer(NULL),
44 		fDataBufferSize(0),
45 		fErrorOccurred(false)
46 	{
47 	}
48 
49 	~PackageContentExtractHandler()
50 	{
51 		free(fDataBuffer);
52 	}
53 
54 	status_t Init()
55 	{
56 		status_t error = fBufferCache.Init();
57 		if (error != B_OK)
58 			return error;
59 
60 		fDataBufferSize = 64 * 1024;
61 		fDataBuffer = malloc(fDataBufferSize);
62 		if (fDataBuffer == NULL)
63 			return B_NO_MEMORY;
64 
65 		return B_OK;
66 	}
67 
68 	virtual status_t HandleEntry(BPackageEntry* entry)
69 	{
70 		// create a token
71 		Token* token = new(std::nothrow) Token;
72 		if (token == NULL)
73 			return B_NO_MEMORY;
74 		ObjectDeleter<Token> tokenDeleter(token);
75 
76 		// get parent FD
77 		int parentFD = AT_FDCWD;
78 		if (entry->Parent() != NULL)
79 			parentFD = ((Token*)entry->Parent()->UserToken())->fd;
80 
81 		// check whether something is in the way
82 		struct stat st;
83 		bool entryExists = fstatat(parentFD, entry->Name(), &st,
84 			AT_SYMLINK_NOFOLLOW) == 0;
85 		if (entryExists) {
86 			if (S_ISREG(entry->Mode()) || S_ISLNK(entry->Mode())) {
87 				// If the entry in the way is a regular file or a symlink,
88 				// remove it, otherwise fail.
89 				if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) {
90 					fprintf(stderr, "Error: Can't create entry \"%s\", since "
91 						"something is in the way\n", entry->Name());
92 					return B_FILE_EXISTS;
93 				}
94 
95 				if (unlinkat(parentFD, entry->Name(), 0) != 0) {
96 					fprintf(stderr, "Error: Failed to unlink entry \"%s\": %s\n",
97 						entry->Name(), strerror(errno));
98 					return errno;
99 				}
100 
101 				entryExists = false;
102 			} else if (S_ISDIR(entry->Mode())) {
103 				// If the entry in the way is a directory, merge, otherwise
104 				// fail.
105 				if (!S_ISDIR(st.st_mode)) {
106 					fprintf(stderr, "Error: Can't create directory \"%s\", "
107 						"since something is in the way\n", entry->Name());
108 					return B_FILE_EXISTS;
109 				}
110 			}
111 		}
112 
113 		// create the entry
114 		int fd = -1;
115 		if (S_ISREG(entry->Mode())) {
116 			// create the file
117 			fd = openat(parentFD, entry->Name(), O_RDWR | O_CREAT | O_EXCL,
118 				entry->Mode() & ALLPERMS);
119 			if (fd < 0) {
120 				fprintf(stderr, "Error: Failed to create file \"%s\": %s\n",
121 					entry->Name(), strerror(errno));
122 				return errno;
123 			}
124 
125 			// write data
126 			status_t error;
127 			const BPackageData& data = entry->Data();
128 			if (data.IsEncodedInline()) {
129 				BBufferDataReader dataReader(data.InlineData(),
130 					data.CompressedSize());
131 				error = _ExtractFileData(&dataReader, data, fd);
132 			} else
133 				error = _ExtractFileData(&fPackageFileReader, data, fd);
134 
135 			if (error != B_OK)
136 				return error;
137 		} else if (S_ISLNK(entry->Mode())) {
138 			// create the symlink
139 			const char* symlinkPath = entry->SymlinkPath();
140 			if (symlinkat(symlinkPath != NULL ? symlinkPath : "", parentFD,
141 					entry->Name()) != 0) {
142 				fprintf(stderr, "Error: Failed to create symlink \"%s\": %s\n",
143 					entry->Name(), strerror(errno));
144 				return errno;
145 			}
146 // TODO: Set symlink permissions?
147  		} else if (S_ISDIR(entry->Mode())) {
148 			// create the directory, if necessary
149 			if (!entryExists
150 				&& mkdirat(parentFD, entry->Name(), entry->Mode() & ALLPERMS)
151 					!= 0) {
152 				fprintf(stderr, "Error: Failed to create directory \"%s\": "
153 					"%s\n", entry->Name(), strerror(errno));
154 				return errno;
155 			}
156 		} else {
157 			fprintf(stderr, "Error: Invalid file type for entry \"%s\"\n",
158 				entry->Name());
159 			return B_BAD_DATA;
160 		}
161 
162 		// If not done yet (symlink, dir), open the node -- we need the FD.
163 		if (fd < 0) {
164 			fd = openat(parentFD, entry->Name(), O_RDONLY | O_NOTRAVERSE);
165 			if (fd < 0) {
166 				fprintf(stderr, "Error: Failed to open entry \"%s\": %s\n",
167 					entry->Name(), strerror(errno));
168 				return errno;
169 			}
170 		}
171 		token->fd = fd;
172 
173 		// set the file times
174 		if (!entryExists) {
175 			timespec times[2] = {entry->AccessTime(), entry->ModifiedTime()};
176 			futimens(fd, times);
177 
178 			// set user/group
179 			// TODO:...
180 		}
181 
182 		entry->SetUserToken(tokenDeleter.Detach());
183 		return B_OK;
184 	}
185 
186 	virtual status_t HandleEntryAttribute(BPackageEntry* entry,
187 		BPackageEntryAttribute* attribute)
188 	{
189 		int entryFD = ((Token*)entry->UserToken())->fd;
190 
191 		// create the attribute
192 		int fd = fs_fopen_attr(entryFD, attribute->Name(), attribute->Type(),
193 			O_WRONLY | O_CREAT | O_TRUNC);
194 		if (fd < 0) {
195 				fprintf(stderr, "Error: Failed to create attribute \"%s\" of "
196 					"file \"%s\": %s\n", attribute->Name(), entry->Name(),
197 					strerror(errno));
198 				return errno;
199 		}
200 
201 		// write data
202 		status_t error;
203 		const BPackageData& data = attribute->Data();
204 		if (data.IsEncodedInline()) {
205 			BBufferDataReader dataReader(data.InlineData(),
206 				data.CompressedSize());
207 			error = _ExtractFileData(&dataReader, data, fd);
208 		} else
209 			error = _ExtractFileData(&fPackageFileReader, data, fd);
210 
211 		close(fd);
212 
213 		return error;
214 	}
215 
216 	virtual status_t HandleEntryDone(BPackageEntry* entry)
217 	{
218 		if (Token* token = (Token*)entry->UserToken()) {
219 			delete token;
220 			entry->SetUserToken(NULL);
221 		}
222 
223 		return B_OK;
224 	}
225 
226 	virtual status_t HandlePackageAttribute(
227 		const BPackageInfoAttributeValue& value)
228 	{
229 		return B_OK;
230 	}
231 
232 	virtual status_t HandlePackageAttributesDone()
233 	{
234 		return B_OK;
235 	}
236 
237 	virtual void HandleErrorOccurred()
238 	{
239 		fErrorOccurred = true;
240 	}
241 
242 private:
243 	struct Token {
244 		int	fd;
245 
246 		Token()
247 			:
248 			fd(-1)
249 		{
250 		}
251 
252 		~Token()
253 		{
254 			if (fd >= 0)
255 				close(fd);
256 		}
257 	};
258 
259 private:
260 	status_t _ExtractFileData(BDataReader* dataReader, const BPackageData& data,
261 		int fd)
262 	{
263 		// create a BPackageDataReader
264 		BPackageDataReader* reader;
265 		status_t error = BPackageDataReaderFactory(&fBufferCache)
266 			.CreatePackageDataReader(dataReader, data, reader);
267 		if (error != B_OK)
268 			return error;
269 		ObjectDeleter<BPackageDataReader> readerDeleter(reader);
270 
271 		// write the data
272 		off_t bytesRemaining = data.UncompressedSize();
273 		off_t offset = 0;
274 		while (bytesRemaining > 0) {
275 			// read
276 			size_t toCopy = std::min((off_t)fDataBufferSize, bytesRemaining);
277 			error = reader->ReadData(offset, fDataBuffer, toCopy);
278 			if (error != B_OK) {
279 				fprintf(stderr, "Error: Failed to read data: %s\n",
280 					strerror(error));
281 				return error;
282 			}
283 
284 			// write
285 			ssize_t bytesWritten = pwrite(fd, fDataBuffer, toCopy, offset);
286 			if (bytesWritten < 0) {
287 				fprintf(stderr, "Error: Failed to write data: %s\n",
288 					strerror(errno));
289 				return errno;
290 			}
291 			if ((size_t)bytesWritten != toCopy) {
292 				fprintf(stderr, "Error: Failed to write all data\n");
293 				return B_ERROR;
294 			}
295 
296 			offset += toCopy;
297 			bytesRemaining -= toCopy;
298 		}
299 
300 		return B_OK;
301 	}
302 
303 private:
304 	BlockBufferCacheNoLock	fBufferCache;
305 	BFDDataReader			fPackageFileReader;
306 	void*					fDataBuffer;
307 	size_t					fDataBufferSize;
308 	bool					fErrorOccurred;
309 };
310 
311 
312 int
313 command_extract(int argc, const char* const* argv)
314 {
315 	const char* changeToDirectory = NULL;
316 
317 	while (true) {
318 		static struct option sLongOptions[] = {
319 			{ "help", no_argument, 0, 'h' },
320 			{ 0, 0, 0, 0 }
321 		};
322 
323 		opterr = 0; // don't print errors
324 		int c = getopt_long(argc, (char**)argv, "+C:h", sLongOptions, NULL);
325 		if (c == -1)
326 			break;
327 
328 		switch (c) {
329 			case 'C':
330 				changeToDirectory = optarg;
331 				break;
332 
333 			case 'h':
334 				print_usage_and_exit(false);
335 				break;
336 
337 			default:
338 				print_usage_and_exit(true);
339 				break;
340 		}
341 	}
342 
343 	// One argument should remain -- the package file name.
344 	if (optind + 1 != argc)
345 		print_usage_and_exit(true);
346 
347 	const char* packageFileName = argv[optind++];
348 
349 	// open package
350 	StandardErrorOutput errorOutput;
351 	BPackageReader packageReader(&errorOutput);
352 	status_t error = packageReader.Init(packageFileName);
353 printf("Init(): %s\n", strerror(error));
354 	if (error != B_OK)
355 		return 1;
356 
357 	// change directory, if requested
358 	if (changeToDirectory != NULL) {
359 		if (chdir(changeToDirectory) != 0) {
360 			fprintf(stderr, "Error: Failed to change the current working "
361 				"directory to \"%s\": %s\n", changeToDirectory,
362 				strerror(errno));
363 		}
364 	}
365 
366 	// extract
367 	PackageContentExtractHandler handler(packageReader.PackageFileFD());
368 	error = handler.Init();
369 	if (error != B_OK)
370 		return 1;
371 
372 	error = packageReader.ParseContent(&handler);
373 printf("ParseContent(): %s\n", strerror(error));
374 	if (error != B_OK)
375 		return 1;
376 
377 	return 0;
378 }
379