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 *----------------------------------------------------------------------
65 ClientData dummy
, /* Not used. */
66 Tcl_Interp
*interp
, /* Current interpreter. */
67 int argc
, /* Number of arguments. */
68 char **argv
/* Argument strings. */
74 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
75 " dirName\"", (char *) NULL
);
84 dirName
= Tcl_TildeSubst(interp
, dirName
);
85 if (dirName
== NULL
) {
88 if (currentDir
!= NULL
) {
92 if (chdir(dirName
) != 0) {
93 Tcl_AppendResult(interp
, "couldn't change working directory to \"",
94 dirName
, "\": ", Tcl_UnixError(interp
), (char *) NULL
);
101 *----------------------------------------------------------------------
105 * This procedure is invoked to process the "close" Tcl command.
106 * See the user documentation for details on what it does.
109 * A standard Tcl result.
112 * See the user documentation.
114 *----------------------------------------------------------------------
120 ClientData dummy
, /* Not used. */
121 Tcl_Interp
*interp
, /* Current interpreter. */
122 int argc
, /* Number of arguments. */
123 char **argv
/* Argument strings. */
130 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
131 " fileId\"", (char *) NULL
);
134 if (TclGetOpenFile(interp
, argv
[1], &filePtr
) != TCL_OK
) {
137 ((Interp
*) interp
)->filePtrArray
[fileno(filePtr
->f
)] = NULL
;
140 * First close the file (in the case of a process pipeline, there may
141 * be two files, one for the pipe at each end of the pipeline).
144 if (filePtr
->f2
!= NULL
) {
145 if (fclose(filePtr
->f2
) == EOF
) {
146 Tcl_AppendResult(interp
, "error closing \"", argv
[1],
147 "\": ", Tcl_UnixError(interp
), "\n", (char *) NULL
);
151 if (fclose(filePtr
->f
) == EOF
) {
152 Tcl_AppendResult(interp
, "error closing \"", argv
[1],
153 "\": ", Tcl_UnixError(interp
), "\n", (char *) NULL
);
158 * If the file was a connection to a pipeline, clean up everything
159 * associated with the child processes.
162 if (filePtr
->numPids
> 0) {
163 if (CleanupChildren(interp
, filePtr
->numPids
, filePtr
->pidPtr
,
164 filePtr
->errorId
) != TCL_OK
) {
169 ckfree((char *) filePtr
);
174 *----------------------------------------------------------------------
178 * This procedure is invoked to process the "eof" Tcl command.
179 * See the user documentation for details on what it does.
182 * A standard Tcl result.
185 * See the user documentation.
187 *----------------------------------------------------------------------
193 ClientData notUsed
, /* Not used. */
194 Tcl_Interp
*interp
, /* Current interpreter. */
195 int argc
, /* Number of arguments. */
196 char **argv
/* Argument strings. */
202 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
203 " fileId\"", (char *) NULL
);
206 if (TclGetOpenFile(interp
, argv
[1], &filePtr
) != TCL_OK
) {
209 if (feof(filePtr
->f
)) {
210 interp
->result
= "1";
212 interp
->result
= "0";
218 *----------------------------------------------------------------------
222 * This procedure is invoked to process the "exec" Tcl command.
223 * See the user documentation for details on what it does.
226 * A standard Tcl result.
229 * See the user documentation.
231 *----------------------------------------------------------------------
237 ClientData dummy
, /* Not used. */
238 Tcl_Interp
*interp
, /* Current interpreter. */
239 int argc
, /* Number of arguments. */
240 char **argv
/* Argument strings. */
243 int outputId
; /* File id for output pipe. -1
244 * means command overrode. */
245 int errorId
; /* File id for temporary file
246 * containing error output. */
251 * See if the command is to be run in background; if so, create
252 * the command, detach it, and return.
255 if ((argv
[argc
-1][0] == '&') && (argv
[argc
-1][1] == 0)) {
258 numPids
= Tcl_CreatePipeline(interp
, argc
-1, argv
+1, &pidPtr
,
259 (int *) NULL
, (int *) NULL
, (int *) NULL
);
263 Tcl_DetachPids(numPids
, pidPtr
);
264 ckfree((char *) pidPtr
);
269 * Create the command's pipeline.
272 numPids
= Tcl_CreatePipeline(interp
, argc
-1, argv
+1, &pidPtr
,
273 (int *) NULL
, &outputId
, &errorId
);
279 * Read the child's output (if any) and put it into the result.
283 if (outputId
!= -1) {
285 # define BUFFER_SIZE 1000
286 char buffer
[BUFFER_SIZE
+1];
289 count
= read(outputId
, buffer
, BUFFER_SIZE
);
295 Tcl_ResetResult(interp
);
296 Tcl_AppendResult(interp
,
297 "error reading from output pipe: ",
298 Tcl_UnixError(interp
), (char *) NULL
);
303 Tcl_AppendResult(interp
, buffer
, (char *) NULL
);
308 if (CleanupChildren(interp
, numPids
, pidPtr
, errorId
) != TCL_OK
) {
315 *----------------------------------------------------------------------
319 * This procedure is invoked to process the "exit" Tcl command.
320 * See the user documentation for details on what it does.
323 * A standard Tcl result.
326 * See the user documentation.
328 *----------------------------------------------------------------------
334 ClientData dummy
, /* Not used. */
335 Tcl_Interp
*interp
, /* Current interpreter. */
336 int argc
, /* Number of arguments. */
337 char **argv
/* Argument strings. */
342 if ((argc
!= 1) && (argc
!= 2)) {
343 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
344 " ?returnCode?\"", (char *) NULL
);
350 if (Tcl_GetInt(interp
, argv
[1], &value
) != TCL_OK
) {
355 return TCL_OK
; /* Better not ever reach this! */
360 *----------------------------------------------------------------------
364 * This procedure is invoked to process the "file" Tcl command.
365 * See the user documentation for details on what it does.
368 * A standard Tcl result.
371 * See the user documentation.
373 *----------------------------------------------------------------------
379 ClientData dummy
, /* Not used. */
380 Tcl_Interp
*interp
, /* Current interpreter. */
381 int argc
, /* Number of arguments. */
382 char **argv
/* Argument strings. */
387 int mode
= 0; /* Initialized only to prevent
388 * compiler warning message. */
393 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
394 " option name ?arg ...?\"", (char *) NULL
);
398 length
= strlen(argv
[1]);
401 * First handle operations on the file name.
404 fileName
= Tcl_TildeSubst(interp
, argv
[2]);
405 if (fileName
== NULL
) {
408 if ((c
== 'd') && (strncmp(argv
[1], "dirname", length
) == 0)) {
412 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
413 " ", argv
[1], " name\"", (char *) NULL
);
417 p
= strrchr(fileName
, '\\');
419 p
= strrchr(fileName
, '/');
422 interp
->result
= ".";
423 } else if (p
== fileName
) {
425 interp
->result
= "\\";
427 interp
->result
= "/";
431 Tcl_SetResult(interp
, fileName
, TCL_VOLATILE
);
435 } else if ((c
== 'r') && (strncmp(argv
[1], "rootname", length
) == 0)
440 argv
[1] = "rootname";
443 p
= strrchr(fileName
, '.');
445 lastSlash
= strrchr(fileName
, '\\');
447 lastSlash
= strrchr(fileName
, '/');
449 if ((p
== NULL
) || ((lastSlash
!= NULL
) && (lastSlash
> p
))) {
450 Tcl_SetResult(interp
, fileName
, TCL_VOLATILE
);
453 Tcl_SetResult(interp
, fileName
, TCL_VOLATILE
);
457 } else if ((c
== 'e') && (strncmp(argv
[1], "extension", length
) == 0)
462 argv
[1] = "extension";
465 p
= strrchr(fileName
, '.');
467 lastSlash
= strrchr(fileName
, '\\');
469 lastSlash
= strrchr(fileName
, '/');
471 if ((p
!= NULL
) && ((lastSlash
== NULL
) || (lastSlash
< p
))) {
472 Tcl_SetResult(interp
, p
, TCL_VOLATILE
);
475 } else if ((c
== 't') && (strncmp(argv
[1], "tail", length
) == 0)
482 p
= strrchr(fileName
, '\\');
484 p
= strrchr(fileName
, '/');
487 Tcl_SetResult(interp
, p
+1, TCL_VOLATILE
);
489 Tcl_SetResult(interp
, fileName
, TCL_VOLATILE
);
495 * Next, handle operations that can be satisfied with the "access"
499 if (fileName
== NULL
) {
502 if ((c
== 'r') && (strncmp(argv
[1], "readable", length
) == 0)
505 argv
[1] = "readable";
510 if (access(fileName
, mode
) == -1) {
511 interp
->result
= "0";
513 interp
->result
= "1";
516 } else if ((c
== 'w') && (strncmp(argv
[1], "writable", length
) == 0)) {
518 argv
[1] = "writable";
523 } else if ((c
== 'e') && (strncmp(argv
[1], "executable", length
) == 0)
526 argv
[1] = "executable";
531 } else if ((c
== 'e') && (strncmp(argv
[1], "exists", length
) == 0)
542 * Lastly, check stuff that requires the file to be stat-ed.
545 if ((c
== 'a') && (strncmp(argv
[1], "atime", length
) == 0)) {
550 if (stat(fileName
, &statBuf
) == -1) {
553 sprintf(interp
->result
, "%ld", statBuf
.st_atime
);
555 } else if ((c
== 'i') && (strncmp(argv
[1], "isdirectory", length
) == 0)
558 argv
[1] = "isdirectory";
562 } else if ((c
== 'i') && (strncmp(argv
[1], "isfile", length
) == 0)
569 } else if ((c
== 'l') && (strncmp(argv
[1], "lstat", length
) == 0)) {
571 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
572 " lstat name varName\"", (char *) NULL
);
576 if (lstat(fileName
, &statBuf
) == -1) {
577 Tcl_AppendResult(interp
, "couldn't lstat \"", argv
[2],
578 "\": ", Tcl_UnixError(interp
), (char *) NULL
);
581 return StoreStatData(interp
, argv
[3], &statBuf
);
582 } else if ((c
== 'm') && (strncmp(argv
[1], "mtime", length
) == 0)) {
587 if (stat(fileName
, &statBuf
) == -1) {
590 sprintf(interp
->result
, "%ld", statBuf
.st_mtime
);
592 } else if ((c
== 'o') && (strncmp(argv
[1], "owned", length
) == 0)) {
600 * This option is only included if symbolic links exist on this system
601 * (in which case S_IFLNK should be defined).
603 } else if ((c
== 'r') && (strncmp(argv
[1], "readlink", length
) == 0)
605 char linkValue
[MAXPATHLEN
+1];
609 argv
[1] = "readlink";
612 linkLength
= readlink(fileName
, linkValue
, sizeof(linkValue
) - 1);
613 if (linkLength
== -1) {
614 Tcl_AppendResult(interp
, "couldn't readlink \"", argv
[2],
615 "\": ", Tcl_UnixError(interp
), (char *) NULL
);
618 linkValue
[linkLength
] = 0;
619 Tcl_SetResult(interp
, linkValue
, TCL_VOLATILE
);
622 } else if ((c
== 's') && (strncmp(argv
[1], "size", length
) == 0)
628 if (stat(fileName
, &statBuf
) == -1) {
631 sprintf(interp
->result
, "%ld", statBuf
.st_size
);
633 } else if ((c
== 's') && (strncmp(argv
[1], "stat", length
) == 0)
636 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
637 " stat name varName\"", (char *) NULL
);
641 if (stat(fileName
, &statBuf
) == -1) {
643 Tcl_AppendResult(interp
, "couldn't stat \"", argv
[2],
644 "\": ", Tcl_UnixError(interp
), (char *) NULL
);
647 return StoreStatData(interp
, argv
[3], &statBuf
);
648 } else if ((c
== 't') && (strncmp(argv
[1], "type", length
) == 0)
654 if (lstat(fileName
, &statBuf
) == -1) {
657 interp
->result
= GetFileType((int) statBuf
.st_mode
);
660 Tcl_AppendResult(interp
, "bad option \"", argv
[1],
661 "\": should be atime, dirname, executable, exists, ",
662 "extension, isdirectory, isfile, lstat, mtime, owned, ",
667 "root, size, stat, tail, type, ",
672 if (stat(fileName
, &statBuf
) == -1) {
673 interp
->result
= "0";
678 mode
= (geteuid() == statBuf
.st_uid
);
681 mode
= S_ISREG(statBuf
.st_mode
);
684 mode
= S_ISDIR(statBuf
.st_mode
);
688 interp
->result
= "1";
690 interp
->result
= "0";
696 *----------------------------------------------------------------------
700 * This is a utility procedure that breaks out the fields of a
701 * "stat" structure and stores them in textual form into the
702 * elements of an associative array.
705 * Returns a standard Tcl return value. If an error occurs then
706 * a message is left in interp->result.
709 * Elements of the associative array given by "varName" are modified.
711 *----------------------------------------------------------------------
716 Tcl_Interp
*interp
, /* Interpreter for error reports. */
717 char *varName
, /* Name of associative array variable
718 * in which to store stat results. */
719 struct stat
*statPtr
/* Pointer to buffer containing
720 * stat data to store in varName. */
725 sprintf(string
, "%d", statPtr
->st_dev
);
726 if (Tcl_SetVar2(interp
, varName
, "dev", string
, TCL_LEAVE_ERR_MSG
)
730 sprintf(string
, "%d", statPtr
->st_ino
);
731 if (Tcl_SetVar2(interp
, varName
, "ino", string
, TCL_LEAVE_ERR_MSG
)
735 sprintf(string
, "%d", statPtr
->st_mode
);
736 if (Tcl_SetVar2(interp
, varName
, "mode", string
, TCL_LEAVE_ERR_MSG
)
740 sprintf(string
, "%d", statPtr
->st_nlink
);
741 if (Tcl_SetVar2(interp
, varName
, "nlink", string
, TCL_LEAVE_ERR_MSG
)
745 sprintf(string
, "%d", statPtr
->st_uid
);
746 if (Tcl_SetVar2(interp
, varName
, "uid", string
, TCL_LEAVE_ERR_MSG
)
750 sprintf(string
, "%d", statPtr
->st_gid
);
751 if (Tcl_SetVar2(interp
, varName
, "gid", string
, TCL_LEAVE_ERR_MSG
)
755 sprintf(string
, "%ld", statPtr
->st_size
);
756 if (Tcl_SetVar2(interp
, varName
, "size", string
, TCL_LEAVE_ERR_MSG
)
760 sprintf(string
, "%ld", statPtr
->st_atime
);
761 if (Tcl_SetVar2(interp
, varName
, "atime", string
, TCL_LEAVE_ERR_MSG
)
765 sprintf(string
, "%ld", statPtr
->st_mtime
);
766 if (Tcl_SetVar2(interp
, varName
, "mtime", string
, TCL_LEAVE_ERR_MSG
)
770 sprintf(string
, "%ld", statPtr
->st_ctime
);
771 if (Tcl_SetVar2(interp
, varName
, "ctime", string
, TCL_LEAVE_ERR_MSG
)
775 if (Tcl_SetVar2(interp
, varName
, "type",
776 GetFileType((int) statPtr
->st_mode
), TCL_LEAVE_ERR_MSG
) == NULL
) {
783 *----------------------------------------------------------------------
787 * Given a mode word, returns a string identifying the type of a
791 * A static text string giving the file type from mode.
796 *----------------------------------------------------------------------
800 GetFileType (int mode
)
804 } else if (S_ISDIR(mode
)) {
806 } else if (S_ISCHR(mode
)) {
807 return "characterSpecial";
808 } else if (S_ISBLK(mode
)) {
809 return "blockSpecial";
810 } else if (S_ISFIFO(mode
)) {
812 } else if (S_ISLNK(mode
)) {
814 } else if (S_ISSOCK(mode
)) {
821 *----------------------------------------------------------------------
825 * This procedure is invoked to process the "flush" Tcl command.
826 * See the user documentation for details on what it does.
829 * A standard Tcl result.
832 * See the user documentation.
834 *----------------------------------------------------------------------
840 ClientData notUsed
, /* Not used. */
841 Tcl_Interp
*interp
, /* Current interpreter. */
842 int argc
, /* Number of arguments. */
843 char **argv
/* Argument strings. */
850 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
851 " fileId\"", (char *) NULL
);
854 if (TclGetOpenFile(interp
, argv
[1], &filePtr
) != TCL_OK
) {
857 if (!filePtr
->writable
) {
858 Tcl_AppendResult(interp
, "\"", argv
[1],
859 "\" wasn't opened for writing", (char *) NULL
);
866 if (fflush(f
) == EOF
) {
867 Tcl_AppendResult(interp
, "error flushing \"", argv
[1],
868 "\": ", Tcl_UnixError(interp
), (char *) NULL
);
876 *----------------------------------------------------------------------
880 * This procedure is invoked to process the "gets" Tcl command.
881 * See the user documentation for details on what it does.
884 * A standard Tcl result.
887 * See the user documentation.
889 *----------------------------------------------------------------------
895 ClientData notUsed
, /* Not used. */
896 Tcl_Interp
*interp
, /* Current interpreter. */
897 int argc
, /* Number of arguments. */
898 char **argv
/* Argument strings. */
901 # define BUF_SIZE 200
902 char buffer
[BUF_SIZE
+1];
903 int totalCount
, done
, flags
;
907 if ((argc
!= 2) && (argc
!= 3)) {
908 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
909 " fileId ?varName?\"", (char *) NULL
);
912 if (TclGetOpenFile(interp
, argv
[1], &filePtr
) != TCL_OK
) {
915 if (!filePtr
->readable
) {
916 Tcl_AppendResult(interp
, "\"", argv
[1],
917 "\" wasn't opened for reading", (char *) NULL
);
922 * We can't predict how large a line will be, so read it in
923 * pieces, appending to the current result or to a variable.
931 register int c
, count
;
934 for (p
= buffer
, count
= 0; count
< BUF_SIZE
-1; count
++, p
++) {
937 if (ferror(filePtr
->f
)) {
938 Tcl_ResetResult(interp
);
939 Tcl_AppendResult(interp
, "error reading \"", argv
[1],
940 "\": ", Tcl_UnixError(interp
), (char *) NULL
);
941 clearerr(filePtr
->f
);
943 } else if (feof(filePtr
->f
)) {
944 if ((totalCount
== 0) && (count
== 0)) {
959 Tcl_AppendResult(interp
, buffer
, (char *) NULL
);
961 if (Tcl_SetVar(interp
, argv
[2], buffer
, flags
|TCL_LEAVE_ERR_MSG
)
965 flags
= TCL_APPEND_VALUE
;
971 sprintf(interp
->result
, "%d", totalCount
);
977 *----------------------------------------------------------------------
981 * This procedure is invoked to process the "open" Tcl command.
982 * See the user documentation for details on what it does.
985 * A standard Tcl result.
988 * See the user documentation.
990 *----------------------------------------------------------------------
996 ClientData notUsed
, /* Not used. */
997 Tcl_Interp
*interp
, /* Current interpreter. */
998 int argc
, /* Number of arguments. */
999 char **argv
/* Argument strings. */
1002 Interp
*iPtr
= (Interp
*) interp
;
1005 register OpenFile
*filePtr
;
1009 } else if (argc
== 3) {
1012 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
1013 " filename ?access?\"", (char *) NULL
);
1017 filePtr
= (OpenFile
*) ckalloc(sizeof(OpenFile
));
1020 filePtr
->readable
= 0;
1021 filePtr
->writable
= 0;
1022 filePtr
->numPids
= 0;
1023 filePtr
->pidPtr
= NULL
;
1024 filePtr
->errorId
= -1;
1027 * Verify the requested form of access.
1031 if (argv
[1][0] == '|') {
1034 switch (access
[0]) {
1036 filePtr
->readable
= 1;
1039 filePtr
->writable
= 1;
1042 filePtr
->writable
= 1;
1046 Tcl_AppendResult(interp
, "illegal access mode \"", access
,
1047 "\"", (char *) NULL
);
1050 if (access
[1] == '+') {
1051 filePtr
->readable
= filePtr
->writable
= 1;
1052 if (access
[2] != 0) {
1055 } else if (access
[1] != 0) {
1060 * Open the file or create a process pipeline.
1064 char *fileName
= argv
[1];
1066 if (fileName
[0] == '~') {
1067 fileName
= Tcl_TildeSubst(interp
, fileName
);
1068 if (fileName
== NULL
) {
1072 filePtr
->f
= fopen(fileName
, access
);
1073 if (filePtr
->f
== NULL
) {
1074 Tcl_AppendResult(interp
, "couldn't open \"", argv
[1],
1075 "\": ", Tcl_UnixError(interp
), (char *) NULL
);
1079 int *inPipePtr
, *outPipePtr
;
1080 int cmdArgc
, inPipe
, outPipe
;
1083 if (Tcl_SplitList(interp
, argv
[1]+1, &cmdArgc
, &cmdArgv
) != TCL_OK
) {
1086 inPipePtr
= (filePtr
->writable
) ? &inPipe
: NULL
;
1087 outPipePtr
= (filePtr
->readable
) ? &outPipe
: NULL
;
1088 inPipe
= outPipe
= -1;
1089 filePtr
->numPids
= Tcl_CreatePipeline(interp
, cmdArgc
, cmdArgv
,
1090 &filePtr
->pidPtr
, inPipePtr
, outPipePtr
, &filePtr
->errorId
);
1091 ckfree((char *) cmdArgv
);
1092 if (filePtr
->numPids
< 0) {
1095 if (filePtr
->readable
) {
1096 if (outPipe
== -1) {
1100 Tcl_AppendResult(interp
, "can't read output from command:",
1101 " standard output was redirected", (char *) NULL
);
1104 filePtr
->f
= fdopen(outPipe
, "r");
1106 if (filePtr
->writable
) {
1108 Tcl_AppendResult(interp
, "can't write input to command:",
1109 " standard input was redirected", (char *) NULL
);
1112 if (filePtr
->f
!= NULL
) {
1113 filePtr
->f2
= fdopen(inPipe
, "w");
1115 filePtr
->f
= fdopen(inPipe
, "w");
1121 * Enter this new OpenFile structure in the table for the
1122 * interpreter. May have to expand the table to do this.
1125 fd
= fileno(filePtr
->f
);
1126 TclMakeFileTable(iPtr
, fd
);
1127 if (iPtr
->filePtrArray
[fd
] != NULL
) {
1128 panic("Tcl_OpenCmd found file already open");
1130 iPtr
->filePtrArray
[fd
] = filePtr
;
1131 sprintf(interp
->result
, "file%d", fd
);
1135 if (filePtr
->f
!= NULL
) {
1138 if (filePtr
->f2
!= NULL
) {
1139 fclose(filePtr
->f2
);
1141 if (filePtr
->numPids
> 0) {
1142 Tcl_DetachPids(filePtr
->numPids
, filePtr
->pidPtr
);
1143 ckfree((char *) filePtr
->pidPtr
);
1145 if (filePtr
->errorId
!= -1) {
1146 close(filePtr
->errorId
);
1148 ckfree((char *) filePtr
);
1153 *----------------------------------------------------------------------
1157 * This procedure is invoked to process the "pwd" Tcl command.
1158 * See the user documentation for details on what it does.
1161 * A standard Tcl result.
1164 * See the user documentation.
1166 *----------------------------------------------------------------------
1172 ClientData dummy
, /* Not used. */
1173 Tcl_Interp
*interp
, /* Current interpreter. */
1174 int argc
, /* Number of arguments. */
1175 char **argv
/* Argument strings. */
1178 char buffer
[MAXPATHLEN
+1];
1181 Tcl_AppendResult(interp
, "wrong # args: should be \"",
1182 argv
[0], "\"", (char *) NULL
);
1185 if (currentDir
== NULL
) {
1187 if (getwd(buffer
) == NULL
) {
1188 Tcl_AppendResult(interp
, "error getting working directory name: ",
1189 buffer
, (char *) NULL
);
1193 if (getcwd(buffer
, MAXPATHLEN
) == 0) {
1194 if (errno
== ERANGE
) {
1195 interp
->result
= "working directory name is too long";
1197 Tcl_AppendResult(interp
,
1198 "error getting working directory name: ",
1199 Tcl_UnixError(interp
), (char *) NULL
);
1204 currentDir
= (char *) ckalloc((unsigned) (strlen(buffer
) + 1));
1205 strcpy(currentDir
, buffer
);
1207 interp
->result
= currentDir
;
1212 *----------------------------------------------------------------------
1216 * This procedure is invoked to process the "puts" Tcl command.
1217 * See the user documentation for details on what it does.
1220 * A standard Tcl result.
1223 * See the user documentation.
1225 *----------------------------------------------------------------------
1231 ClientData dummy
, /* Not used. */
1232 Tcl_Interp
*interp
, /* Current interpreter. */
1233 int argc
, /* Number of arguments. */
1234 char **argv
/* Argument strings. */
1241 if (strncmp(argv
[3], "nonewline", strlen(argv
[3])) != 0) {
1242 Tcl_AppendResult(interp
, "bad argument \"", argv
[3],
1243 "\": should be \"nonewline\"", (char *) NULL
);
1246 } else if (argc
!= 3) {
1247 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
1248 " fileId string ?nonewline?\"", (char *) NULL
);
1251 if (TclGetOpenFile(interp
, argv
[1], &filePtr
) != TCL_OK
) {
1254 if (!filePtr
->writable
) {
1255 Tcl_AppendResult(interp
, "\"", argv
[1],
1256 "\" wasn't opened for writing", (char *) NULL
);
1269 Tcl_AppendResult(interp
, "error writing \"", argv
[1],
1270 "\": ", Tcl_UnixError(interp
), (char *) NULL
);
1278 *----------------------------------------------------------------------
1282 * This procedure is invoked to process the "read" Tcl command.
1283 * See the user documentation for details on what it does.
1286 * A standard Tcl result.
1289 * See the user documentation.
1291 *----------------------------------------------------------------------
1297 ClientData dummy
, /* Not used. */
1298 Tcl_Interp
*interp
, /* Current interpreter. */
1299 int argc
, /* Number of arguments. */
1300 char **argv
/* Argument strings. */
1304 int bytesLeft
, bytesRead
, count
;
1305 #define READ_BUF_SIZE 4096
1306 char buffer
[READ_BUF_SIZE
+1];
1309 if ((argc
!= 2) && (argc
!= 3)) {
1310 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
1311 " fileId ?numBytes|nonewline?\"", (char *) NULL
);
1314 if (TclGetOpenFile(interp
, argv
[1], &filePtr
) != TCL_OK
) {
1317 if (!filePtr
->readable
) {
1318 Tcl_AppendResult(interp
, "\"", argv
[1],
1319 "\" wasn't opened for reading", (char *) NULL
);
1324 * Compute how many bytes to read, and see whether the final
1325 * newline should be dropped.
1329 if ((argc
> 2) && isdigit(argv
[2][0])) {
1330 if (Tcl_GetInt(interp
, argv
[2], &bytesLeft
) != TCL_OK
) {
1336 if (strncmp(argv
[2], "nonewline", strlen(argv
[2])) == 0) {
1339 Tcl_AppendResult(interp
, "bad argument \"", argv
[2],
1340 "\": should be \"nonewline\"", (char *) NULL
);
1347 * Read the file in one or more chunks.
1351 while (bytesLeft
> 0) {
1352 count
= READ_BUF_SIZE
;
1353 if (bytesLeft
< READ_BUF_SIZE
) {
1356 count
= fread(buffer
, 1, count
, filePtr
->f
);
1357 if (ferror(filePtr
->f
)) {
1358 Tcl_ResetResult(interp
);
1359 Tcl_AppendResult(interp
, "error reading \"", argv
[1],
1360 "\": ", Tcl_UnixError(interp
), (char *) NULL
);
1361 clearerr(filePtr
->f
);
1368 Tcl_AppendResult(interp
, buffer
, (char *) NULL
);
1372 if ((newline
== 0) && (interp
->result
[bytesRead
-1] == '\n')) {
1373 interp
->result
[bytesRead
-1] = 0;
1379 *----------------------------------------------------------------------
1383 * This procedure is invoked to process the "seek" Tcl command.
1384 * See the user documentation for details on what it does.
1387 * A standard Tcl result.
1390 * See the user documentation.
1392 *----------------------------------------------------------------------
1398 ClientData notUsed
, /* Not used. */
1399 Tcl_Interp
*interp
, /* Current interpreter. */
1400 int argc
, /* Number of arguments. */
1401 char **argv
/* Argument strings. */
1407 if ((argc
!= 3) && (argc
!= 4)) {
1408 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
1409 " fileId offset ?origin?\"", (char *) NULL
);
1412 if (TclGetOpenFile(interp
, argv
[1], &filePtr
) != TCL_OK
) {
1415 if (Tcl_GetInt(interp
, argv
[2], &offset
) != TCL_OK
) {
1423 length
= strlen(argv
[3]);
1425 if ((c
== 's') && (strncmp(argv
[3], "start", length
) == 0)) {
1427 } else if ((c
== 'c') && (strncmp(argv
[3], "current", length
) == 0)) {
1429 } else if ((c
== 'e') && (strncmp(argv
[3], "end", length
) == 0)) {
1432 Tcl_AppendResult(interp
, "bad origin \"", argv
[3],
1433 "\": should be start, current, or end", (char *) NULL
);
1437 if (fseek(filePtr
->f
, offset
, mode
) == -1) {
1438 Tcl_AppendResult(interp
, "error during seek: ",
1439 Tcl_UnixError(interp
), (char *) NULL
);
1440 clearerr(filePtr
->f
);
1448 *----------------------------------------------------------------------
1452 * This procedure is invoked to process the "source" Tcl command.
1453 * See the user documentation for details on what it does.
1456 * A standard Tcl result.
1459 * See the user documentation.
1461 *----------------------------------------------------------------------
1467 ClientData dummy
, /* Not used. */
1468 Tcl_Interp
*interp
, /* Current interpreter. */
1469 int argc
, /* Number of arguments. */
1470 char **argv
/* Argument strings. */
1474 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
1475 " fileName\"", (char *) NULL
);
1478 return Tcl_EvalFile(interp
, argv
[1]);
1482 *----------------------------------------------------------------------
1486 * This procedure is invoked to process the "tell" Tcl command.
1487 * See the user documentation for details on what it does.
1490 * A standard Tcl result.
1493 * See the user documentation.
1495 *----------------------------------------------------------------------
1501 ClientData notUsed
, /* Not used. */
1502 Tcl_Interp
*interp
, /* Current interpreter. */
1503 int argc
, /* Number of arguments. */
1504 char **argv
/* Argument strings. */
1510 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
1511 " fileId\"", (char *) NULL
);
1514 if (TclGetOpenFile(interp
, argv
[1], &filePtr
) != TCL_OK
) {
1517 sprintf(interp
->result
, "%d", ftell(filePtr
->f
));
1522 *----------------------------------------------------------------------
1526 * This procedure is invoked to process the "time" Tcl command.
1527 * See the user documentation for details on what it does.
1530 * A standard Tcl result.
1533 * See the user documentation.
1535 *----------------------------------------------------------------------
1541 ClientData dummy
, /* Not used. */
1542 Tcl_Interp
*interp
, /* Current interpreter. */
1543 int argc
, /* Number of arguments. */
1544 char **argv
/* Argument strings. */
1547 int count
, i
, result
;
1550 struct timeval start
, stop
;
1560 } else if (argc
== 3) {
1561 if (Tcl_GetInt(interp
, argv
[2], &count
) != TCL_OK
) {
1565 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
1566 " command ?count?\"", (char *) NULL
);
1570 gettimeofday(&start
, &tz
);
1572 start
= times(&dummy2
);
1574 for (i
= count
; i
> 0; i
--) {
1575 result
= Tcl_Eval(interp
, argv
[1], 0, (char **) NULL
);
1576 if (result
!= TCL_OK
) {
1577 if (result
== TCL_ERROR
) {
1579 sprintf(msg
, "\n (\"time\" body line %d)",
1581 Tcl_AddErrorInfo(interp
, msg
);
1587 gettimeofday(&stop
, &tz
);
1588 micros
= (stop
.tv_sec
- start
.tv_sec
)*1000000
1589 + (stop
.tv_usec
- start
.tv_usec
);
1592 stop
= times(&dummy2
);
1593 timePer
= (((double) (stop
- start
))*1000000.0)/CLK_TCK
;
1595 Tcl_ResetResult(interp
);
1596 sprintf(interp
->result
, "%.0f microseconds per iteration", timePer
/count
);
1601 *----------------------------------------------------------------------
1603 * CleanupChildren --
1605 * This is a utility procedure used to wait for child processes
1606 * to exit, record information about abnormal exits, and then
1607 * collect any stderr output generated by them.
1610 * The return value is a standard Tcl result. If anything at
1611 * weird happened with the child processes, TCL_ERROR is returned
1612 * and a message is left in interp->result.
1615 * If the last character of interp->result is a newline, then it
1616 * is removed. File errorId gets closed, and pidPtr is freed
1617 * back to the storage allocator.
1619 *----------------------------------------------------------------------
1624 Tcl_Interp
*interp
, /* Used for error messages. */
1625 int numPids
, /* Number of entries in pidPtr array. */
1626 int *pidPtr
, /* Array of process ids of children. */
1627 int errorId
/* File descriptor index for file containing
1628 * stderr output from pipeline. -1 means
1629 * there isn't any stderr output. */
1632 int result
= TCL_OK
;
1634 WAIT_STATUS_TYPE waitStatus
;
1636 for (i
= 0; i
< numPids
; i
++) {
1637 pid
= Tcl_WaitPids(1, &pidPtr
[i
], (int *) &waitStatus
);
1639 Tcl_AppendResult(interp
, "error waiting for process to exit: ",
1640 Tcl_UnixError(interp
), (char *) NULL
);
1645 * Create error messages for unusual process exits. An
1646 * extra newline gets appended to each error message, but
1647 * it gets removed below (in the same fashion that an
1648 * extra newline in the command's output is removed).
1651 if (!WIFEXITED(waitStatus
) || (WEXITSTATUS(waitStatus
) != 0)) {
1652 char msg1
[20], msg2
[20];
1655 sprintf(msg1
, "%d", pid
);
1656 if (WIFEXITED(waitStatus
)) {
1657 sprintf(msg2
, "%d", WEXITSTATUS(waitStatus
));
1658 Tcl_SetErrorCode(interp
, "CHILDSTATUS", msg1
, msg2
,
1660 } else if (WIFSIGNALED(waitStatus
)) {
1663 p
= Tcl_SignalMsg((int) (WTERMSIG(waitStatus
)));
1664 Tcl_SetErrorCode(interp
, "CHILDKILLED", msg1
,
1665 Tcl_SignalId((int) (WTERMSIG(waitStatus
))), p
,
1667 Tcl_AppendResult(interp
, "child killed: ", p
, "\n",
1669 } else if (WIFSTOPPED(waitStatus
)) {
1672 p
= Tcl_SignalMsg((int) (WSTOPSIG(waitStatus
)));
1673 Tcl_SetErrorCode(interp
, "CHILDSUSP", msg1
,
1674 Tcl_SignalId((int) (WSTOPSIG(waitStatus
))), p
, (char *) NULL
);
1675 Tcl_AppendResult(interp
, "child suspended: ", p
, "\n",
1678 Tcl_AppendResult(interp
,
1679 "child wait status didn't make sense\n",
1684 ckfree((char *) pidPtr
);
1687 * Read the standard error file. If there's anything there,
1688 * then return an error and add the file's contents to the result
1694 # define BUFFER_SIZE 1000
1695 char buffer
[BUFFER_SIZE
+1];
1698 count
= read(errorId
, buffer
, BUFFER_SIZE
);
1704 Tcl_AppendResult(interp
,
1705 "error reading stderr output file: ",
1706 Tcl_UnixError(interp
), (char *) NULL
);
1710 Tcl_AppendResult(interp
, buffer
, (char *) NULL
);
1716 * If the last character of interp->result is a newline, then remove
1717 * the newline character (the newline would just confuse things).
1720 length
= strlen(interp
->result
);
1721 if ((length
> 0) && (interp
->result
[length
-1] == '\n')) {
1722 interp
->result
[length
-1] = '\0';