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