1 /* 2 * Copyright 2004-2010, 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 "ExpanderThread.h" 7 8 9 #include <errno.h> 10 #include <image.h> 11 #include <signal.h> 12 #include <termios.h> 13 #include <unistd.h> 14 15 #include <Messenger.h> 16 #include <Path.h> 17 18 19 const char* ExpanderThreadName = "ExpanderThread"; 20 21 22 ExpanderThread::ExpanderThread(BMessage* refs_message, BMessenger* messenger) 23 : 24 GenericThread(ExpanderThreadName, B_NORMAL_PRIORITY, refs_message), 25 fWindowMessenger(messenger), 26 fThreadId(-1), 27 fStdIn(-1), 28 fStdOut(-1), 29 fStdErr(-1), 30 fExpanderOutput(NULL), 31 fExpanderError(NULL) 32 { 33 SetDataStore(new BMessage(*refs_message)); 34 // leak? 35 // prevents bug with B_SIMPLE_DATA 36 // (drag&drop messages) 37 } 38 39 40 ExpanderThread::~ExpanderThread() 41 { 42 delete fWindowMessenger; 43 } 44 45 46 status_t 47 ExpanderThread::ThreadStartup() 48 { 49 status_t status = B_OK; 50 entry_ref srcRef; 51 entry_ref destRef; 52 BString cmd; 53 54 if ((status = GetDataStore()->FindRef("srcRef", &srcRef)) != B_OK) 55 return status; 56 57 if (GetDataStore()->FindRef("destRef", &destRef) == B_OK) { 58 BPath path(&destRef); 59 chdir(path.Path()); 60 } 61 62 if ((status = GetDataStore()->FindString("cmd", &cmd)) != B_OK) 63 return status; 64 65 BPath path(&srcRef); 66 BString pathString(path.Path()); 67 pathString.CharacterEscape("\\\"$`", '\\'); 68 pathString.Prepend("\""); 69 pathString.Append("\""); 70 cmd.ReplaceAll("%s", pathString.String()); 71 72 int32 argc = 3; 73 const char** argv = new const char * [argc + 1]; 74 75 argv[0] = strdup("/bin/sh"); 76 argv[1] = strdup("-c"); 77 argv[2] = strdup(cmd.String()); 78 argv[argc] = NULL; 79 80 fThreadId = PipeCommand(argc, argv, fStdIn, fStdOut, fStdErr); 81 82 delete [] argv; 83 84 if (fThreadId < 0) 85 return fThreadId; 86 87 // lower the command priority since it is a background task. 88 set_thread_priority(fThreadId, B_LOW_PRIORITY); 89 90 resume_thread(fThreadId); 91 92 int flags = fcntl(fStdOut, F_GETFL, 0); 93 flags |= O_NONBLOCK; 94 fcntl(fStdOut, F_SETFL, flags); 95 flags = fcntl(fStdErr, F_GETFL, 0); 96 flags |= O_NONBLOCK; 97 fcntl(fStdErr, F_SETFL, flags); 98 99 fExpanderOutput = fdopen(fStdOut, "r"); 100 fExpanderError = fdopen(fStdErr, "r"); 101 102 return B_OK; 103 } 104 105 106 status_t 107 ExpanderThread::ExecuteUnit(void) 108 { 109 // read output and error from command 110 // send it to window 111 112 BMessage message('outp'); 113 bool outputAdded = false; 114 for (int32 i = 0; i < 50; i++) { 115 char* output_string = fgets(fExpanderOutputBuffer , LINE_MAX, 116 fExpanderOutput); 117 if (output_string == NULL) 118 break; 119 120 message.AddString("output", output_string); 121 outputAdded = true; 122 } 123 if (outputAdded) 124 fWindowMessenger->SendMessage(&message); 125 126 if (feof(fExpanderOutput)) 127 return EOF; 128 129 char* error_string = fgets(fExpanderOutputBuffer, LINE_MAX, 130 fExpanderError); 131 if (error_string != NULL && strcmp(error_string, "\n")) { 132 BMessage message('errp'); 133 message.AddString("error", error_string); 134 fWindowMessenger->SendMessage(&message); 135 } 136 137 // streams are non blocking, sleep every 100ms 138 snooze(100000); 139 140 return B_OK; 141 } 142 143 144 void 145 ExpanderThread::PushInput(BString text) 146 { 147 text += "\n"; 148 write(fStdIn, text.String(), text.Length()); 149 } 150 151 152 status_t 153 ExpanderThread::ThreadShutdown(void) 154 { 155 close(fStdIn); 156 close(fStdOut); 157 close(fStdErr); 158 159 return B_OK; 160 } 161 162 163 void 164 ExpanderThread::ThreadStartupFailed(status_t status) 165 { 166 fprintf(stderr, "ExpanderThread::ThreadStartupFailed() : %s\n", 167 strerror(status)); 168 169 Quit(); 170 } 171 172 173 void 174 ExpanderThread::ExecuteUnitFailed(status_t status) 175 { 176 if (status == EOF) { 177 // thread has finished, been quit or killed, we don't know 178 fWindowMessenger->SendMessage(new BMessage('exit')); 179 } else { 180 // explicit error - communicate error to Window 181 fWindowMessenger->SendMessage(new BMessage('exrr')); 182 } 183 184 Quit(); 185 } 186 187 188 void 189 ExpanderThread::ThreadShutdownFailed(status_t status) 190 { 191 fprintf(stderr, "ExpanderThread::ThreadShutdownFailed() %s\n", 192 strerror(status)); 193 } 194 195 196 status_t 197 ExpanderThread::ProcessRefs(BMessage *msg) 198 { 199 return B_OK; 200 } 201 202 203 thread_id 204 ExpanderThread::PipeCommand(int argc, const char** argv, int& in, int& out, 205 int& err, const char** envp) 206 { 207 // This function written by Peter Folk <pfolk@uni.uiuc.edu> 208 // and published in the BeDevTalk FAQ 209 // http://www.abisoft.com/faq/BeDevTalk_FAQ.html#FAQ-209 210 211 // Save current FDs 212 int old_out = dup(1); 213 int old_err = dup(2); 214 215 int filedes[2]; 216 217 // create new pipe FDs as stdout, stderr 218 pipe(filedes); dup2(filedes[1], 1); close(filedes[1]); 219 out = filedes[0]; // Read from out, taken from cmd's stdout 220 pipe(filedes); dup2(filedes[1], 2); close(filedes[1]); 221 err = filedes[0]; // Read from err, taken from cmd's stderr 222 223 // taken from pty.cpp 224 // create a tty for stdin, as utilities don't generally use stdin 225 int master = posix_openpt(O_RDWR); 226 if (master < 0) 227 return -1; 228 229 int slave; 230 const char* ttyName; 231 if (grantpt(master) != 0 || unlockpt(master) != 0 232 || (ttyName = ptsname(master)) == NULL 233 || (slave = open(ttyName, O_RDWR | O_NOCTTY)) < 0) { 234 close(master); 235 return -1; 236 } 237 238 int pid = fork(); 239 if (pid < 0) { 240 close(master); 241 close(slave); 242 return -1; 243 } 244 245 // child 246 if (pid == 0) { 247 close(master); 248 249 setsid(); 250 if (ioctl(slave, TIOCSCTTY, NULL) != 0) { 251 close(slave); 252 return -1; 253 } 254 255 dup2(slave, 0); 256 close(slave); 257 258 // "load" command. 259 execv(argv[0], (char *const *)argv); 260 261 // shouldn't return 262 return -1; 263 } 264 265 // parent 266 close (slave); 267 in = master; 268 269 // Restore old FDs 270 close(1); dup(old_out); close(old_out); 271 close(2); dup(old_err); close(old_err); 272 273 // Theoretically I should do loads of error checking, but 274 // the calls aren't very likely to fail, and that would 275 // muddy up the example quite a bit. YMMV. 276 277 return pid; 278 } 279 280 281 status_t 282 ExpanderThread::SuspendExternalExpander() 283 { 284 thread_info info; 285 status_t status = get_thread_info(fThreadId, &info); 286 287 if (status == B_OK) 288 return send_signal(-fThreadId, SIGSTOP); 289 else 290 return status; 291 } 292 293 294 status_t 295 ExpanderThread::ResumeExternalExpander() 296 { 297 thread_info info; 298 status_t status = get_thread_info(fThreadId, &info); 299 300 if (status == B_OK) 301 return send_signal(-fThreadId, SIGCONT); 302 else 303 return status; 304 } 305 306 307 status_t 308 ExpanderThread::InterruptExternalExpander() 309 { 310 thread_info info; 311 status_t status = get_thread_info(fThreadId, &info); 312 313 if (status == B_OK) { 314 status = send_signal(-fThreadId, SIGINT); 315 WaitOnExternalExpander(); 316 } 317 318 return status; 319 } 320 321 322 status_t 323 ExpanderThread::WaitOnExternalExpander() 324 { 325 thread_info info; 326 status_t status = get_thread_info(fThreadId, &info); 327 328 if (status == B_OK) 329 return wait_for_thread(fThreadId, &status); 330 else 331 return status; 332 } 333