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
ZipperThread(BMessage * refsMessage,BWindow * window)36 ZipperThread::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
~ZipperThread()50 ZipperThread::~ZipperThread()
51 {
52 }
53
54
55 status_t
ThreadStartup()56 ZipperThread::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
182 status_t
ExecuteUnit()183 ZipperThread::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
209 status_t
ThreadShutdown()210 ZipperThread::ThreadShutdown()
211 {
212 close(fStdIn);
213 close(fStdOut);
214 close(fStdErr);
215
216 _SelectInTracker();
217
218 return B_OK;
219 }
220
221
222 void
ThreadStartupFailed(status_t status)223 ZipperThread::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
233 void
ExecuteUnitFailed(status_t status)234 ZipperThread::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
251 void
ThreadShutdownFailed(status_t status)252 ZipperThread::ThreadShutdownFailed(status_t status)
253 {
254 fprintf(stderr, "ZipperThread::ThreadShutdownFailed(): %s\n",
255 strerror(status));
256 }
257
258
259 void
_MakeShellSafe(BString * string)260 ZipperThread::_MakeShellSafe(BString* string)
261 {
262 string->CharacterEscape("\"$`", '\\');
263 string->Prepend("\"");
264 string->Append("\"");
265 }
266
267
268 thread_id
_PipeCommand(int argc,const char ** argv,int & in,int & out,int & err,const char ** envp)269 ZipperThread::_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
347 void
_SendMessageToWindow(uint32 what,const char * name,const char * value)348 ZipperThread::_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
359 status_t
SuspendExternalZip()360 ZipperThread::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
372 status_t
ResumeExternalZip()373 ZipperThread::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
385 status_t
InterruptExternalZip()386 ZipperThread::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
401 status_t
WaitOnExternalZip()402 ZipperThread::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
414 status_t
_SelectInTracker()415 ZipperThread::_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