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