164 " |
164 " |
165 Pipestreams allow reading or writing from/to a unix command. |
165 Pipestreams allow reading or writing from/to a unix command. |
166 For example, to get a stream reading the output of an 'ls -l' |
166 For example, to get a stream reading the output of an 'ls -l' |
167 command, a PipeStream can be created with: |
167 command, a PipeStream can be created with: |
168 |
168 |
169 PipeStream readingFrom:'ls -l' |
169 PipeStream readingFrom:'ls -l' |
170 |
170 |
171 the characters of the commands output can be read using the |
171 the characters of the commands output can be read using the |
172 standard stream messages as next, nextLine etc. |
172 standard stream messages as next, nextLine etc. |
173 |
173 |
174 If a writing pipeStream is written to, after the command has finished, |
174 If a writing pipeStream is written to, after the command has finished, |
175 UNIX will generate an error-signal (SIGPIPE), which will raise the BrokenPipeSignal. |
175 UNIX will generate an error-signal (SIGPIPE), which will raise the BrokenPipeSignal. |
176 Thus, to handle this condition correctly, the following code is suggested: |
176 Thus, to handle this condition correctly, the following code is suggested: |
177 |
177 |
178 |p| |
178 |p| |
179 p := PipeStream writingTo:'echo hello'. |
179 p := PipeStream writingTo:'echo hello'. |
180 PipeStream brokenPipeSignal handle:[:ex | |
180 PipeStream brokenPipeSignal handle:[:ex | |
181 'broken pipe' printNewline. |
181 'broken pipe' printNewline. |
182 p shutDown. |
182 p shutDown. |
183 ex return |
183 ex return |
184 ] do:[ |
184 ] do:[ |
185 p nextPutLine:'oops'. |
185 p nextPutLine:'oops'. |
186 'after write' printNewline. |
186 'after write' printNewline. |
187 p close. |
187 p close. |
188 'after close' printNewline |
188 'after close' printNewline |
189 ] |
189 ] |
190 |
190 |
191 Notice, that iff the Stream is buffered, the Signal may occur some time after |
191 Notice, that iff the Stream is buffered, the Signal may occur some time after |
192 the write - or even at close time; to avoid a recursive signal in the exception |
192 the write - or even at close time; to avoid a recursive signal in the exception |
193 handler, a #shutDown is useful there; if you use close in the handler, this would |
193 handler, a #shutDown is useful there; if you use close in the handler, this would |
194 try to send any buffered output to the pipe, leading to another brokenPipe exception. |
194 try to send any buffered output to the pipe, leading to another brokenPipe exception. |
219 BrokenPipeSignal nameClass:self message:#brokenPipeSignal. |
219 BrokenPipeSignal nameClass:self message:#brokenPipeSignal. |
220 BrokenPipeSignal notifierString:'write on a pipe with no one to read'. |
220 BrokenPipeSignal notifierString:'write on a pipe with no one to read'. |
221 ] |
221 ] |
222 ! ! |
222 ! ! |
223 |
223 |
|
224 !PipeStream class methodsFor:'helpers'! |
|
225 |
|
226 createCOMFileForVMSCommands:aCollectionOfCommandStrings |
|
227 "since DCL seems to not support multiple commands in one |
|
228 line, create a temporary COM file for them and let DCL |
|
229 execute that one. |
|
230 A kludge around a poor CLI design." |
|
231 |
|
232 |tmpComFile s| |
|
233 |
|
234 tmpComFile := Filename newTemporary withSuffix:'COM'. |
|
235 s := tmpComFile writeStream. |
|
236 aCollectionOfCommandStrings do:[:aCommand | |
|
237 s nextPutAll:'$'. |
|
238 s nextPutAll:aCommand. |
|
239 s nextPut:(Character nl). |
|
240 ]. |
|
241 s close. |
|
242 ^ tmpComFile. |
|
243 ! |
|
244 |
|
245 createCOMFileForVMSCommand:aCommandString in:aDirectory |
|
246 "since DCL seems to not support multiple commands in one |
|
247 line, create a temporary COM file for a set def, followed |
|
248 by the actual command string. |
|
249 A kludge around a poor CLI design." |
|
250 |
|
251 ^ self |
|
252 createCOMFileForVMSCommands:(Array |
|
253 with:('$set def ' , aDirectory asFilename pathName asFilename osNameForDirectory) |
|
254 with:('$' , aCommandString)). |
|
255 ! ! |
|
256 |
224 !PipeStream class methodsFor:'instance creation'! |
257 !PipeStream class methodsFor:'instance creation'! |
225 |
258 |
226 readingFrom:commandString |
259 readingFrom:commandString |
227 "create and return a new pipeStream which can read from the unix command |
260 "create and return a new pipeStream which can read from the unix command |
228 given by command." |
261 given by command." |
229 |
262 |
230 ^ (self basicNew) readingFrom:commandString |
263 ^ (self basicNew) readingFrom:commandString |
231 |
264 |
232 "unix: |
265 "unix: |
233 PipeStream readingFrom:'ls -l'. |
266 PipeStream readingFrom:'ls -l'. |
234 " |
267 " |
235 |
268 |
236 " |
269 " |
237 p := PipeStream readingFrom:'ls -l'. |
270 p := PipeStream readingFrom:'ls -l'. |
238 Transcript showCR:p nextLine. |
271 Transcript showCR:p nextLine. |
239 p close |
272 p close |
240 " |
273 " |
241 |
274 |
242 " |
275 " |
243 |s| |
276 |s| |
244 s := PipeStream readingFrom:'sh -c sleep\ 600'. |
277 s := PipeStream readingFrom:'sh -c sleep\ 600'. |
245 (Delay forSeconds:2) wait. |
278 (Delay forSeconds:2) wait. |
246 s shutDown |
279 s shutDown |
247 " |
280 " |
248 |
281 |
249 "vms: |
282 "vms: |
250 PipeStream readingFrom:'dir'. |
283 PipeStream readingFrom:'dir'. |
251 " |
284 " |
252 |
285 |
253 " |
286 " |
254 |p| |
287 |p| |
255 p := PipeStream readingFrom:'dir'. |
288 p := PipeStream readingFrom:'dir'. |
256 Transcript showCR:p nextLine. |
289 Transcript showCR:p nextLine. |
257 p close |
290 p close |
258 " |
291 " |
259 |
292 |
260 "msdos: |
293 "msdos: |
261 PipeStream readingFrom:'dir'. |
294 PipeStream readingFrom:'dir'. |
262 " |
295 " |
263 " |
296 " |
264 |p| |
297 |p| |
265 p := PipeStream readingFrom:'dir'. |
298 p := PipeStream readingFrom:'dir'. |
266 Transcript showCR:p nextLine. |
299 Transcript showCR:p nextLine. |
267 p close |
300 p close |
268 " |
301 " |
269 |
302 |
270 "Modified: 24.4.1996 / 09:09:25 / stefan" |
303 "Modified: 24.4.1996 / 09:09:25 / stefan" |
271 ! |
304 ! |
272 |
305 |
|
306 readingFrom:commandString inDirectory:aDirectory |
|
307 "similar to #readingFrom, but changes the directory while |
|
308 executing the command. Use this if a command is to be |
|
309 executed in another directory, to avoid any OS dependencies |
|
310 in your code." |
|
311 |
|
312 |cmd tmpComFile pipe| |
|
313 |
|
314 (OperatingSystem platformName == #vms) ifTrue:[ |
|
315 tmpComFile := self createCOMFileForVMSCommand:commandString in:aDirectory. |
|
316 cmd := '@' , tmpComFile osName. |
|
317 pipe := self readingFrom:cmd. |
|
318 pipe notNil ifTrue:[ |
|
319 pipe exitAction:[tmpComFile delete]. |
|
320 ]. |
|
321 ^ pipe |
|
322 ]. |
|
323 |
|
324 "/ unix - prepend a 'cd' to the command |
|
325 cmd := 'cd ' , aDirectory asFilename pathName, '; ' , commandString. |
|
326 ^ self readingFrom:cmd |
|
327 ! |
|
328 |
273 writingTo:commandString |
329 writingTo:commandString |
274 "create and return a new pipeStream which can write to the unix command |
330 "create and return a new pipeStream which can write to the unix command |
275 given by command." |
331 given by command." |
276 |
332 |
277 ^ (self basicNew) writingTo:commandString |
333 ^ (self basicNew) writingTo:commandString |
278 |
334 |
279 "unix: |
335 "unix: |
280 PipeStream writingTo:'sort' |
336 PipeStream writingTo:'sort' |
281 " |
337 " |
|
338 ! |
|
339 |
|
340 writingTo:commandString inDirectory:aDirectory |
|
341 "similar to #writingTo, but changes the directory while |
|
342 executing the command. Use this if a command is to be |
|
343 executed in another directory, to avoid any OS dependencies |
|
344 in your code." |
|
345 |
|
346 |cmd tmpComFile pipe| |
|
347 |
|
348 (OperatingSystem platformName == #vms) ifTrue:[ |
|
349 tmpComFile := self createCOMFileForVMSCommand:commandString in:aDirectory. |
|
350 cmd := '@' , tmpComFile osName. |
|
351 pipe := self writingTo:cmd. |
|
352 pipe notNil ifTrue:[ |
|
353 pipe exitAction:[tmpComFile delete]. |
|
354 ]. |
|
355 ^ pipe |
|
356 ]. |
|
357 |
|
358 "/ unix - prepend a 'cd' to the command |
|
359 cmd := 'cd ' , aDirectory asFilename pathName, '; ' , commandString. |
|
360 ^ self writingTo:cmd |
282 ! ! |
361 ! ! |
283 |
362 |
284 !PipeStream class methodsFor:'Signal constants'! |
363 !PipeStream class methodsFor:'Signal constants'! |
285 |
364 |
286 brokenPipeSignal |
365 brokenPipeSignal |
364 shutDown |
450 shutDown |
365 "close the Stream, ignoring any broken-pipe errors. |
451 "close the Stream, ignoring any broken-pipe errors. |
366 Terminate the command" |
452 Terminate the command" |
367 |
453 |
368 BrokenPipeSignal catch:[ |
454 BrokenPipeSignal catch:[ |
369 |tpid| |
455 |tpid| |
370 |
456 |
371 Lobby unregister:self. |
457 Lobby unregister:self. |
372 self closeFileDescriptor. |
458 self closeFileDescriptor. |
373 tpid := pid. "copy pid to avoid race" |
459 tpid := pid. "copy pid to avoid race" |
374 tpid notNil ifTrue:[ |
460 tpid notNil ifTrue:[ |
375 "/ |
461 "/ |
376 "/ Terminate both the process and group, just in case the |
462 "/ Terminate both the process and group, just in case the |
377 "/ operating system does not support process groups. |
463 "/ operating system does not support process groups. |
378 "/ |
464 "/ |
379 OperatingSystem terminateProcess:tpid. |
465 OperatingSystem terminateProcess:tpid. |
380 OperatingSystem terminateProcessGroup:tpid. |
466 OperatingSystem terminateProcessGroup:tpid. |
381 pid := nil. |
467 pid := nil. |
382 ]. |
468 ]. |
|
469 |
383 ] |
470 ] |
384 |
471 |
385 "Modified: 23.5.1996 / 09:15:41 / stefan" |
472 "Modified: 23.5.1996 / 09:15:41 / stefan" |
386 ! ! |
473 ! ! |
387 |
474 |
388 !PipeStream methodsFor:'private'! |
475 !PipeStream methodsFor:'private'! |
|
476 |
|
477 exitAction:aBlock |
|
478 "define a block to be evaluated when the pipe is closed. |
|
479 This is only used with VMS, to remove any temporary COM file. |
|
480 (see readingFrom:inDirectory:)" |
|
481 |
|
482 exitAction := aBlock |
|
483 ! |
389 |
484 |
390 openPipeFor:aCommandString withMode:mode |
485 openPipeFor:aCommandString withMode:mode |
391 "open a pipe to the unix command in commandString; |
486 "open a pipe to the unix command in commandString; |
392 mode may be 'r' or 'w'" |
487 mode may be 'r' or 'w'" |
393 |
488 |
394 |blocked pipeFdArray execFdArray execFd myFd |
489 |blocked pipeFdArray execFdArray execFd myFd |
395 osType shellPath shellArgs closeFdArray mbx mbxName| |
490 osType shellPath shellArgs closeFdArray mbx mbxName| |
396 |
491 |
397 filePointer notNil ifTrue:[ |
492 filePointer notNil ifTrue:[ |
398 "the pipe was already open ... |
493 "the pipe was already open ... |
399 this should (can) not happen." |
494 this should (can) not happen." |
400 ^ self errorOpen |
495 ^ self errorOpen |
401 ]. |
496 ]. |
402 lastErrorNumber := nil. |
497 lastErrorNumber := nil. |
403 exitStatus := nil. |
498 exitStatus := nil. |
404 exitSema := Semaphore new name:'pipe exitSema'. |
499 exitSema := Semaphore new name:'pipe exitSema'. |
405 |
500 |
406 osType := OperatingSystem platformName. |
501 osType := OperatingSystem platformName. |
407 osType == #vms ifTrue:[ |
502 osType == #vms ifTrue:[ |
408 mbx := OperatingSystem createMailBox. |
503 mbx := OperatingSystem createMailBox. |
409 mbx isNil ifTrue:[ |
504 mbx isNil ifTrue:[ |
410 lastErrorNumber := OperatingSystem currentErrorNumber. |
505 lastErrorNumber := OperatingSystem currentErrorNumber. |
411 ^ self openError |
506 ^ self openError |
412 ]. |
507 ]. |
413 'mailBox is ' print. mbx printCR. |
508 mbxName := OperatingSystem mailBoxNameOf:mbx. |
|
509 'mailBox is ' print. mbx print. ' name is ' print. mbxName printCR. |
414 shellPath := ''. |
510 shellPath := ''. |
415 shellArgs := aCommandString. |
511 shellArgs := aCommandString. |
416 |
512 |
417 mode = 'r' ifTrue:[ |
513 mode = 'r' ifTrue:[ |
418 execFdArray := Array with:0 with:mbx with:2. |
514 execFdArray := Array with:0 with:mbx with:2. |
419 ] ifFalse:[ |
515 ] ifFalse:[ |
420 execFdArray := Array with:mbx with:1 with:2. |
516 execFdArray := Array with:mbx with:1 with:2. |
421 ]. |
517 ]. |
422 closeFdArray := nil. |
518 closeFdArray := nil. |
423 ] ifFalse:[ |
519 ] ifFalse:[ |
424 pipeFdArray := OperatingSystem makePipe. |
520 pipeFdArray := OperatingSystem makePipe. |
425 pipeFdArray isNil ifTrue:[ |
521 pipeFdArray isNil ifTrue:[ |
426 lastErrorNumber := OperatingSystem currentErrorNumber. |
522 lastErrorNumber := OperatingSystem currentErrorNumber. |
427 ^ self openError |
523 ^ self openError |
428 ]. |
524 ]. |
429 |
525 |
430 osType == #unix ifTrue:[ |
526 osType == #unix ifTrue:[ |
431 shellPath := '/bin/sh'. |
527 shellPath := '/bin/sh'. |
432 shellArgs := Array with:'sh' with:'-c' with:aCommandString. |
528 shellArgs := Array with:'sh' with:'-c' with:aCommandString. |
433 ] ifFalse:[ |
529 ] ifFalse:[ |
434 osType == #win32 ifTrue:[ |
530 osType == #win32 ifTrue:[ |
435 shellPath := 'C:\WINNT\System32\cmd /c'. |
531 shellPath := 'C:\WINNT\System32\cmd /c'. |
436 shellArgs := aCommandString. |
532 shellArgs := aCommandString. |
437 ] ifFalse:[ |
533 ] ifFalse:[ |
438 OperatingSystem closeFd:execFd; closeFd:myFd. |
534 OperatingSystem closeFd:execFd; closeFd:myFd. |
439 "/ |
535 "/ |
440 "/ dont know how to do it ... |
536 "/ dont know how to do it ... |
441 "/ |
537 "/ |
442 ^ self openError |
538 ^ self openError |
443 ] |
539 ] |
444 ]. |
540 ]. |
445 mode = 'r' ifTrue:[ |
541 mode = 'r' ifTrue:[ |
446 execFd := pipeFdArray at:2. |
542 execFd := pipeFdArray at:2. |
447 execFdArray := Array with:0 with:execFd with:2. |
543 execFdArray := Array with:0 with:execFd with:2. |
448 myFd := pipeFdArray at:1. |
544 myFd := pipeFdArray at:1. |
449 ] ifFalse:[ |
545 ] ifFalse:[ |
450 execFd := pipeFdArray at:1. |
546 execFd := pipeFdArray at:1. |
451 execFdArray := Array with:execFd with:1 with:2. |
547 execFdArray := Array with:execFd with:1 with:2. |
452 myFd := pipeFdArray at:2. |
548 myFd := pipeFdArray at:2. |
453 ]. |
549 ]. |
454 closeFdArray := Array with:myFd. |
550 closeFdArray := Array with:myFd. |
455 ]. |
551 ]. |
456 |
552 |
457 |
553 |
458 "/ must block here, to avoid races due to early finishing |
554 "/ must block here, to avoid races due to early finishing |
460 |
556 |
461 blocked := OperatingSystem blockInterrupts. |
557 blocked := OperatingSystem blockInterrupts. |
462 |
558 |
463 pid := Processor |
559 pid := Processor |
464 monitor:[ |
560 monitor:[ |
465 OperatingSystem |
561 OperatingSystem |
466 exec:shellPath |
562 exec:shellPath |
467 withArguments:shellArgs |
563 withArguments:shellArgs |
468 fileDescriptors:execFdArray |
564 fileDescriptors:execFdArray |
469 closeDescriptors:closeFdArray |
565 closeDescriptors:closeFdArray |
470 fork:true |
566 fork:true |
471 newPgrp:true. |
567 newPgrp:true. |
472 ] |
568 ] |
473 action:[:status | |
569 action:[:status | |
474 status stillAlive ifFalse:[ |
570 status stillAlive ifFalse:[ |
475 exitStatus := status. |
571 exitStatus := status. |
476 pid := nil. |
572 pid := nil. |
477 exitSema signal. |
573 exitSema signal. |
478 ]. |
574 ]. |
479 ]. |
575 ]. |
480 |
576 |
481 (osType ~~ #vms) ifTrue:[ |
577 (osType ~~ #vms) ifTrue:[ |
482 OperatingSystem closeFd:execFd. |
578 OperatingSystem closeFd:execFd. |
483 ]. |
579 ]. |
484 |
580 |
485 pid notNil ifTrue:[ |
581 pid notNil ifTrue:[ |
486 (osType == #win32) ifTrue:[ |
582 (osType == #win32) ifTrue:[ |
487 self setFileHandle:myFd mode:mode |
583 self setFileHandle:myFd mode:mode |
488 ] ifFalse:[ |
584 ] ifFalse:[ |
489 (osType == #vms) ifTrue:[ |
585 (osType == #vms) ifTrue:[ |
490 "/ |
586 "/ |
491 "/ reopen the mailbox as a file ... |
587 "/ reopen the mailbox as a file ... |
492 "/ |
588 "/ |
493 mbxName := OperatingSystem mailBoxNameOf:mbx. |
589 mbxName := OperatingSystem mailBoxNameOf:mbx. |
494 mbxName notNil ifTrue:[ |
590 mbxName notNil ifTrue:[ |
495 super open:mbxName withMode:mode |
591 super open:mbxName withMode:mode |
496 ]. |
592 ]. |
497 ] ifFalse:[ |
593 ] ifFalse:[ |
498 self setFileDescriptor:myFd mode:mode. |
594 self setFileDescriptor:myFd mode:mode. |
499 ] |
595 ] |
500 ] |
596 ] |
501 ] ifFalse:[ |
597 ] ifFalse:[ |
502 lastErrorNumber := OperatingSystem currentErrorNumber. |
598 lastErrorNumber := OperatingSystem currentErrorNumber. |
503 osType ~~ #vms ifTrue:[ |
599 osType ~~ #vms ifTrue:[ |
504 OperatingSystem closeFd:myFd. |
600 OperatingSystem closeFd:myFd. |
505 ] ifFalse:[ |
601 ] ifFalse:[ |
506 OperatingSystem destroyMailBox:mbx |
602 OperatingSystem destroyMailBox:mbx |
507 ]. |
603 ]. |
508 ]. |
604 ]. |
509 |
605 |
510 blocked ifFalse:[ |
606 blocked ifFalse:[ |
511 OperatingSystem unblockInterrupts |
607 OperatingSystem unblockInterrupts |
512 ]. |
608 ]. |
513 |
609 |
514 lastErrorNumber notNil ifTrue:[ |
610 lastErrorNumber notNil ifTrue:[ |
515 " |
611 " |
516 the pipe open failed for some reason ... |
612 the pipe open failed for some reason ... |
517 ... this may be either due to an invalid command string, |
613 ... this may be either due to an invalid command string, |
518 or due to the system running out of memory (when forking |
614 or due to the system running out of memory (when forking |
519 the unix process) |
615 the unix process) |
520 " |
616 " |
521 ^ self openError |
617 ^ self openError |
522 ]. |
618 ]. |
523 |
619 |
524 commandString := aCommandString. |
620 commandString := aCommandString. |
525 %{ |
621 %{ |
526 /* LINUX stdio is corrupt here ... */ |
622 /* LINUX stdio is corrupt here ... */ |