1/*
2 * Copyright 2003-2009, Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Jonas Sundstr��m, jonas@kirilla.com
7 *		Peter Folk <pfolk@uni.uiuc.edu>
8 */
9
10
11#include "ZipperThread.h"
12
13#include <errno.h>
14#include <signal.h>
15#include <string.h>
16#include <unistd.h>
17
18#include <Catalog.h>
19#include <FindDirectory.h>
20#include <Locale.h>
21#include <Locker.h>
22#include <Message.h>
23#include <Path.h>
24#include <Volume.h>
25
26#include <private/tracker/tracker_private.h>
27
28#include "ZipOMaticMisc.h"
29#include "ZipOMaticWindow.h"
30
31
32#undef B_TRANSLATION_CONTEXT
33#define B_TRANSLATION_CONTEXT "file:ZipperThread.cpp"
34
35
36ZipperThread::ZipperThread(BMessage* refsMessage, BWindow* window)
37	:
38	GenericThread("ZipperThread", B_NORMAL_PRIORITY, refsMessage),
39	fWindowMessenger(window),
40	fZipProcess(-1),
41	fStdIn(-1),
42	fStdOut(-1),
43	fStdErr(-1),
44	fOutputFile(NULL)
45{
46	fThreadDataStore = new BMessage(*refsMessage);
47}
48
49
50ZipperThread::~ZipperThread()
51{
52}
53
54
55status_t
56ZipperThread::ThreadStartup()
57{
58	type_code type = B_REF_TYPE;
59	int32 refCount = 0;
60	entry_ref ref;
61	entry_ref lastRef;
62	bool sameFolder = true;
63
64	status_t status = fThreadDataStore->GetInfo("refs", &type, &refCount);
65	if (status != B_OK || refCount < 1) {
66		_SendMessageToWindow(ZIPPO_THREAD_EXIT_ERROR);
67		Quit();
68		return B_ERROR;
69	}
70
71	for	(int index = 0; index < refCount; index++) {
72		fThreadDataStore->FindRef("refs", index, &ref);
73
74		if (index > 0) {
75			if (lastRef.directory != ref.directory) {
76				sameFolder = false;
77				break;
78			}
79		}
80		lastRef = ref;
81	}
82
83	entry_ref dirRef;
84	bool gotDirRef = false;
85
86	status = fThreadDataStore->FindRef("dir_ref", 0, &dirRef);
87	if (status == B_OK) {
88		BEntry dirEntry(&dirRef);
89		BNode dirNode(&dirRef);
90
91		if (dirEntry.InitCheck() == B_OK
92			&& dirEntry.Exists()
93			&& dirNode.InitCheck() == B_OK
94			&& dirNode.IsDirectory())
95			gotDirRef = true;
96	}
97
98	if (gotDirRef) {
99		BEntry entry(&dirRef);
100		BPath path;
101		entry.GetPath(&path);
102		chdir(path.Path());
103	} else if (sameFolder) {
104		BEntry entry(&lastRef);
105		BPath path;
106		entry.GetParent(&entry);
107		entry.GetPath(&path);
108		chdir(path.Path());
109	} else {
110		BPath path;
111		if (find_directory(B_DESKTOP_DIRECTORY, &path) == B_OK)
112			chdir(path.Path());
113	}
114
115	BString archiveName;
116
117	if (refCount > 1)
118		archiveName = B_TRANSLATE("Archive");
119	else
120		archiveName = lastRef.name;
121
122	int index = 1;
123	for (;; index++) {
124		BString tryName = archiveName;
125
126		if (index != 1)
127			tryName << " " << index;
128
129		tryName << ".zip";
130
131		BEntry entry(tryName.String());
132		if (!entry.Exists()) {
133			archiveName = tryName;
134			entry.GetRef(&fOutputEntryRef);
135			break;
136		}
137	}
138
139	int32 argc = refCount + 3;
140	const char** argv = new const char* [argc + 1];
141
142	argv[0] = strdup("/bin/zip");
143	argv[1] = strdup("-ry");
144	argv[2] = strdup(archiveName.String());
145
146	for (int index = 0; index < refCount; index++) {
147		fThreadDataStore->FindRef("refs", index, &ref);
148
149		if (gotDirRef || sameFolder) {
150			argv[3 + index]	= strdup(ref.name);
151		} else {
152			BPath path(&ref);
153			BString file = path.Path();
154			argv[3 + index]	= strdup(path.Path());
155		}
156	}
157
158	argv[argc] = NULL;
159
160	fZipProcess = _PipeCommand(argc, argv, fStdIn, fStdOut, fStdErr);
161
162	delete [] argv;
163
164	if (fZipProcess < 0)
165		return fZipProcess;
166
167	resume_thread(fZipProcess);
168
169	fOutputFile = fdopen(fStdOut, "r");
170	if (fOutputFile == NULL)
171		return errno;
172
173	_SendMessageToWindow(ZIPPO_TASK_DESCRIPTION, "archive_filename",
174		archiveName.String());
175	_SendMessageToWindow(ZIPPO_LINE_OF_STDOUT, "zip_output",
176		B_TRANSLATE("Preparing to archive"));
177
178	return B_OK;
179}
180
181
182status_t
183ZipperThread::ExecuteUnit()
184{
185	char buffer[4096];
186
187	char* output = fgets(buffer, sizeof(buffer) - 1, fOutputFile);
188	if (output == NULL)
189		return EOF;
190
191	char* newLine = strrchr(output, '\n');
192	if (newLine != NULL)
193		*newLine = '\0';
194
195	if (!strncmp("  a", output, 3)) {
196		output[2] = 'A';
197		_SendMessageToWindow(ZIPPO_LINE_OF_STDOUT, "zip_output", output + 2);
198	} else if (!strncmp("up", output, 2)) {
199		output[0] = 'U';
200		_SendMessageToWindow(ZIPPO_LINE_OF_STDOUT, "zip_output", output);
201	} else {
202		_SendMessageToWindow(ZIPPO_LINE_OF_STDOUT, "zip_output", output);
203	}
204
205	return B_OK;
206}
207
208
209status_t
210ZipperThread::ThreadShutdown()
211{
212	close(fStdIn);
213	close(fStdOut);
214	close(fStdErr);
215
216	_SelectInTracker();
217
218	return B_OK;
219}
220
221
222void
223ZipperThread::ThreadStartupFailed(status_t status)
224{
225	fprintf(stderr, "ZipperThread::ThreadStartupFailed(): %s\n",
226		strerror(status));
227	_SendMessageToWindow(ZIPPO_THREAD_EXIT_ERROR);
228
229	Quit();
230}
231
232
233void
234ZipperThread::ExecuteUnitFailed(status_t status)
235{
236	if (status == EOF) {
237		fprintf(stderr, "ZipperThread::ExecuteUnitFailed(): EOF\n");
238		// thread has finished, been quit or killed, we don't know
239		_SendMessageToWindow(ZIPPO_THREAD_EXIT);
240	} else {
241		fprintf(stderr, "ZipperThread::ExecuteUnitFailed(): %s\n",
242			strerror(status));
243		// explicit error - communicate error to Window
244		_SendMessageToWindow(ZIPPO_THREAD_EXIT_ERROR);
245	}
246
247	Quit();
248}
249
250
251void
252ZipperThread::ThreadShutdownFailed(status_t status)
253{
254	fprintf(stderr, "ZipperThread::ThreadShutdownFailed(): %s\n",
255		strerror(status));
256}
257
258
259void
260ZipperThread::_MakeShellSafe(BString* string)
261{
262	string->CharacterEscape("\"$`", '\\');
263	string->Prepend("\"");
264	string->Append("\"");
265}
266
267
268thread_id
269ZipperThread::_PipeCommand(int argc, const char** argv, int& in, int& out,
270	int& err, const char** envp)
271{
272	static BLocker lock;
273
274	if (lock.Lock()) {
275		// This function was originally written by Peter Folk
276		// <pfolk@uni.uiuc.edu> and published in the BeDevTalk FAQ
277		// http://www.abisoft.com/faq/BeDevTalk_FAQ.html#FAQ-209
278
279		thread_id thread;
280
281		// Save current FDs
282		int oldIn = dup(STDIN_FILENO);
283		int oldOut = dup(STDOUT_FILENO);
284		int oldErr = dup(STDERR_FILENO);
285
286		int inPipe[2], outPipe[2], errPipe[2];
287
288		// Create new pipe FDs as stdin, stdout, stderr
289		if (pipe(inPipe) < 0)
290			goto err1;
291		if (pipe(outPipe) < 0)
292			goto err2;
293		if (pipe(errPipe) < 0)
294			goto err3;
295
296		errno = 0;
297
298		// replace old stdin/stderr/stdout
299		dup2(inPipe[0], STDIN_FILENO);
300		close(inPipe[0]);
301		dup2(outPipe[1], STDOUT_FILENO);
302		close(outPipe[1]);
303		dup2(errPipe[1], STDERR_FILENO);
304		close(errPipe[1]);
305
306		if (errno == 0) {
307			in = inPipe[1];		// Write to in, appears on cmd's stdin
308			out = outPipe[0];	// Read from out, taken from cmd's stdout
309			err = errPipe[0];	// Read from err, taken from cmd's stderr
310
311			// execute command
312			thread = load_image(argc, argv, envp);
313		} else {
314			thread = errno;
315		}
316
317		// Restore old FDs
318		dup2(oldIn, STDIN_FILENO);
319		close(oldIn);
320		dup2(oldOut, STDOUT_FILENO);
321		close(oldOut);
322		dup2(oldErr, STDERR_FILENO);
323		close(oldErr);
324
325		lock.Unlock();
326		return thread;
327
328	err3:
329		close(outPipe[0]);
330		close(outPipe[1]);
331	err2:
332		close(inPipe[0]);
333		close(inPipe[1]);
334	err1:
335		close(oldIn);
336		close(oldOut);
337		close(oldErr);
338
339		lock.Unlock();
340		return errno;
341	} else {
342		return B_ERROR;
343	}
344}
345
346
347void
348ZipperThread::_SendMessageToWindow(uint32 what, const char* name,
349	const char* value)
350{
351	BMessage msg(what);
352	if (name != NULL && value != NULL)
353		msg.AddString(name, value);
354
355	fWindowMessenger.SendMessage(&msg);
356}
357
358
359status_t
360ZipperThread::SuspendExternalZip()
361{
362	thread_info info;
363	status_t status = get_thread_info(fZipProcess, &info);
364
365	if (status == B_OK && !strcmp(info.name, "zip"))
366		return suspend_thread(fZipProcess);
367
368	return status;
369}
370
371
372status_t
373ZipperThread::ResumeExternalZip()
374{
375	thread_info info;
376	status_t status = get_thread_info(fZipProcess, &info);
377
378	if (status == B_OK && !strcmp(info.name, "zip"))
379		return resume_thread(fZipProcess);
380
381	return status;
382}
383
384
385status_t
386ZipperThread::InterruptExternalZip()
387{
388	thread_info info;
389	status_t status = get_thread_info(fZipProcess, &info);
390
391	if (status == B_OK && !strcmp(info.name, "zip")) {
392		status = send_signal(fZipProcess, SIGINT);
393		WaitOnExternalZip();
394		return status;
395	}
396
397	return status;
398}
399
400
401status_t
402ZipperThread::WaitOnExternalZip()
403{
404	thread_info info;
405	status_t status = get_thread_info(fZipProcess, &info);
406
407	if (status == B_OK && !strcmp(info.name, "zip"))
408		return wait_for_thread(fZipProcess, &status);
409
410	return status;
411}
412
413
414status_t
415ZipperThread::_SelectInTracker()
416{
417	entry_ref parentRef;
418	BEntry entry(&fOutputEntryRef);
419
420	if (!entry.Exists())
421		return B_ENTRY_NOT_FOUND;
422
423	entry.GetParent(&entry);
424	entry.GetRef(&parentRef);
425
426	BMessenger trackerMessenger(kTrackerSignature);
427	if (!trackerMessenger.IsValid())
428		return B_ERROR;
429
430	BMessage request, reply;
431	request.what = B_REFS_RECEIVED;
432	request.AddRef("refs", &parentRef);
433	status_t status = trackerMessenger.SendMessage(&request, &reply);
434	if (status != B_OK)
435		return status;
436
437	// Wait 0.3 seconds to give Tracker time to populate.
438	snooze(300000);
439
440	request.MakeEmpty();
441	request.what = BPrivate::kSelect;
442	request.AddRef("refs", &fOutputEntryRef);
443
444	reply.MakeEmpty();
445	return trackerMessenger.SendMessage(&request, &reply);
446}
447