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