4 * This file contains the top-level command procedures for
5 * commands in the Tcl core that require UNIX facilities
6 * such as files and process execution. Much of the code
7 * in this file is based on earlier versions contributed
8 * by Karl Lehenbauer, Mark Diekhans and Peter da Silva.
10 * Copyright 1991 Regents of the University of California
11 * Permission to use, copy, modify, and distribute this
12 * software and its documentation for any purpose and without
13 * fee is hereby granted, provided that this copyright
14 * notice appears in all copies. The University of California
15 * makes no representations about the suitability of this
16 * software for any purpose. It is provided "as is" without
17 * express or implied warranty.
21 static char rcsid
[] = "$Header: /user6/ouster/tcl/RCS/tclUnixAZ.c,v 1.36 92/04/16 13:32:02 ouster Exp $ sprite (Berkeley)";
28 * The variable below caches the name of the current working directory
29 * in order to avoid repeated calls to getwd. The string is malloc-ed.
30 * NULL means the cache needs to be refreshed.
33 static char *currentDir
= NULL
;
36 * Prototypes for local procedures defined in this file:
39 static int CleanupChildren
_ANSI_ARGS_((Tcl_Interp
*interp
,
40 int numPids
, int *pidPtr
, int errorId
));
41 static char * GetFileType
_ANSI_ARGS_((int mode
));
42 static int StoreStatData
_ANSI_ARGS_((Tcl_Interp
*interp
,
43 char *varName
, struct stat
*statPtr
));
46 *----------------------------------------------------------------------
50 * This procedure is invoked to process the "cd" Tcl command.
51 * See the user documentation for details on what it does.
54 * A standard Tcl result.
57 * See the user documentation.
59 *----------------------------------------------------------------------
64 Tcl_CdCmd(dummy
, interp
, argc
, argv
)
65 ClientData dummy
; /* Not used. */
66 Tcl_Interp
*interp
; /* Current interpreter. */
67 int argc
; /* Number of arguments. */
68 char **argv
; /* Argument strings. */
73 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
74 " dirName\"", (char *) NULL
);
83 dirName
= Tcl_TildeSubst(interp
, dirName
);
84 if (dirName
== NULL
) {
87 if (currentDir
!= NULL
) {
91 if (chdir(dirName
) != 0) {
92 Tcl_AppendResult(interp
, "couldn't change working directory to \"",
93 dirName
, "\": ", Tcl_UnixError(interp
), (char *) NULL
);
100 *----------------------------------------------------------------------
104 * This procedure is invoked to process the "close" Tcl command.
105 * See the user documentation for details on what it does.
108 * A standard Tcl result.
111 * See the user documentation.
113 *----------------------------------------------------------------------
118 Tcl_CloseCmd(dummy
, interp
, argc
, argv
)
119 ClientData dummy
; /* Not used. */
120 Tcl_Interp
*interp
; /* Current interpreter. */
121 int argc
; /* Number of arguments. */
122 char **argv
; /* Argument strings. */
128 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
129 " fileId\"", (char *) NULL
);
132 if (TclGetOpenFile(interp
, argv
[1], &filePtr
) != TCL_OK
) {
135 ((Interp
*) interp
)->filePtrArray
[fileno(filePtr
->f
)] = NULL
;
138 * First close the file (in the case of a process pipeline, there may
139 * be two files, one for the pipe at each end of the pipeline).
142 if (filePtr
->f2
!= NULL
) {
143 if (fclose(filePtr
->f2
) == EOF
) {
144 Tcl_AppendResult(interp
, "error closing \"", argv
[1],
145 "\": ", Tcl_UnixError(interp
), "\n", (char *) NULL
);
149 if (fclose(filePtr
->f
) == EOF
) {
150 Tcl_AppendResult(interp
, "error closing \"", argv
[1],
151 "\": ", Tcl_UnixError(interp
), "\n", (char *) NULL
);
156 * If the file was a connection to a pipeline, clean up everything
157 * associated with the child processes.
160 if (filePtr
->numPids
> 0) {
161 if (CleanupChildren(interp
, filePtr
->numPids
, filePtr
->pidPtr
,
162 filePtr
->errorId
) != TCL_OK
) {
167 ckfree((char *) filePtr
);
172 *----------------------------------------------------------------------
176 * This procedure is invoked to process the "eof" Tcl command.
177 * See the user documentation for details on what it does.
180 * A standard Tcl result.
183 * See the user documentation.
185 *----------------------------------------------------------------------
190 Tcl_EofCmd(notUsed
, interp
, argc
, argv
)
191 ClientData notUsed
; /* Not used. */
192 Tcl_Interp
*interp
; /* Current interpreter. */
193 int argc
; /* Number of arguments. */
194 char **argv
; /* Argument strings. */
199 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
200 " fileId\"", (char *) NULL
);
203 if (TclGetOpenFile(interp
, argv
[1], &filePtr
) != TCL_OK
) {
206 if (feof(filePtr
->f
)) {
207 interp
->result
= "1";
209 interp
->result
= "0";
215 *----------------------------------------------------------------------
219 * This procedure is invoked to process the "exec" Tcl command.
220 * See the user documentation for details on what it does.
223 * A standard Tcl result.
226 * See the user documentation.
228 *----------------------------------------------------------------------
233 Tcl_ExecCmd(dummy
, interp
, argc
, argv
)
234 ClientData dummy
; /* Not used. */
235 Tcl_Interp
*interp
; /* Current interpreter. */
236 int argc
; /* Number of arguments. */
237 char **argv
; /* Argument strings. */
239 int outputId
; /* File id for output pipe. -1
240 * means command overrode. */
241 int errorId
; /* File id for temporary file
242 * containing error output. */
247 * See if the command is to be run in background; if so, create
248 * the command, detach it, and return.
251 if ((argv
[argc
-1][0] == '&') && (argv
[argc
-1][1] == 0)) {
254 numPids
= Tcl_CreatePipeline(interp
, argc
-1, argv
+1, &pidPtr
,
255 (int *) NULL
, (int *) NULL
, (int *) NULL
);
259 Tcl_DetachPids(numPids
, pidPtr
);
260 ckfree((char *) pidPtr
);
265 * Create the command's pipeline.
268 numPids
= Tcl_CreatePipeline(interp
, argc
-1, argv
+1, &pidPtr
,
269 (int *) NULL
, &outputId
, &errorId
);
275 * Read the child's output (if any) and put it into the result.
279 if (outputId
!= -1) {
281 # define BUFFER_SIZE 1000
282 char buffer
[BUFFER_SIZE
+1];
285 count
= read(outputId
, buffer
, BUFFER_SIZE
);
291 Tcl_ResetResult(interp
);
292 Tcl_AppendResult(interp
,
293 "error reading from output pipe: ",
294 Tcl_UnixError(interp
), (char *) NULL
);
299 Tcl_AppendResult(interp
, buffer
, (char *) NULL
);
304 if (CleanupChildren(interp
, numPids
, pidPtr
, errorId
) != TCL_OK
) {
311 *----------------------------------------------------------------------
315 * This procedure is invoked to process the "exit" Tcl command.
316 * See the user documentation for details on what it does.
319 * A standard Tcl result.
322 * See the user documentation.
324 *----------------------------------------------------------------------
329 Tcl_ExitCmd(dummy
, interp
, argc
, argv
)
330 ClientData dummy
; /* Not used. */
331 Tcl_Interp
*interp
; /* Current interpreter. */
332 int argc
; /* Number of arguments. */
333 char **argv
; /* Argument strings. */
337 if ((argc
!= 1) && (argc
!= 2)) {
338 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
339 " ?returnCode?\"", (char *) NULL
);
345 if (Tcl_GetInt(interp
, argv
[1], &value
) != TCL_OK
) {
350 return TCL_OK
; /* Better not ever reach this! */
355 *----------------------------------------------------------------------
359 * This procedure is invoked to process the "file" Tcl command.
360 * See the user documentation for details on what it does.
363 * A standard Tcl result.
366 * See the user documentation.
368 *----------------------------------------------------------------------
373 Tcl_FileCmd(dummy
, interp
, argc
, argv
)
374 ClientData dummy
; /* Not used. */
375 Tcl_Interp
*interp
; /* Current interpreter. */
376 int argc
; /* Number of arguments. */
377 char **argv
; /* Argument strings. */
381 int mode
= 0; /* Initialized only to prevent
382 * compiler warning message. */
387 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
388 " option name ?arg ...?\"", (char *) NULL
);
392 length
= strlen(argv
[1]);
395 * First handle operations on the file name.
398 fileName
= Tcl_TildeSubst(interp
, argv
[2]);
399 if (fileName
== NULL
) {
402 if ((c
== 'd') && (strncmp(argv
[1], "dirname", length
) == 0)) {
406 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
407 " ", argv
[1], " name\"", (char *) NULL
);
411 p
= strrchr(fileName
, '\\');
413 p
= strrchr(fileName
, '/');
416 interp
->result
= ".";
417 } else if (p
== fileName
) {
419 interp
->result
= "\\";
421 interp
->result
= "/";
425 Tcl_SetResult(interp
, fileName
, TCL_VOLATILE
);
429 } else if ((c
== 'r') && (strncmp(argv
[1], "rootname", length
) == 0)
434 argv
[1] = "rootname";
437 p
= strrchr(fileName
, '.');
439 lastSlash
= strrchr(fileName
, '\\');
441 lastSlash
= strrchr(fileName
, '/');
443 if ((p
== NULL
) || ((lastSlash
!= NULL
) && (lastSlash
> p
))) {
444 Tcl_SetResult(interp
, fileName
, TCL_VOLATILE
);
447 Tcl_SetResult(interp
, fileName
, TCL_VOLATILE
);
451 } else if ((c
== 'e') && (strncmp(argv
[1], "extension", length
) == 0)
456 argv
[1] = "extension";
459 p
= strrchr(fileName
, '.');
461 lastSlash
= strrchr(fileName
, '\\');
463 lastSlash
= strrchr(fileName
, '/');
465 if ((p
!= NULL
) && ((lastSlash
== NULL
) || (lastSlash
< p
))) {
466 Tcl_SetResult(interp
, p
, TCL_VOLATILE
);
469 } else if ((c
== 't') && (strncmp(argv
[1], "tail", length
) == 0)
476 p
= strrchr(fileName
, '\\');
478 p
= strrchr(fileName
, '/');
481 Tcl_SetResult(interp
, p
+1, TCL_VOLATILE
);
483 Tcl_SetResult(interp
, fileName
, TCL_VOLATILE
);
489 * Next, handle operations that can be satisfied with the "access"
493 if (fileName
== NULL
) {
496 if ((c
== 'r') && (strncmp(argv
[1], "readable", length
) == 0)
499 argv
[1] = "readable";
504 if (access(fileName
, mode
) == -1) {
505 interp
->result
= "0";
507 interp
->result
= "1";
510 } else if ((c
== 'w') && (strncmp(argv
[1], "writable", length
) == 0)) {
512 argv
[1] = "writable";
517 } else if ((c
== 'e') && (strncmp(argv
[1], "executable", length
) == 0)
520 argv
[1] = "executable";
525 } else if ((c
== 'e') && (strncmp(argv
[1], "exists", length
) == 0)
536 * Lastly, check stuff that requires the file to be stat-ed.
539 if ((c
== 'a') && (strncmp(argv
[1], "atime", length
) == 0)) {
544 if (stat(fileName
, &statBuf
) == -1) {
547 sprintf(interp
->result
, "%ld", statBuf
.st_atime
);
549 } else if ((c
== 'i') && (strncmp(argv
[1], "isdirectory", length
) == 0)
552 argv
[1] = "isdirectory";
556 } else if ((c
== 'i') && (strncmp(argv
[1], "isfile", length
) == 0)
563 } else if ((c
== 'l') && (strncmp(argv
[1], "lstat", length
) == 0)) {
565 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
566 " lstat name varName\"", (char *) NULL
);
570 if (lstat(fileName
, &statBuf
) == -1) {
571 Tcl_AppendResult(interp
, "couldn't lstat \"", argv
[2],
572 "\": ", Tcl_UnixError(interp
), (char *) NULL
);
575 return StoreStatData(interp
, argv
[3], &statBuf
);
576 } else if ((c
== 'm') && (strncmp(argv
[1], "mtime", length
) == 0)) {
581 if (stat(fileName
, &statBuf
) == -1) {
584 sprintf(interp
->result
, "%ld", statBuf
.st_mtime
);
586 } else if ((c
== 'o') && (strncmp(argv
[1], "owned", length
) == 0)) {
594 * This option is only included if symbolic links exist on this system
595 * (in which case S_IFLNK should be defined).
597 } else if ((c
== 'r') && (strncmp(argv
[1], "readlink", length
) == 0)
599 char linkValue
[MAXPATHLEN
+1];
603 argv
[1] = "readlink";
606 linkLength
= readlink(fileName
, linkValue
, sizeof(linkValue
) - 1);
607 if (linkLength
== -1) {
608 Tcl_AppendResult(interp
, "couldn't readlink \"", argv
[2],
609 "\": ", Tcl_UnixError(interp
), (char *) NULL
);
612 linkValue
[linkLength
] = 0;
613 Tcl_SetResult(interp
, linkValue
, TCL_VOLATILE
);
616 } else if ((c
== 's') && (strncmp(argv
[1], "size", length
) == 0)
622 if (stat(fileName
, &statBuf
) == -1) {
625 sprintf(interp
->result
, "%ld", statBuf
.st_size
);
627 } else if ((c
== 's') && (strncmp(argv
[1], "stat", length
) == 0)
630 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
631 " stat name varName\"", (char *) NULL
);
635 if (stat(fileName
, &statBuf
) == -1) {
637 Tcl_AppendResult(interp
, "couldn't stat \"", argv
[2],
638 "\": ", Tcl_UnixError(interp
), (char *) NULL
);
641 return StoreStatData(interp
, argv
[3], &statBuf
);
642 } else if ((c
== 't') && (strncmp(argv
[1], "type", length
) == 0)
648 if (lstat(fileName
, &statBuf
) == -1) {
651 interp
->result
= GetFileType((int) statBuf
.st_mode
);
654 Tcl_AppendResult(interp
, "bad option \"", argv
[1],
655 "\": should be atime, dirname, executable, exists, ",
656 "extension, isdirectory, isfile, lstat, mtime, owned, ",
661 "root, size, stat, tail, type, ",
666 if (stat(fileName
, &statBuf
) == -1) {
667 interp
->result
= "0";
672 mode
= (geteuid() == statBuf
.st_uid
);
675 mode
= S_ISREG(statBuf
.st_mode
);
678 mode
= S_ISDIR(statBuf
.st_mode
);
682 interp
->result
= "1";
684 interp
->result
= "0";
690 *----------------------------------------------------------------------
694 * This is a utility procedure that breaks out the fields of a
695 * "stat" structure and stores them in textual form into the
696 * elements of an associative array.
699 * Returns a standard Tcl return value. If an error occurs then
700 * a message is left in interp->result.
703 * Elements of the associative array given by "varName" are modified.
705 *----------------------------------------------------------------------
709 StoreStatData(interp
, varName
, statPtr
)
710 Tcl_Interp
*interp
; /* Interpreter for error reports. */
711 char *varName
; /* Name of associative array variable
712 * in which to store stat results. */
713 struct stat
*statPtr
; /* Pointer to buffer containing
714 * stat data to store in varName. */
718 sprintf(string
, "%d", statPtr
->st_dev
);
719 if (Tcl_SetVar2(interp
, varName
, "dev", string
, TCL_LEAVE_ERR_MSG
)
723 sprintf(string
, "%d", statPtr
->st_ino
);
724 if (Tcl_SetVar2(interp
, varName
, "ino", string
, TCL_LEAVE_ERR_MSG
)
728 sprintf(string
, "%d", statPtr
->st_mode
);
729 if (Tcl_SetVar2(interp
, varName
, "mode", string
, TCL_LEAVE_ERR_MSG
)
733 sprintf(string
, "%d", statPtr
->st_nlink
);
734 if (Tcl_SetVar2(interp
, varName
, "nlink", string
, TCL_LEAVE_ERR_MSG
)
738 sprintf(string
, "%d", statPtr
->st_uid
);
739 if (Tcl_SetVar2(interp
, varName
, "uid", string
, TCL_LEAVE_ERR_MSG
)
743 sprintf(string
, "%d", statPtr
->st_gid
);
744 if (Tcl_SetVar2(interp
, varName
, "gid", string
, TCL_LEAVE_ERR_MSG
)
748 sprintf(string
, "%ld", statPtr
->st_size
);
749 if (Tcl_SetVar2(interp
, varName
, "size", string
, TCL_LEAVE_ERR_MSG
)
753 sprintf(string
, "%ld", statPtr
->st_atime
);
754 if (Tcl_SetVar2(interp
, varName
, "atime", string
, TCL_LEAVE_ERR_MSG
)
758 sprintf(string
, "%ld", statPtr
->st_mtime
);
759 if (Tcl_SetVar2(interp
, varName
, "mtime", string
, TCL_LEAVE_ERR_MSG
)
763 sprintf(string
, "%ld", statPtr
->st_ctime
);
764 if (Tcl_SetVar2(interp
, varName
, "ctime", string
, TCL_LEAVE_ERR_MSG
)
768 if (Tcl_SetVar2(interp
, varName
, "type",
769 GetFileType((int) statPtr
->st_mode
), TCL_LEAVE_ERR_MSG
) == NULL
) {
776 *----------------------------------------------------------------------
780 * Given a mode word, returns a string identifying the type of a
784 * A static text string giving the file type from mode.
789 *----------------------------------------------------------------------
798 } else if (S_ISDIR(mode
)) {
800 } else if (S_ISCHR(mode
)) {
801 return "characterSpecial";
802 } else if (S_ISBLK(mode
)) {
803 return "blockSpecial";
804 } else if (S_ISFIFO(mode
)) {
806 } else if (S_ISLNK(mode
)) {
808 } else if (S_ISSOCK(mode
)) {
815 *----------------------------------------------------------------------
819 * This procedure is invoked to process the "flush" Tcl command.
820 * See the user documentation for details on what it does.
823 * A standard Tcl result.
826 * See the user documentation.
828 *----------------------------------------------------------------------
833 Tcl_FlushCmd(notUsed
, interp
, argc
, argv
)
834 ClientData notUsed
; /* Not used. */
835 Tcl_Interp
*interp
; /* Current interpreter. */
836 int argc
; /* Number of arguments. */
837 char **argv
; /* Argument strings. */
843 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
844 " fileId\"", (char *) NULL
);
847 if (TclGetOpenFile(interp
, argv
[1], &filePtr
) != TCL_OK
) {
850 if (!filePtr
->writable
) {
851 Tcl_AppendResult(interp
, "\"", argv
[1],
852 "\" wasn't opened for writing", (char *) NULL
);
859 if (fflush(f
) == EOF
) {
860 Tcl_AppendResult(interp
, "error flushing \"", argv
[1],
861 "\": ", Tcl_UnixError(interp
), (char *) NULL
);
869 *----------------------------------------------------------------------
873 * This procedure is invoked to process the "gets" Tcl command.
874 * See the user documentation for details on what it does.
877 * A standard Tcl result.
880 * See the user documentation.
882 *----------------------------------------------------------------------
887 Tcl_GetsCmd(notUsed
, interp
, argc
, argv
)
888 ClientData notUsed
; /* Not used. */
889 Tcl_Interp
*interp
; /* Current interpreter. */
890 int argc
; /* Number of arguments. */
891 char **argv
; /* Argument strings. */
893 # define BUF_SIZE 200
894 char buffer
[BUF_SIZE
+1];
895 int totalCount
, done
, flags
;
899 if ((argc
!= 2) && (argc
!= 3)) {
900 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
901 " fileId ?varName?\"", (char *) NULL
);
904 if (TclGetOpenFile(interp
, argv
[1], &filePtr
) != TCL_OK
) {
907 if (!filePtr
->readable
) {
908 Tcl_AppendResult(interp
, "\"", argv
[1],
909 "\" wasn't opened for reading", (char *) NULL
);
914 * We can't predict how large a line will be, so read it in
915 * pieces, appending to the current result or to a variable.
923 register int c
, count
;
926 for (p
= buffer
, count
= 0; count
< BUF_SIZE
-1; count
++, p
++) {
929 if (ferror(filePtr
->f
)) {
930 Tcl_ResetResult(interp
);
931 Tcl_AppendResult(interp
, "error reading \"", argv
[1],
932 "\": ", Tcl_UnixError(interp
), (char *) NULL
);
933 clearerr(filePtr
->f
);
935 } else if (feof(filePtr
->f
)) {
936 if ((totalCount
== 0) && (count
== 0)) {
951 Tcl_AppendResult(interp
, buffer
, (char *) NULL
);
953 if (Tcl_SetVar(interp
, argv
[2], buffer
, flags
|TCL_LEAVE_ERR_MSG
)
957 flags
= TCL_APPEND_VALUE
;
963 sprintf(interp
->result
, "%d", totalCount
);
969 *----------------------------------------------------------------------
973 * This procedure is invoked to process the "open" Tcl command.
974 * See the user documentation for details on what it does.
977 * A standard Tcl result.
980 * See the user documentation.
982 *----------------------------------------------------------------------
987 Tcl_OpenCmd(notUsed
, interp
, argc
, argv
)
988 ClientData notUsed
; /* Not used. */
989 Tcl_Interp
*interp
; /* Current interpreter. */
990 int argc
; /* Number of arguments. */
991 char **argv
; /* Argument strings. */
993 Interp
*iPtr
= (Interp
*) interp
;
996 register OpenFile
*filePtr
;
1000 } else if (argc
== 3) {
1003 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
1004 " filename ?access?\"", (char *) NULL
);
1008 filePtr
= (OpenFile
*) ckalloc(sizeof(OpenFile
));
1011 filePtr
->readable
= 0;
1012 filePtr
->writable
= 0;
1013 filePtr
->numPids
= 0;
1014 filePtr
->pidPtr
= NULL
;
1015 filePtr
->errorId
= -1;
1018 * Verify the requested form of access.
1022 if (argv
[1][0] == '|') {
1025 switch (access
[0]) {
1027 filePtr
->readable
= 1;
1030 filePtr
->writable
= 1;
1033 filePtr
->writable
= 1;
1037 Tcl_AppendResult(interp
, "illegal access mode \"", access
,
1038 "\"", (char *) NULL
);
1041 if (access
[1] == '+') {
1042 filePtr
->readable
= filePtr
->writable
= 1;
1043 if (access
[2] != 0) {
1046 } else if (access
[1] != 0) {
1051 * Open the file or create a process pipeline.
1055 char *fileName
= argv
[1];
1057 if (fileName
[0] == '~') {
1058 fileName
= Tcl_TildeSubst(interp
, fileName
);
1059 if (fileName
== NULL
) {
1063 filePtr
->f
= fopen(fileName
, access
);
1064 if (filePtr
->f
== NULL
) {
1065 Tcl_AppendResult(interp
, "couldn't open \"", argv
[1],
1066 "\": ", Tcl_UnixError(interp
), (char *) NULL
);
1070 int *inPipePtr
, *outPipePtr
;
1071 int cmdArgc
, inPipe
, outPipe
;
1074 if (Tcl_SplitList(interp
, argv
[1]+1, &cmdArgc
, &cmdArgv
) != TCL_OK
) {
1077 inPipePtr
= (filePtr
->writable
) ? &inPipe
: NULL
;
1078 outPipePtr
= (filePtr
->readable
) ? &outPipe
: NULL
;
1079 inPipe
= outPipe
= -1;
1080 filePtr
->numPids
= Tcl_CreatePipeline(interp
, cmdArgc
, cmdArgv
,
1081 &filePtr
->pidPtr
, inPipePtr
, outPipePtr
, &filePtr
->errorId
);
1082 ckfree((char *) cmdArgv
);
1083 if (filePtr
->numPids
< 0) {
1086 if (filePtr
->readable
) {
1087 if (outPipe
== -1) {
1091 Tcl_AppendResult(interp
, "can't read output from command:",
1092 " standard output was redirected", (char *) NULL
);
1095 filePtr
->f
= fdopen(outPipe
, "r");
1097 if (filePtr
->writable
) {
1099 Tcl_AppendResult(interp
, "can't write input to command:",
1100 " standard input was redirected", (char *) NULL
);
1103 if (filePtr
->f
!= NULL
) {
1104 filePtr
->f2
= fdopen(inPipe
, "w");
1106 filePtr
->f
= fdopen(inPipe
, "w");
1112 * Enter this new OpenFile structure in the table for the
1113 * interpreter. May have to expand the table to do this.
1116 fd
= fileno(filePtr
->f
);
1117 TclMakeFileTable(iPtr
, fd
);
1118 if (iPtr
->filePtrArray
[fd
] != NULL
) {
1119 panic("Tcl_OpenCmd found file already open");
1121 iPtr
->filePtrArray
[fd
] = filePtr
;
1122 sprintf(interp
->result
, "file%d", fd
);
1126 if (filePtr
->f
!= NULL
) {
1129 if (filePtr
->f2
!= NULL
) {
1130 fclose(filePtr
->f2
);
1132 if (filePtr
->numPids
> 0) {
1133 Tcl_DetachPids(filePtr
->numPids
, filePtr
->pidPtr
);
1134 ckfree((char *) filePtr
->pidPtr
);
1136 if (filePtr
->errorId
!= -1) {
1137 close(filePtr
->errorId
);
1139 ckfree((char *) filePtr
);
1144 *----------------------------------------------------------------------
1148 * This procedure is invoked to process the "pwd" Tcl command.
1149 * See the user documentation for details on what it does.
1152 * A standard Tcl result.
1155 * See the user documentation.
1157 *----------------------------------------------------------------------
1162 Tcl_PwdCmd(dummy
, interp
, argc
, argv
)
1163 ClientData dummy
; /* Not used. */
1164 Tcl_Interp
*interp
; /* Current interpreter. */
1165 int argc
; /* Number of arguments. */
1166 char **argv
; /* Argument strings. */
1168 char buffer
[MAXPATHLEN
+1];
1171 Tcl_AppendResult(interp
, "wrong # args: should be \"",
1172 argv
[0], "\"", (char *) NULL
);
1175 if (currentDir
== NULL
) {
1177 if (getwd(buffer
) == NULL
) {
1178 Tcl_AppendResult(interp
, "error getting working directory name: ",
1179 buffer
, (char *) NULL
);
1183 if (getcwd(buffer
, MAXPATHLEN
) == 0) {
1184 if (errno
== ERANGE
) {
1185 interp
->result
= "working directory name is too long";
1187 Tcl_AppendResult(interp
,
1188 "error getting working directory name: ",
1189 Tcl_UnixError(interp
), (char *) NULL
);
1194 currentDir
= (char *) ckalloc((unsigned) (strlen(buffer
) + 1));
1195 strcpy(currentDir
, buffer
);
1197 interp
->result
= currentDir
;
1202 *----------------------------------------------------------------------
1206 * This procedure is invoked to process the "puts" Tcl command.
1207 * See the user documentation for details on what it does.
1210 * A standard Tcl result.
1213 * See the user documentation.
1215 *----------------------------------------------------------------------
1220 Tcl_PutsCmd(dummy
, interp
, argc
, argv
)
1221 ClientData dummy
; /* Not used. */
1222 Tcl_Interp
*interp
; /* Current interpreter. */
1223 int argc
; /* Number of arguments. */
1224 char **argv
; /* Argument strings. */
1230 if (strncmp(argv
[3], "nonewline", strlen(argv
[3])) != 0) {
1231 Tcl_AppendResult(interp
, "bad argument \"", argv
[3],
1232 "\": should be \"nonewline\"", (char *) NULL
);
1235 } else if (argc
!= 3) {
1236 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
1237 " fileId string ?nonewline?\"", (char *) NULL
);
1240 if (TclGetOpenFile(interp
, argv
[1], &filePtr
) != TCL_OK
) {
1243 if (!filePtr
->writable
) {
1244 Tcl_AppendResult(interp
, "\"", argv
[1],
1245 "\" wasn't opened for writing", (char *) NULL
);
1258 Tcl_AppendResult(interp
, "error writing \"", argv
[1],
1259 "\": ", Tcl_UnixError(interp
), (char *) NULL
);
1267 *----------------------------------------------------------------------
1271 * This procedure is invoked to process the "read" Tcl command.
1272 * See the user documentation for details on what it does.
1275 * A standard Tcl result.
1278 * See the user documentation.
1280 *----------------------------------------------------------------------
1285 Tcl_ReadCmd(dummy
, interp
, argc
, argv
)
1286 ClientData dummy
; /* Not used. */
1287 Tcl_Interp
*interp
; /* Current interpreter. */
1288 int argc
; /* Number of arguments. */
1289 char **argv
; /* Argument strings. */
1292 int bytesLeft
, bytesRead
, count
;
1293 #define READ_BUF_SIZE 4096
1294 char buffer
[READ_BUF_SIZE
+1];
1297 if ((argc
!= 2) && (argc
!= 3)) {
1298 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
1299 " fileId ?numBytes|nonewline?\"", (char *) NULL
);
1302 if (TclGetOpenFile(interp
, argv
[1], &filePtr
) != TCL_OK
) {
1305 if (!filePtr
->readable
) {
1306 Tcl_AppendResult(interp
, "\"", argv
[1],
1307 "\" wasn't opened for reading", (char *) NULL
);
1312 * Compute how many bytes to read, and see whether the final
1313 * newline should be dropped.
1317 if ((argc
> 2) && isdigit(argv
[2][0])) {
1318 if (Tcl_GetInt(interp
, argv
[2], &bytesLeft
) != TCL_OK
) {
1324 if (strncmp(argv
[2], "nonewline", strlen(argv
[2])) == 0) {
1327 Tcl_AppendResult(interp
, "bad argument \"", argv
[2],
1328 "\": should be \"nonewline\"", (char *) NULL
);
1335 * Read the file in one or more chunks.
1339 while (bytesLeft
> 0) {
1340 count
= READ_BUF_SIZE
;
1341 if (bytesLeft
< READ_BUF_SIZE
) {
1344 count
= fread(buffer
, 1, count
, filePtr
->f
);
1345 if (ferror(filePtr
->f
)) {
1346 Tcl_ResetResult(interp
);
1347 Tcl_AppendResult(interp
, "error reading \"", argv
[1],
1348 "\": ", Tcl_UnixError(interp
), (char *) NULL
);
1349 clearerr(filePtr
->f
);
1356 Tcl_AppendResult(interp
, buffer
, (char *) NULL
);
1360 if ((newline
== 0) && (interp
->result
[bytesRead
-1] == '\n')) {
1361 interp
->result
[bytesRead
-1] = 0;
1367 *----------------------------------------------------------------------
1371 * This procedure is invoked to process the "seek" Tcl command.
1372 * See the user documentation for details on what it does.
1375 * A standard Tcl result.
1378 * See the user documentation.
1380 *----------------------------------------------------------------------
1385 Tcl_SeekCmd(notUsed
, interp
, argc
, argv
)
1386 ClientData notUsed
; /* Not used. */
1387 Tcl_Interp
*interp
; /* Current interpreter. */
1388 int argc
; /* Number of arguments. */
1389 char **argv
; /* Argument strings. */
1394 if ((argc
!= 3) && (argc
!= 4)) {
1395 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
1396 " fileId offset ?origin?\"", (char *) NULL
);
1399 if (TclGetOpenFile(interp
, argv
[1], &filePtr
) != TCL_OK
) {
1402 if (Tcl_GetInt(interp
, argv
[2], &offset
) != TCL_OK
) {
1410 length
= strlen(argv
[3]);
1412 if ((c
== 's') && (strncmp(argv
[3], "start", length
) == 0)) {
1414 } else if ((c
== 'c') && (strncmp(argv
[3], "current", length
) == 0)) {
1416 } else if ((c
== 'e') && (strncmp(argv
[3], "end", length
) == 0)) {
1419 Tcl_AppendResult(interp
, "bad origin \"", argv
[3],
1420 "\": should be start, current, or end", (char *) NULL
);
1424 if (fseek(filePtr
->f
, offset
, mode
) == -1) {
1425 Tcl_AppendResult(interp
, "error during seek: ",
1426 Tcl_UnixError(interp
), (char *) NULL
);
1427 clearerr(filePtr
->f
);
1435 *----------------------------------------------------------------------
1439 * This procedure is invoked to process the "source" Tcl command.
1440 * See the user documentation for details on what it does.
1443 * A standard Tcl result.
1446 * See the user documentation.
1448 *----------------------------------------------------------------------
1453 Tcl_SourceCmd(dummy
, interp
, argc
, argv
)
1454 ClientData dummy
; /* Not used. */
1455 Tcl_Interp
*interp
; /* Current interpreter. */
1456 int argc
; /* Number of arguments. */
1457 char **argv
; /* Argument strings. */
1460 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
1461 " fileName\"", (char *) NULL
);
1464 return Tcl_EvalFile(interp
, argv
[1]);
1468 *----------------------------------------------------------------------
1472 * This procedure is invoked to process the "tell" Tcl command.
1473 * See the user documentation for details on what it does.
1476 * A standard Tcl result.
1479 * See the user documentation.
1481 *----------------------------------------------------------------------
1486 Tcl_TellCmd(notUsed
, interp
, argc
, argv
)
1487 ClientData notUsed
; /* Not used. */
1488 Tcl_Interp
*interp
; /* Current interpreter. */
1489 int argc
; /* Number of arguments. */
1490 char **argv
; /* Argument strings. */
1495 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
1496 " fileId\"", (char *) NULL
);
1499 if (TclGetOpenFile(interp
, argv
[1], &filePtr
) != TCL_OK
) {
1502 sprintf(interp
->result
, "%d", ftell(filePtr
->f
));
1507 *----------------------------------------------------------------------
1511 * This procedure is invoked to process the "time" Tcl command.
1512 * See the user documentation for details on what it does.
1515 * A standard Tcl result.
1518 * See the user documentation.
1520 *----------------------------------------------------------------------
1525 Tcl_TimeCmd(dummy
, interp
, argc
, argv
)
1526 ClientData dummy
; /* Not used. */
1527 Tcl_Interp
*interp
; /* Current interpreter. */
1528 int argc
; /* Number of arguments. */
1529 char **argv
; /* Argument strings. */
1531 int count
, i
, result
;
1534 struct timeval start
, stop
;
1544 } else if (argc
== 3) {
1545 if (Tcl_GetInt(interp
, argv
[2], &count
) != TCL_OK
) {
1549 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
1550 " command ?count?\"", (char *) NULL
);
1554 gettimeofday(&start
, &tz
);
1556 start
= times(&dummy2
);
1558 for (i
= count
; i
> 0; i
--) {
1559 result
= Tcl_Eval(interp
, argv
[1], 0, (char **) NULL
);
1560 if (result
!= TCL_OK
) {
1561 if (result
== TCL_ERROR
) {
1563 sprintf(msg
, "\n (\"time\" body line %d)",
1565 Tcl_AddErrorInfo(interp
, msg
);
1571 gettimeofday(&stop
, &tz
);
1572 micros
= (stop
.tv_sec
- start
.tv_sec
)*1000000
1573 + (stop
.tv_usec
- start
.tv_usec
);
1576 stop
= times(&dummy2
);
1577 timePer
= (((double) (stop
- start
))*1000000.0)/CLK_TCK
;
1579 Tcl_ResetResult(interp
);
1580 sprintf(interp
->result
, "%.0f microseconds per iteration", timePer
/count
);
1585 *----------------------------------------------------------------------
1587 * CleanupChildren --
1589 * This is a utility procedure used to wait for child processes
1590 * to exit, record information about abnormal exits, and then
1591 * collect any stderr output generated by them.
1594 * The return value is a standard Tcl result. If anything at
1595 * weird happened with the child processes, TCL_ERROR is returned
1596 * and a message is left in interp->result.
1599 * If the last character of interp->result is a newline, then it
1600 * is removed. File errorId gets closed, and pidPtr is freed
1601 * back to the storage allocator.
1603 *----------------------------------------------------------------------
1607 CleanupChildren(interp
, numPids
, pidPtr
, errorId
)
1608 Tcl_Interp
*interp
; /* Used for error messages. */
1609 int numPids
; /* Number of entries in pidPtr array. */
1610 int *pidPtr
; /* Array of process ids of children. */
1611 int errorId
; /* File descriptor index for file containing
1612 * stderr output from pipeline. -1 means
1613 * there isn't any stderr output. */
1615 int result
= TCL_OK
;
1617 WAIT_STATUS_TYPE waitStatus
;
1619 for (i
= 0; i
< numPids
; i
++) {
1620 pid
= Tcl_WaitPids(1, &pidPtr
[i
], (int *) &waitStatus
);
1622 Tcl_AppendResult(interp
, "error waiting for process to exit: ",
1623 Tcl_UnixError(interp
), (char *) NULL
);
1628 * Create error messages for unusual process exits. An
1629 * extra newline gets appended to each error message, but
1630 * it gets removed below (in the same fashion that an
1631 * extra newline in the command's output is removed).
1634 if (!WIFEXITED(waitStatus
) || (WEXITSTATUS(waitStatus
) != 0)) {
1635 char msg1
[20], msg2
[20];
1638 sprintf(msg1
, "%d", pid
);
1639 if (WIFEXITED(waitStatus
)) {
1640 sprintf(msg2
, "%d", WEXITSTATUS(waitStatus
));
1641 Tcl_SetErrorCode(interp
, "CHILDSTATUS", msg1
, msg2
,
1643 } else if (WIFSIGNALED(waitStatus
)) {
1646 p
= Tcl_SignalMsg((int) (WTERMSIG(waitStatus
)));
1647 Tcl_SetErrorCode(interp
, "CHILDKILLED", msg1
,
1648 Tcl_SignalId((int) (WTERMSIG(waitStatus
))), p
,
1650 Tcl_AppendResult(interp
, "child killed: ", p
, "\n",
1652 } else if (WIFSTOPPED(waitStatus
)) {
1655 p
= Tcl_SignalMsg((int) (WSTOPSIG(waitStatus
)));
1656 Tcl_SetErrorCode(interp
, "CHILDSUSP", msg1
,
1657 Tcl_SignalId((int) (WSTOPSIG(waitStatus
))), p
, (char *) NULL
);
1658 Tcl_AppendResult(interp
, "child suspended: ", p
, "\n",
1661 Tcl_AppendResult(interp
,
1662 "child wait status didn't make sense\n",
1667 ckfree((char *) pidPtr
);
1670 * Read the standard error file. If there's anything there,
1671 * then return an error and add the file's contents to the result
1677 # define BUFFER_SIZE 1000
1678 char buffer
[BUFFER_SIZE
+1];
1681 count
= read(errorId
, buffer
, BUFFER_SIZE
);
1687 Tcl_AppendResult(interp
,
1688 "error reading stderr output file: ",
1689 Tcl_UnixError(interp
), (char *) NULL
);
1693 Tcl_AppendResult(interp
, buffer
, (char *) NULL
);
1699 * If the last character of interp->result is a newline, then remove
1700 * the newline character (the newline would just confuse things).
1703 length
= strlen(interp
->result
);
1704 if ((length
> 0) && (interp
->result
[length
-1] == '\n')) {
1705 interp
->result
[length
-1] = '\0';