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