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