1 /* 2 * Copyright 2004-2006, Jérôme Duval. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * Original code from ZipOMatic by jonas.sundstrom@kirilla.com 5 */ 6 #include <Messenger.h> 7 #include <Path.h> 8 #include "ExpanderThread.h" 9 #include <image.h> 10 #include <signal.h> 11 #include <unistd.h> 12 #include <errno.h> 13 14 const char * ExpanderThreadName = "ExpanderThread"; 15 16 ExpanderThread::ExpanderThread(BMessage * refs_message, BMessenger * messenger) 17 : GenericThread(ExpanderThreadName, B_NORMAL_PRIORITY, refs_message), 18 fWindowMessenger(messenger), 19 fThreadId(-1), 20 fStdIn(-1), 21 fStdOut(-1), 22 fStdErr(-1), 23 fExpanderOutput(NULL), 24 fExpanderOutputString(), 25 fExpanderOutputBuffer(new char [4096]) 26 { 27 SetDataStore(new BMessage(* refs_message)); // leak? 28 // prevents bug with B_SIMPLE_DATA 29 // (drag&drop messages) 30 } 31 32 ExpanderThread::~ExpanderThread() 33 { 34 delete fWindowMessenger; 35 delete [] fExpanderOutputBuffer; 36 } 37 38 status_t 39 ExpanderThread::ThreadStartup() 40 { 41 status_t status = B_OK; 42 entry_ref srcRef, destRef; 43 BString cmd; 44 45 if ((status = GetDataStore()->FindRef("srcRef", &srcRef)) != B_OK) 46 return status; 47 if ((status = GetDataStore()->FindRef("destRef", &destRef)) == B_OK) { 48 BPath path(&destRef); 49 chdir(path.Path()); 50 } 51 if ((status = GetDataStore()->FindString("cmd", &cmd)) != B_OK) 52 return status; 53 54 BPath path(&srcRef); 55 BString pathString(path.Path()); 56 pathString.CharacterEscape("\"$`", '\\'); 57 pathString.Prepend("\""); 58 pathString.Append("\""); 59 cmd.ReplaceAll("%s", pathString.String()); 60 61 int32 argc = 3; 62 const char ** argv = new const char * [argc + 1]; 63 64 argv[0] = strdup("/bin/sh"); 65 argv[1] = strdup("-c"); 66 argv[2] = strdup(cmd.String()); 67 argv[argc] = NULL; 68 69 fThreadId = PipeCommand(argc, argv, fStdIn, fStdOut, fStdErr); 70 71 delete [] argv; 72 73 if (fThreadId < 0) 74 return fThreadId; 75 76 // lower the command priority since it is a background task. 77 set_thread_priority(fThreadId, B_LOW_PRIORITY); 78 79 resume_thread(fThreadId); 80 81 fExpanderOutput = fdopen(fStdOut, "r"); 82 83 return B_OK; 84 } 85 86 status_t 87 ExpanderThread::ExecuteUnit(void) 88 { 89 // read output from command 90 // send it to window 91 92 char *output_string = fgets(fExpanderOutputBuffer , 4096 - 1, fExpanderOutput); 93 94 if (output_string == NULL) 95 return EOF; 96 97 BMessage message('outp'); 98 message.AddString("output", output_string); 99 for (int32 i = 0; i < 5; i++) { 100 output_string = fgets(fExpanderOutputBuffer , 4096 - 1, fExpanderOutput); 101 if (!output_string) 102 break; 103 message.AddString("output", output_string); 104 } 105 fWindowMessenger->SendMessage(&message); 106 107 return B_OK; 108 } 109 110 status_t 111 ExpanderThread::ThreadShutdown(void) 112 { 113 close(fStdIn); 114 close(fStdOut); 115 close(fStdErr); 116 117 return B_OK; 118 } 119 120 void 121 ExpanderThread::ThreadStartupFailed(status_t status) 122 { 123 fprintf(stderr, "ExpanderThread::ThreadStartupFailed() : %s\n", strerror(status)); 124 125 Quit(); 126 } 127 128 void 129 ExpanderThread::ExecuteUnitFailed(status_t status) 130 { 131 if (status == EOF) { 132 // thread has finished, been quit or killed, we don't know 133 fWindowMessenger->SendMessage(new BMessage('exit')); 134 } else { 135 // explicit error - communicate error to Window 136 fWindowMessenger->SendMessage(new BMessage('exrr')); 137 } 138 139 Quit(); 140 } 141 142 void 143 ExpanderThread::ThreadShutdownFailed(status_t status) 144 { 145 fprintf(stderr, "ExpanderThread::ThreadShutdownFailed() %s\n", strerror(status)); 146 } 147 148 149 status_t 150 ExpanderThread::ProcessRefs(BMessage *msg) 151 { 152 return B_OK; 153 } 154 155 thread_id 156 ExpanderThread::PipeCommand(int argc, const char **argv, int &in, int &out, int &err, const char **envp) 157 { 158 // This function written by Peter Folk <pfolk@uni.uiuc.edu> 159 // and published in the BeDevTalk FAQ 160 // http://www.abisoft.com/faq/BeDevTalk_FAQ.html#FAQ-209 161 162 // Save current FDs 163 int old_in = dup(0); 164 int old_out = dup(1); 165 int old_err = dup(2); 166 167 int filedes[2]; 168 169 /* Create new pipe FDs as stdin, stdout, stderr */ 170 pipe(filedes); dup2(filedes[0], 0); close(filedes[0]); 171 in = filedes[1]; // Write to in, appears on cmd's stdin 172 pipe(filedes); dup2(filedes[1], 1); close(filedes[1]); 173 out = filedes[0]; // Read from out, taken from cmd's stdout 174 pipe(filedes); dup2(filedes[1], 2); close(filedes[1]); 175 err = filedes[0]; // Read from err, taken from cmd's stderr 176 177 // "load" command. 178 thread_id ret = load_image(argc, argv, envp); 179 180 if (ret < B_OK) 181 return ret; 182 183 // thread ret is now suspended. 184 185 setpgid(ret, ret); 186 187 // Restore old FDs 188 close(0); dup(old_in); close(old_in); 189 close(1); dup(old_out); close(old_out); 190 close(2); dup(old_err); close(old_err); 191 192 /* Theoretically I should do loads of error checking, but 193 the calls aren't very likely to fail, and that would 194 muddy up the example quite a bit. YMMV. */ 195 196 return ret; 197 } 198 199 200 status_t 201 ExpanderThread::SuspendExternalExpander() 202 { 203 thread_info thread_info; 204 status_t status = get_thread_info(fThreadId, &thread_info); 205 206 if (status == B_OK) 207 return send_signal(-fThreadId, SIGSTOP); 208 else 209 return status; 210 } 211 212 status_t 213 ExpanderThread::ResumeExternalExpander() 214 { 215 thread_info thread_info; 216 status_t status = get_thread_info(fThreadId, &thread_info); 217 218 if (status == B_OK) 219 return send_signal(-fThreadId, SIGCONT); 220 else 221 return status; 222 } 223 224 status_t 225 ExpanderThread::InterruptExternalExpander() 226 { 227 thread_info thread_info; 228 status_t status = get_thread_info(fThreadId, &thread_info); 229 230 if (status == B_OK) { 231 status = send_signal(-fThreadId, SIGINT); 232 WaitOnExternalExpander(); 233 } 234 return status; 235 } 236 237 status_t 238 ExpanderThread::WaitOnExternalExpander() 239 { 240 thread_info thread_info; 241 status_t status = get_thread_info(fThreadId, &thread_info); 242 243 if (status == B_OK) 244 return wait_for_thread(fThreadId, &status); 245 else 246 return status; 247 } 248