6 * This file contains the top-level command procedures for
7 * commands in the Tcl core that require UNIX facilities
8 * such as files and process execution. Much of the code
9 * in this file is based on earlier versions contributed
10 * by Karl Lehenbauer, Mark Diekhans and Peter da Silva.
12 * Copyright 1991 Regents of the University of California
13 * Permission to use, copy, modify, and distribute this
14 * software and its documentation for any purpose and without
15 * fee is hereby granted, provided that this copyright
16 * notice appears in all copies. The University of California
17 * makes no representations about the suitability of this
18 * software for any purpose. It is provided "as is" without
19 * express or implied warranty.
21 * $Id: tclunxaz.c,v 1.1.1.1 2001/04/29 20:35:40 karll Exp $
30 * The variable below caches the name of the current working directory
31 * in order to avoid repeated calls to getwd. The string is malloc-ed.
32 * NULL means the cache needs to be refreshed.
35 static char *currentDir = NULL;
38 * Prototypes for local procedures defined in this file:
42 static int CleanupChildren _ANSI_ARGS_((Tcl_Interp *interp,
43 int numPids, int *pidPtr, int errorId));
45 static char * GetFileType _ANSI_ARGS_((int mode));
46 static int StoreStatData _ANSI_ARGS_((Tcl_Interp *interp,
47 char *varName, struct stat *statPtr));
50 *----------------------------------------------------------------------
54 * This procedure is invoked to process the "cd" Tcl command.
55 * See the user documentation for details on what it does.
58 * A standard Tcl result.
61 * See the user documentation.
63 *----------------------------------------------------------------------
68 Tcl_CdCmd(dummy, interp, argc, argv)
69 ClientData dummy; /* Not used. */
70 Tcl_Interp *interp; /* Current interpreter. */
71 int argc; /* Number of arguments. */
72 char **argv; /* Argument strings. */
77 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
78 " dirName\"", (char *) NULL);
87 dirName = Tcl_TildeSubst(interp, dirName);
88 if (dirName == NULL) {
91 if (currentDir != NULL) {
95 if (chdir(dirName) != 0) {
96 Tcl_AppendResult(interp, "couldn't change working directory to \"",
97 dirName, "\": ", Tcl_UnixError(interp), (char *) NULL);
104 *----------------------------------------------------------------------
108 * This procedure is invoked to process the "close" Tcl command.
109 * See the user documentation for details on what it does.
112 * A standard Tcl result.
115 * See the user documentation.
117 *----------------------------------------------------------------------
122 Tcl_CloseCmd(dummy, interp, argc, argv)
123 ClientData dummy; /* Not used. */
124 Tcl_Interp *interp; /* Current interpreter. */
125 int argc; /* Number of arguments. */
126 char **argv; /* Argument strings. */
132 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
133 " fileId\"", (char *) NULL);
136 if (TclGetOpenFile(interp, argv[1], &filePtr) != TCL_OK) {
139 ((Interp *) interp)->filePtrArray[fileno(filePtr->f)] = NULL;
142 * First close the file (in the case of a process pipeline, there may
143 * be two files, one for the pipe at each end of the pipeline).
146 if (filePtr->f2 != NULL) {
147 if (fclose(filePtr->f2) == EOF) {
148 Tcl_AppendResult(interp, "error closing \"", argv[1],
149 "\": ", Tcl_UnixError(interp), "\n", (char *) NULL);
153 if (fclose(filePtr->f) == EOF) {
154 Tcl_AppendResult(interp, "error closing \"", argv[1],
155 "\": ", Tcl_UnixError(interp), "\n", (char *) NULL);
160 * If the file was a connection to a pipeline, clean up everything
161 * associated with the child processes.
165 if (filePtr->numPids > 0) {
166 if (CleanupChildren(interp, filePtr->numPids, filePtr->pidPtr,
167 filePtr->errorId) != TCL_OK) {
173 ckfree((char *) filePtr);
178 *----------------------------------------------------------------------
182 * This procedure is invoked to process the "eof" Tcl command.
183 * See the user documentation for details on what it does.
186 * A standard Tcl result.
189 * See the user documentation.
191 *----------------------------------------------------------------------
196 Tcl_EofCmd(notUsed, interp, argc, argv)
197 ClientData notUsed; /* Not used. */
198 Tcl_Interp *interp; /* Current interpreter. */
199 int argc; /* Number of arguments. */
200 char **argv; /* Argument strings. */
205 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
206 " fileId\"", (char *) NULL);
209 if (TclGetOpenFile(interp, argv[1], &filePtr) != TCL_OK) {
212 if (feof(filePtr->f)) {
213 interp->result = "1";
215 interp->result = "0";
222 *----------------------------------------------------------------------
226 * This procedure is invoked to process the "exec" Tcl command.
227 * See the user documentation for details on what it does.
230 * A standard Tcl result.
233 * See the user documentation.
235 *----------------------------------------------------------------------
240 Tcl_ExecCmd(dummy, interp, argc, argv)
241 ClientData dummy; /* Not used. */
242 Tcl_Interp *interp; /* Current interpreter. */
243 int argc; /* Number of arguments. */
244 char **argv; /* Argument strings. */
246 int outputId; /* File id for output pipe. -1
247 * means command overrode. */
248 int errorId; /* File id for temporary file
249 * containing error output. */
254 * See if the command is to be run in background; if so, create
255 * the command, detach it, and return.
258 if ((argv[argc-1][0] == '&') && (argv[argc-1][1] == 0)) {
261 numPids = Tcl_CreatePipeline(interp, argc-1, argv+1, &pidPtr,
262 (int *) NULL, (int *) NULL, (int *) NULL);
266 Tcl_DetachPids(numPids, pidPtr);
267 ckfree((char *) pidPtr);
272 * Create the command's pipeline.
275 numPids = Tcl_CreatePipeline(interp, argc-1, argv+1, &pidPtr,
276 (int *) NULL, &outputId, &errorId);
282 * Read the child's output (if any) and put it into the result.
286 if (outputId != -1) {
288 # define BUFFER_SIZE 1000
289 char buffer[BUFFER_SIZE+1];
292 count = read(outputId, buffer, BUFFER_SIZE);
298 Tcl_ResetResult(interp);
299 Tcl_AppendResult(interp,
300 "error reading from output pipe: ",
301 Tcl_UnixError(interp), (char *) NULL);
306 Tcl_AppendResult(interp, buffer, (char *) NULL);
311 if (CleanupChildren(interp, numPids, pidPtr, errorId) != TCL_OK) {
318 Tcl_ExecCmd(dummy, interp, argc, argv)
319 ClientData dummy; /* Not used. */
320 Tcl_Interp *interp; /* Current interpreter. */
321 int argc; /* Number of arguments. */
322 char **argv; /* Argument strings. */
329 #define TMP_NAME "/tmp/tcl.exec.XXXXXX"
330 char tmpname[sizeof(TMP_NAME) + 1];
333 /* Create a temporary file for the output from our exec command */
334 strcpy(tmpname, TMP_NAME);
335 tmpfd = mkstemp(tmpname);
337 Tcl_AppendResult(interp,
338 "couldn't create temp file file for exec: ", Tcl_UnixError(interp), (char *) NULL);
341 /*printf("Writing output to %s, fd=%d\n", tmpname, tmpfd);*/
344 /* Use vfork and send output to this temporary file */
348 open("/dev/null", O_RDONLY);
352 /*open("/dev/null", O_WRONLY);*/
355 execvp(argv[1], argv + 1);
359 /* Wait for the child to exit */
361 waitpid(pid, &status, 0);
362 } while (!WIFEXITED(status));
365 * Read the child's output (if any) and put it into the result.
367 lseek(tmpfd, SEEK_SET, 0);
370 # define BUFFER_SIZE 1000
371 char buffer[BUFFER_SIZE+1];
374 count = read(tmpfd, buffer, BUFFER_SIZE);
380 Tcl_ResetResult(interp);
381 Tcl_AppendResult(interp,
382 "error reading result: ",
383 Tcl_UnixError(interp), (char *) NULL);
388 Tcl_AppendResult(interp, buffer, (char *) NULL);
397 *----------------------------------------------------------------------
401 * This procedure is invoked to process the "exit" Tcl command.
402 * See the user documentation for details on what it does.
405 * A standard Tcl result.
408 * See the user documentation.
410 *----------------------------------------------------------------------
415 Tcl_ExitCmd(dummy, interp, argc, argv)
416 ClientData dummy; /* Not used. */
417 Tcl_Interp *interp; /* Current interpreter. */
418 int argc; /* Number of arguments. */
419 char **argv; /* Argument strings. */
423 if ((argc != 1) && (argc != 2)) {
424 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
425 " ?returnCode?\"", (char *) NULL);
431 if (Tcl_GetInt(interp, argv[1], &value) != TCL_OK) {
435 return TCL_OK; /* Better not ever reach this! */
439 *----------------------------------------------------------------------
443 * This procedure is invoked to process the "file" Tcl command.
444 * See the user documentation for details on what it does.
447 * A standard Tcl result.
450 * See the user documentation.
452 *----------------------------------------------------------------------
457 Tcl_FileCmd(dummy, interp, argc, argv)
458 ClientData dummy; /* Not used. */
459 Tcl_Interp *interp; /* Current interpreter. */
460 int argc; /* Number of arguments. */
461 char **argv; /* Argument strings. */
465 int mode = 0; /* Initialized only to prevent
466 * compiler warning message. */
471 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
472 " option name ?arg ...?\"", (char *) NULL);
476 length = strlen(argv[1]);
479 * First handle operations on the file name.
482 fileName = Tcl_TildeSubst(interp, argv[2]);
483 if (fileName == NULL) {
486 if ((c == 'd') && (strncmp(argv[1], "dirname", length) == 0)) {
490 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
491 " ", argv[1], " name\"", (char *) NULL);
494 p = strrchr(fileName, '/');
496 interp->result = ".";
497 } else if (p == fileName) {
498 interp->result = "/";
501 Tcl_SetResult(interp, fileName, TCL_VOLATILE);
505 } else if ((c == 'r') && (strncmp(argv[1], "rootname", length) == 0)
510 argv[1] = "rootname";
513 p = strrchr(fileName, '.');
514 lastSlash = strrchr(fileName, '/');
515 if ((p == NULL) || ((lastSlash != NULL) && (lastSlash > p))) {
516 Tcl_SetResult(interp, fileName, TCL_VOLATILE);
519 Tcl_SetResult(interp, fileName, TCL_VOLATILE);
523 } else if ((c == 'e') && (strncmp(argv[1], "extension", length) == 0)
528 argv[1] = "extension";
531 p = strrchr(fileName, '.');
532 lastSlash = strrchr(fileName, '/');
533 if ((p != NULL) && ((lastSlash == NULL) || (lastSlash < p))) {
534 Tcl_SetResult(interp, p, TCL_VOLATILE);
537 } else if ((c == 't') && (strncmp(argv[1], "tail", length) == 0)
543 p = strrchr(fileName, '/');
545 Tcl_SetResult(interp, p+1, TCL_VOLATILE);
547 Tcl_SetResult(interp, fileName, TCL_VOLATILE);
553 * Next, handle operations that can be satisfied with the "access"
557 if (fileName == NULL) {
560 if ((c == 'r') && (strncmp(argv[1], "readable", length) == 0)
563 argv[1] = "readable";
568 if (access(fileName, mode) == -1) {
569 interp->result = "0";
571 interp->result = "1";
574 } else if ((c == 'w') && (strncmp(argv[1], "writable", length) == 0)) {
576 argv[1] = "writable";
581 } else if ((c == 'e') && (strncmp(argv[1], "executable", length) == 0)
584 argv[1] = "executable";
589 } else if ((c == 'e') && (strncmp(argv[1], "exists", length) == 0)
600 * Next, handle operations on the file
603 if ((c == 'd') && (strncmp(argv[1], "delete", length) == 0)
609 if (unlink(fileName) == -1 && errno != ENOENT) {
610 Tcl_AppendResult(interp, "couldn't delete \"", argv[2],
611 "\": ", Tcl_UnixError(interp), (char *) NULL);
616 else if (strcmp(argv[1], "rename") == 0) {
618 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
619 " ", argv[1], " source target\"", (char *) NULL);
622 if (rename(argv[2], argv[3]) == -1) {
623 Tcl_AppendResult(interp, "couldn't rename \"", argv[2],
624 "\": ", Tcl_UnixError(interp), (char *) NULL);
632 * Lastly, check stuff that requires the file to be stat-ed.
635 if ((c == 'a') && (strncmp(argv[1], "atime", length) == 0)) {
640 if (stat(fileName, &statBuf) == -1) {
643 sprintf(interp->result, "%ld", statBuf.st_atime);
645 } else if ((c == 'i') && (strncmp(argv[1], "isdirectory", length) == 0)
648 argv[1] = "isdirectory";
652 } else if ((c == 'i') && (strncmp(argv[1], "isfile", length) == 0)
659 } else if ((c == 'l') && (strncmp(argv[1], "lstat", length) == 0)) {
661 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
662 " lstat name varName\"", (char *) NULL);
666 if (lstat(fileName, &statBuf) == -1) {
667 Tcl_AppendResult(interp, "couldn't lstat \"", argv[2],
668 "\": ", Tcl_UnixError(interp), (char *) NULL);
671 return StoreStatData(interp, argv[3], &statBuf);
672 } else if ((c == 'm') && (strncmp(argv[1], "mtime", length) == 0)) {
677 if (stat(fileName, &statBuf) == -1) {
680 sprintf(interp->result, "%ld", statBuf.st_mtime);
682 } else if ((c == 'o') && (strncmp(argv[1], "owned", length) == 0)) {
690 * This option is only included if symbolic links exist on this system
691 * (in which case S_IFLNK should be defined).
693 } else if ((c == 'r') && (strncmp(argv[1], "readlink", length) == 0)
695 char linkValue[MAXPATHLEN+1];
699 argv[1] = "readlink";
702 linkLength = readlink(fileName, linkValue, sizeof(linkValue) - 1);
703 if (linkLength == -1) {
704 Tcl_AppendResult(interp, "couldn't readlink \"", argv[2],
705 "\": ", Tcl_UnixError(interp), (char *) NULL);
708 linkValue[linkLength] = 0;
709 Tcl_SetResult(interp, linkValue, TCL_VOLATILE);
712 } else if ((c == 's') && (strncmp(argv[1], "size", length) == 0)
718 if (stat(fileName, &statBuf) == -1) {
721 sprintf(interp->result, "%ld", statBuf.st_size);
723 } else if ((c == 's') && (strncmp(argv[1], "stat", length) == 0)
726 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
727 " stat name varName\"", (char *) NULL);
731 if (stat(fileName, &statBuf) == -1) {
733 Tcl_AppendResult(interp, "couldn't stat \"", argv[2],
734 "\": ", Tcl_UnixError(interp), (char *) NULL);
737 return StoreStatData(interp, argv[3], &statBuf);
738 } else if ((c == 't') && (strncmp(argv[1], "type", length) == 0)
744 if (lstat(fileName, &statBuf) == -1) {
747 interp->result = GetFileType((int) statBuf.st_mode);
750 Tcl_AppendResult(interp, "bad option \"", argv[1],
751 "\": should be atime, dirname, executable, exists, ",
752 "extension, isdirectory, isfile, lstat, mtime, owned, ",
757 "root, size, stat, tail, type, ",
762 if (stat(fileName, &statBuf) == -1) {
763 interp->result = "0";
768 mode = (geteuid() == statBuf.st_uid);
771 mode = S_ISREG(statBuf.st_mode);
774 mode = S_ISDIR(statBuf.st_mode);
778 interp->result = "1";
780 interp->result = "0";
786 *----------------------------------------------------------------------
790 * This is a utility procedure that breaks out the fields of a
791 * "stat" structure and stores them in textual form into the
792 * elements of an associative array.
795 * Returns a standard Tcl return value. If an error occurs then
796 * a message is left in interp->result.
799 * Elements of the associative array given by "varName" are modified.
801 *----------------------------------------------------------------------
805 StoreStatData(interp, varName, statPtr)
806 Tcl_Interp *interp; /* Interpreter for error reports. */
807 char *varName; /* Name of associative array variable
808 * in which to store stat results. */
809 struct stat *statPtr; /* Pointer to buffer containing
810 * stat data to store in varName. */
814 sprintf(string, "%d", (int)statPtr->st_dev);
815 if (Tcl_SetVar2(interp, varName, "dev", string, TCL_LEAVE_ERR_MSG)
819 sprintf(string, "%d", (int)statPtr->st_ino);
820 if (Tcl_SetVar2(interp, varName, "ino", string, TCL_LEAVE_ERR_MSG)
824 sprintf(string, "%d", statPtr->st_mode);
825 if (Tcl_SetVar2(interp, varName, "mode", string, TCL_LEAVE_ERR_MSG)
829 sprintf(string, "%d", statPtr->st_nlink);
830 if (Tcl_SetVar2(interp, varName, "nlink", string, TCL_LEAVE_ERR_MSG)
834 sprintf(string, "%d", statPtr->st_uid);
835 if (Tcl_SetVar2(interp, varName, "uid", string, TCL_LEAVE_ERR_MSG)
839 sprintf(string, "%d", statPtr->st_gid);
840 if (Tcl_SetVar2(interp, varName, "gid", string, TCL_LEAVE_ERR_MSG)
844 sprintf(string, "%ld", statPtr->st_size);
845 if (Tcl_SetVar2(interp, varName, "size", string, TCL_LEAVE_ERR_MSG)
849 sprintf(string, "%ld", statPtr->st_atime);
850 if (Tcl_SetVar2(interp, varName, "atime", string, TCL_LEAVE_ERR_MSG)
854 sprintf(string, "%ld", statPtr->st_mtime);
855 if (Tcl_SetVar2(interp, varName, "mtime", string, TCL_LEAVE_ERR_MSG)
859 sprintf(string, "%ld", statPtr->st_ctime);
860 if (Tcl_SetVar2(interp, varName, "ctime", string, TCL_LEAVE_ERR_MSG)
864 if (Tcl_SetVar2(interp, varName, "type",
865 GetFileType((int) statPtr->st_mode), TCL_LEAVE_ERR_MSG) == NULL) {
872 *----------------------------------------------------------------------
876 * Given a mode word, returns a string identifying the type of a
880 * A static text string giving the file type from mode.
885 *----------------------------------------------------------------------
894 } else if (S_ISDIR(mode)) {
896 } else if (S_ISCHR(mode)) {
897 return "characterSpecial";
898 } else if (S_ISBLK(mode)) {
899 return "blockSpecial";
900 } else if (S_ISFIFO(mode)) {
902 } else if (S_ISLNK(mode)) {
904 } else if (S_ISSOCK(mode)) {
911 *----------------------------------------------------------------------
915 * This procedure is invoked to process the "flush" Tcl command.
916 * See the user documentation for details on what it does.
919 * A standard Tcl result.
922 * See the user documentation.
924 *----------------------------------------------------------------------
929 Tcl_FlushCmd(notUsed, interp, argc, argv)
930 ClientData notUsed; /* Not used. */
931 Tcl_Interp *interp; /* Current interpreter. */
932 int argc; /* Number of arguments. */
933 char **argv; /* Argument strings. */
939 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
940 " fileId\"", (char *) NULL);
943 if (TclGetOpenFile(interp, argv[1], &filePtr) != TCL_OK) {
946 if (!filePtr->writable) {
947 Tcl_AppendResult(interp, "\"", argv[1],
948 "\" wasn't opened for writing", (char *) NULL);
955 if (fflush(f) == EOF) {
956 Tcl_AppendResult(interp, "error flushing \"", argv[1],
957 "\": ", Tcl_UnixError(interp), (char *) NULL);
965 *----------------------------------------------------------------------
969 * This procedure is invoked to process the "gets" Tcl command.
970 * See the user documentation for details on what it does.
973 * A standard Tcl result.
976 * See the user documentation.
978 *----------------------------------------------------------------------
983 Tcl_GetsCmd(notUsed, interp, argc, argv)
984 ClientData notUsed; /* Not used. */
985 Tcl_Interp *interp; /* Current interpreter. */
986 int argc; /* Number of arguments. */
987 char **argv; /* Argument strings. */
989 # define BUF_SIZE 200
990 char buffer[BUF_SIZE+1];
991 int totalCount, done, flags;
995 if ((argc != 2) && (argc != 3)) {
996 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
997 " fileId ?varName?\"", (char *) NULL);
1000 if (TclGetOpenFile(interp, argv[1], &filePtr) != TCL_OK) {
1003 if (!filePtr->readable) {
1004 Tcl_AppendResult(interp, "\"", argv[1],
1005 "\" wasn't opened for reading", (char *) NULL);
1010 * We can't predict how large a line will be, so read it in
1011 * pieces, appending to the current result or to a variable.
1019 register int c, count;
1022 for (p = buffer, count = 0; count < BUF_SIZE-1; count++, p++) {
1025 if (ferror(filePtr->f)) {
1026 Tcl_ResetResult(interp);
1027 Tcl_AppendResult(interp, "error reading \"", argv[1],
1028 "\": ", Tcl_UnixError(interp), (char *) NULL);
1029 clearerr(filePtr->f);
1031 } else if (feof(filePtr->f)) {
1032 if ((totalCount == 0) && (count == 0)) {
1047 Tcl_AppendResult(interp, buffer, (char *) NULL);
1049 if (Tcl_SetVar(interp, argv[2], buffer, flags|TCL_LEAVE_ERR_MSG)
1053 flags = TCL_APPEND_VALUE;
1055 totalCount += count;
1059 sprintf(interp->result, "%d", totalCount);
1065 *----------------------------------------------------------------------
1069 * This procedure is invoked to process the "open" Tcl command.
1070 * See the user documentation for details on what it does.
1073 * A standard Tcl result.
1076 * See the user documentation.
1078 *----------------------------------------------------------------------
1083 Tcl_OpenCmd(notUsed, interp, argc, argv)
1084 ClientData notUsed; /* Not used. */
1085 Tcl_Interp *interp; /* Current interpreter. */
1086 int argc; /* Number of arguments. */
1087 char **argv; /* Argument strings. */
1089 Interp *iPtr = (Interp *) interp;
1092 register OpenFile *filePtr;
1096 } else if (argc == 3) {
1099 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
1100 " filename ?access?\"", (char *) NULL);
1104 filePtr = (OpenFile *) ckalloc(sizeof(OpenFile));
1107 filePtr->readable = 0;
1108 filePtr->writable = 0;
1109 filePtr->numPids = 0;
1110 filePtr->pidPtr = NULL;
1111 filePtr->errorId = -1;
1114 * Verify the requested form of access.
1118 if (argv[1][0] == '|') {
1122 Tcl_AppendResult(interp, "open with pipeline not supported in this version of Tcl", (char *) NULL);
1126 switch (access[0]) {
1128 filePtr->readable = 1;
1131 filePtr->writable = 1;
1134 filePtr->writable = 1;
1138 Tcl_AppendResult(interp, "illegal access mode \"", access,
1139 "\"", (char *) NULL);
1142 if (access[1] == '+') {
1143 filePtr->readable = filePtr->writable = 1;
1144 if (access[2] != 0) {
1147 } else if (access[1] != 0) {
1151 /* Before we open any files, make sure the file table is allocated
1152 * so that stdin, etc. are sorted out
1154 TclMakeFileTable(iPtr, 0);
1157 * Open the file or create a process pipeline.
1161 char *fileName = argv[1];
1163 if (fileName[0] == '~') {
1164 fileName = Tcl_TildeSubst(interp, fileName);
1165 if (fileName == NULL) {
1169 filePtr->f = fopen(fileName, access);
1170 if (filePtr->f == NULL) {
1171 Tcl_AppendResult(interp, "couldn't open \"", argv[1],
1172 "\": ", Tcl_UnixError(interp), (char *) NULL);
1176 syslog(LOG_INFO, "Opened %s to give fd %d", fileName, fileno(filePtr->f));
1181 int *inPipePtr, *outPipePtr;
1182 int cmdArgc, inPipe, outPipe;
1185 if (Tcl_SplitList(interp, argv[1]+1, &cmdArgc, &cmdArgv) != TCL_OK) {
1188 inPipePtr = (filePtr->writable) ? &inPipe : NULL;
1189 outPipePtr = (filePtr->readable) ? &outPipe : NULL;
1190 inPipe = outPipe = -1;
1191 filePtr->numPids = Tcl_CreatePipeline(interp, cmdArgc, cmdArgv,
1192 &filePtr->pidPtr, inPipePtr, outPipePtr, &filePtr->errorId);
1193 ckfree((char *) cmdArgv);
1194 if (filePtr->numPids < 0) {
1197 if (filePtr->readable) {
1198 if (outPipe == -1) {
1202 Tcl_AppendResult(interp, "can't read output from command:",
1203 " standard output was redirected", (char *) NULL);
1206 filePtr->f = fdopen(outPipe, "r");
1208 if (filePtr->writable) {
1210 Tcl_AppendResult(interp, "can't write input to command:",
1211 " standard input was redirected", (char *) NULL);
1214 if (filePtr->f != NULL) {
1215 filePtr->f2 = fdopen(inPipe, "w");
1217 filePtr->f = fdopen(inPipe, "w");
1224 * Enter this new OpenFile structure in the table for the
1225 * interpreter. May have to expand the table to do this.
1228 fd = fileno(filePtr->f);
1229 TclMakeFileTable(iPtr, fd);
1230 if (iPtr->filePtrArray[fd] != NULL) {
1231 panic("Tcl_OpenCmd found file already open");
1233 iPtr->filePtrArray[fd] = filePtr;
1234 sprintf(interp->result, "file%d", fd);
1238 if (filePtr->f != NULL) {
1241 if (filePtr->f2 != NULL) {
1242 fclose(filePtr->f2);
1245 if (filePtr->numPids > 0) {
1246 Tcl_DetachPids(filePtr->numPids, filePtr->pidPtr);
1247 ckfree((char *) filePtr->pidPtr);
1250 if (filePtr->errorId != -1) {
1251 close(filePtr->errorId);
1253 ckfree((char *) filePtr);
1258 *----------------------------------------------------------------------
1262 * This procedure is invoked to process the "pwd" Tcl command.
1263 * See the user documentation for details on what it does.
1266 * A standard Tcl result.
1269 * See the user documentation.
1271 *----------------------------------------------------------------------
1276 Tcl_PwdCmd(dummy, interp, argc, argv)
1277 ClientData dummy; /* Not used. */
1278 Tcl_Interp *interp; /* Current interpreter. */
1279 int argc; /* Number of arguments. */
1280 char **argv; /* Argument strings. */
1282 char buffer[MAXPATHLEN+1];
1285 Tcl_AppendResult(interp, "wrong # args: should be \"",
1286 argv[0], "\"", (char *) NULL);
1289 if (currentDir == NULL) {
1290 if (getcwd(buffer, MAXPATHLEN) == NULL) {
1291 if (errno == ERANGE) {
1292 interp->result = "working directory name is too long";
1294 Tcl_AppendResult(interp,
1295 "error getting working directory name: ",
1296 Tcl_UnixError(interp), (char *) NULL);
1300 currentDir = (char *) ckalloc((unsigned) (strlen(buffer) + 1));
1301 strcpy(currentDir, buffer);
1303 interp->result = currentDir;
1308 *----------------------------------------------------------------------
1312 * This procedure is invoked to process the "puts" Tcl command.
1313 * See the user documentation for details on what it does.
1316 * A standard Tcl result.
1319 * See the user documentation.
1321 *----------------------------------------------------------------------
1326 Tcl_PutsCmd(dummy, interp, argc, argv)
1327 ClientData dummy; /* Not used. */
1328 Tcl_Interp *interp; /* Current interpreter. */
1329 int argc; /* Number of arguments. */
1330 char **argv; /* Argument strings. */
1339 if ((argc >= 2) && (strcmp(argv[1], "-nonewline") == 0)) {
1343 if ((i < (argc-3)) || (i >= argc)) {
1344 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
1345 "\" ?-nonewline? ?fileId? string", (char *) NULL);
1350 * The code below provides backwards compatibility with an old
1351 * form of the command that is no longer recommended or documented.
1354 if (i == (argc-3)) {
1355 if (strncmp(argv[i+2], "nonewline", strlen(argv[i+2])) != 0) {
1356 Tcl_AppendResult(interp, "bad argument \"", argv[i+2],
1357 "\": should be \"nonewline\"", (char *) NULL);
1362 if (i == (argc-1)) {
1369 if (TclGetOpenFile(interp, fileId, &filePtr) != TCL_OK) {
1372 if (!filePtr->writable) {
1373 Tcl_AppendResult(interp, "\"", fileId,
1374 "\" wasn't opened for writing", (char *) NULL);
1387 Tcl_AppendResult(interp, "error writing \"", fileId,
1388 "\": ", Tcl_UnixError(interp), (char *) NULL);
1396 *----------------------------------------------------------------------
1400 * This procedure is invoked to process the "read" Tcl command.
1401 * See the user documentation for details on what it does.
1404 * A standard Tcl result.
1407 * See the user documentation.
1409 *----------------------------------------------------------------------
1414 Tcl_ReadCmd(dummy, interp, argc, argv)
1415 ClientData dummy; /* Not used. */
1416 Tcl_Interp *interp; /* Current interpreter. */
1417 int argc; /* Number of arguments. */
1418 char **argv; /* Argument strings. */
1421 int bytesLeft, bytesRead, count;
1422 #define READ_BUF_SIZE 4096
1423 char buffer[READ_BUF_SIZE+1];
1426 if ((argc != 2) && (argc != 3)) {
1427 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
1428 " fileId ?numBytes?\" or \"", argv[0],
1429 " ?-nonewline? fileId\"", (char *) NULL);
1434 if ((argc == 3) && (strcmp(argv[1], "-nonewline") == 0)) {
1439 if (TclGetOpenFile(interp, argv[i], &filePtr) != TCL_OK) {
1442 if (!filePtr->readable) {
1443 Tcl_AppendResult(interp, "\"", argv[i],
1444 "\" wasn't opened for reading", (char *) NULL);
1449 * Compute how many bytes to read, and see whether the final
1450 * newline should be dropped.
1453 if ((argc >= (i + 2)) && isdigit(argv[i+1][0])) {
1454 if (Tcl_GetInt(interp, argv[i+1], &bytesLeft) != TCL_OK) {
1461 * The code below provides backward compatibility for an
1462 * archaic earlier version of this command.
1465 if (argc >= (i + 2)) {
1466 if (strncmp(argv[i+1], "nonewline", strlen(argv[i+1])) == 0) {
1469 Tcl_AppendResult(interp, "bad argument \"", argv[i+1],
1470 "\": should be \"nonewline\"", (char *) NULL);
1477 * Read the file in one or more chunks.
1481 while (bytesLeft > 0) {
1482 count = READ_BUF_SIZE;
1483 if (bytesLeft < READ_BUF_SIZE) {
1486 count = fread(buffer, 1, count, filePtr->f);
1487 if (ferror(filePtr->f)) {
1488 Tcl_ResetResult(interp);
1489 Tcl_AppendResult(interp, "error reading \"", argv[i],
1490 "\": ", Tcl_UnixError(interp), (char *) NULL);
1491 clearerr(filePtr->f);
1498 Tcl_AppendResult(interp, buffer, (char *) NULL);
1502 if ((newline == 0) && (bytesRead > 0)
1503 && (interp->result[bytesRead-1] == '\n')) {
1504 interp->result[bytesRead-1] = 0;
1510 *----------------------------------------------------------------------
1514 * This procedure is invoked to process the "seek" Tcl command.
1515 * See the user documentation for details on what it does.
1518 * A standard Tcl result.
1521 * See the user documentation.
1523 *----------------------------------------------------------------------
1528 Tcl_SeekCmd(notUsed, interp, argc, argv)
1529 ClientData notUsed; /* Not used. */
1530 Tcl_Interp *interp; /* Current interpreter. */
1531 int argc; /* Number of arguments. */
1532 char **argv; /* Argument strings. */
1537 if ((argc != 3) && (argc != 4)) {
1538 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
1539 " fileId offset ?origin?\"", (char *) NULL);
1542 if (TclGetOpenFile(interp, argv[1], &filePtr) != TCL_OK) {
1545 if (Tcl_GetInt(interp, argv[2], &offset) != TCL_OK) {
1553 length = strlen(argv[3]);
1555 if ((c == 's') && (strncmp(argv[3], "start", length) == 0)) {
1557 } else if ((c == 'c') && (strncmp(argv[3], "current", length) == 0)) {
1559 } else if ((c == 'e') && (strncmp(argv[3], "end", length) == 0)) {
1562 Tcl_AppendResult(interp, "bad origin \"", argv[3],
1563 "\": should be start, current, or end", (char *) NULL);
1567 if (fseek(filePtr->f, (long) offset, mode) == -1) {
1568 Tcl_AppendResult(interp, "error during seek: ",
1569 Tcl_UnixError(interp), (char *) NULL);
1570 clearerr(filePtr->f);
1578 *----------------------------------------------------------------------
1582 * This procedure is invoked to process the "source" Tcl command.
1583 * See the user documentation for details on what it does.
1586 * A standard Tcl result.
1589 * See the user documentation.
1591 *----------------------------------------------------------------------
1596 Tcl_SourceCmd(dummy, interp, argc, argv)
1597 ClientData dummy; /* Not used. */
1598 Tcl_Interp *interp; /* Current interpreter. */
1599 int argc; /* Number of arguments. */
1600 char **argv; /* Argument strings. */
1603 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
1604 " fileName\"", (char *) NULL);
1607 return Tcl_EvalFile(interp, argv[1]);
1611 *----------------------------------------------------------------------
1615 * This procedure is invoked to process the "tell" Tcl command.
1616 * See the user documentation for details on what it does.
1619 * A standard Tcl result.
1622 * See the user documentation.
1624 *----------------------------------------------------------------------
1629 Tcl_TellCmd(notUsed, interp, argc, argv)
1630 ClientData notUsed; /* Not used. */
1631 Tcl_Interp *interp; /* Current interpreter. */
1632 int argc; /* Number of arguments. */
1633 char **argv; /* Argument strings. */
1638 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
1639 " fileId\"", (char *) NULL);
1642 if (TclGetOpenFile(interp, argv[1], &filePtr) != TCL_OK) {
1645 sprintf(interp->result, "%ld", ftell(filePtr->f));
1650 *----------------------------------------------------------------------
1654 * This procedure is invoked to process the "time" Tcl command.
1655 * See the user documentation for details on what it does.
1658 * A standard Tcl result.
1661 * See the user documentation.
1663 *----------------------------------------------------------------------
1668 Tcl_TimeCmd(dummy, interp, argc, argv)
1669 ClientData dummy; /* Not used. */
1670 Tcl_Interp *interp; /* Current interpreter. */
1671 int argc; /* Number of arguments. */
1672 char **argv; /* Argument strings. */
1674 int count, i, result;
1677 struct timeval start, stop;
1687 } else if (argc == 3) {
1688 if (Tcl_GetInt(interp, argv[2], &count) != TCL_OK) {
1692 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
1693 " command ?count?\"", (char *) NULL);
1697 gettimeofday(&start, &tz);
1699 start = times(&dummy2);
1701 for (i = count ; i > 0; i--) {
1702 result = Tcl_Eval(interp, argv[1], 0, (char **) NULL);
1703 if (result != TCL_OK) {
1704 if (result == TCL_ERROR) {
1706 sprintf(msg, "\n (\"time\" body line %d)",
1708 Tcl_AddErrorInfo(interp, msg);
1714 gettimeofday(&stop, &tz);
1715 micros = (stop.tv_sec - start.tv_sec)*1000000
1716 + (stop.tv_usec - start.tv_usec);
1719 stop = times(&dummy2);
1720 timePer = (((double) (stop - start))*1000000.0)/CLK_TCK;
1722 Tcl_ResetResult(interp);
1723 sprintf(interp->result, "%.0f microseconds per iteration", timePer/count);
1729 *----------------------------------------------------------------------
1731 * CleanupChildren --
1733 * This is a utility procedure used to wait for child processes
1734 * to exit, record information about abnormal exits, and then
1735 * collect any stderr output generated by them.
1738 * The return value is a standard Tcl result. If anything at
1739 * weird happened with the child processes, TCL_ERROR is returned
1740 * and a message is left in interp->result.
1743 * If the last character of interp->result is a newline, then it
1744 * is removed. File errorId gets closed, and pidPtr is freed
1745 * back to the storage allocator.
1747 *----------------------------------------------------------------------
1751 CleanupChildren(interp, numPids, pidPtr, errorId)
1752 Tcl_Interp *interp; /* Used for error messages. */
1753 int numPids; /* Number of entries in pidPtr array. */
1754 int *pidPtr; /* Array of process ids of children. */
1755 int errorId; /* File descriptor index for file containing
1756 * stderr output from pipeline. -1 means
1757 * there isn't any stderr output. */
1759 int result = TCL_OK;
1761 #define WAIT_STATUS_TYPE int
1762 WAIT_STATUS_TYPE waitStatus;
1764 for (i = 0; i < numPids; i++) {
1765 pid = Tcl_WaitPids(1, &pidPtr[i], (int *) &waitStatus);
1767 /* This can happen if the process was already reaped, so just ignore it */
1769 Tcl_AppendResult(interp, "error waiting for process to exit: ",
1770 Tcl_UnixError(interp), (char *) NULL);
1776 * Create error messages for unusual process exits. An
1777 * extra newline gets appended to each error message, but
1778 * it gets removed below (in the same fashion that an
1779 * extra newline in the command's output is removed).
1782 if (!WIFEXITED(waitStatus) || (WEXITSTATUS(waitStatus) != 0)) {
1783 char msg1[20], msg2[20];
1786 sprintf(msg1, "%d", pid);
1787 if (WIFEXITED(waitStatus)) {
1788 sprintf(msg2, "%d", WEXITSTATUS(waitStatus));
1789 Tcl_SetErrorCode(interp, "CHILDSTATUS", msg1, msg2,
1791 } else if (WIFSIGNALED(waitStatus)) {
1794 p = Tcl_SignalMsg((int) (WTERMSIG(waitStatus)));
1795 Tcl_SetErrorCode(interp, "CHILDKILLED", msg1,
1796 Tcl_SignalId((int) (WTERMSIG(waitStatus))), p,
1798 Tcl_AppendResult(interp, "child killed: ", p, "\n",
1800 } else if (WIFSTOPPED(waitStatus)) {
1803 p = Tcl_SignalMsg((int) (WSTOPSIG(waitStatus)));
1804 Tcl_SetErrorCode(interp, "CHILDSUSP", msg1,
1805 Tcl_SignalId((int) (WSTOPSIG(waitStatus))), p, (char *) NULL);
1806 Tcl_AppendResult(interp, "child suspended: ", p, "\n",
1809 Tcl_AppendResult(interp,
1810 "child wait status didn't make sense\n",
1815 ckfree((char *) pidPtr);
1818 * Read the standard error file. If there's anything there,
1819 * then return an error and add the file's contents to the result
1825 # define BUFFER_SIZE 1000
1826 char buffer[BUFFER_SIZE+1];
1829 count = read(errorId, buffer, BUFFER_SIZE);
1835 Tcl_AppendResult(interp,
1836 "error reading stderr output file: ",
1837 Tcl_UnixError(interp), (char *) NULL);
1841 Tcl_AppendResult(interp, buffer, (char *) NULL);
1847 * If the last character of interp->result is a newline, then remove
1848 * the newline character (the newline would just confuse things).
1851 length = strlen(interp->result);
1852 if ((length > 0) && (interp->result[length-1] == '\n')) {
1853 interp->result[length-1] = '\0';
1858 #endif /* NO_FORK */
1861 *-----------------------------------------------------------------------------
1864 * Implements the pid TCL command:
1868 * Standard TCL result.
1869 *-----------------------------------------------------------------------------
1872 Tcl_PidCmd (clientData, interp, argc, argv)
1873 ClientData clientData;
1881 Tcl_AppendResult (interp, "bad # args: ", argv[0], (char *) NULL);
1885 sprintf(buf, "%d", getpid());
1887 Tcl_AppendResult (interp, buf, (char *) NULL);