/*1* tclUnixFile.c --2*3* This file contains wrappers around UNIX file handling functions.4* These wrappers mask differences between Windows and UNIX.5*6* Copyright (c) 1995 Sun Microsystems, Inc.7*8* See the file "license.terms" for information on usage and redistribution9* of this file, and for a DISCLAIMER OF ALL WARRANTIES.10*11* SCCS: @(#) tclUnixFile.c 1.41 96/12/05 14:59:2012*/1314#include "tclInt.h"15#include "tclPort.h"1617/*18* The variable below caches the name of the current working directory19* in order to avoid repeated calls to getcwd. The string is malloc-ed.20* NULL means the cache needs to be refreshed.21*/2223static char *currentDir = NULL;24static int currentDirExitHandlerSet = 0;2526/*27* The variable below is set if the exit routine for deleting the string28* containing the executable name has been registered.29*/3031static int executableNameExitHandlerSet = 0;3233extern pid_t waitpid _ANSI_ARGS_((pid_t pid, int *stat_loc, int options));3435/*36* Static routines for this file:37*/3839static void FreeCurrentDir _ANSI_ARGS_((ClientData clientData));40static void FreeExecutableName _ANSI_ARGS_((ClientData clientData));4142/*43*----------------------------------------------------------------------44*45* Tcl_WaitPid --46*47* Implements the waitpid system call on Unix systems.48*49* Results:50* Result of calling waitpid.51*52* Side effects:53* Waits for a process to terminate.54*55*----------------------------------------------------------------------56*/5758int59Tcl_WaitPid(pid, statPtr, options)60int pid;61int *statPtr;62int options;63{64int result;65pid_t real_pid;6667real_pid = (pid_t) pid;68while (1) {69result = (int) waitpid(real_pid, statPtr, options);70if ((result != -1) || (errno != EINTR)) {71return result;72}73}74}7576/*77*----------------------------------------------------------------------78*79* FreeCurrentDir --80*81* Frees the string stored in the currentDir variable. This routine82* is registered as an exit handler and will be called during shutdown.83*84* Results:85* None.86*87* Side effects:88* Frees the memory occuppied by the currentDir value.89*90*----------------------------------------------------------------------91*/9293/* ARGSUSED */94static void95FreeCurrentDir(clientData)96ClientData clientData; /* Not used. */97{98if (currentDir != (char *) NULL) {99ckfree(currentDir);100currentDir = (char *) NULL;101}102}103104/*105*----------------------------------------------------------------------106*107* FreeExecutableName --108*109* Frees the string stored in the tclExecutableName variable. This110* routine is registered as an exit handler and will be called111* during shutdown.112*113* Results:114* None.115*116* Side effects:117* Frees the memory occuppied by the tclExecutableName value.118*119*----------------------------------------------------------------------120*/121122/* ARGSUSED */123static void124FreeExecutableName(clientData)125ClientData clientData; /* Not used. */126{127if (tclExecutableName != (char *) NULL) {128ckfree(tclExecutableName);129tclExecutableName = (char *) NULL;130}131}132133/*134*----------------------------------------------------------------------135*136* TclChdir --137*138* Change the current working directory.139*140* Results:141* The result is a standard Tcl result. If an error occurs and142* interp isn't NULL, an error message is left in interp->result.143*144* Side effects:145* The working directory for this application is changed. Also146* the cache maintained used by TclGetCwd is deallocated and147* set to NULL.148*149*----------------------------------------------------------------------150*/151152int153TclChdir(interp, dirName)154Tcl_Interp *interp; /* If non NULL, used for error reporting. */155char *dirName; /* Path to new working directory. */156{157if (currentDir != NULL) {158ckfree(currentDir);159currentDir = NULL;160}161if (chdir(dirName) != 0) {162if (interp != NULL) {163Tcl_AppendResult(interp, "couldn't change working directory to \"",164dirName, "\": ", Tcl_PosixError(interp), (char *) NULL);165}166return TCL_ERROR;167}168return TCL_OK;169}170171/*172*----------------------------------------------------------------------173*174* TclGetCwd --175*176* Return the path name of the current working directory.177*178* Results:179* The result is the full path name of the current working180* directory, or NULL if an error occurred while figuring it out.181* The returned string is owned by the TclGetCwd routine and must182* not be freed by the caller. If an error occurs and interp183* isn't NULL, an error message is left in interp->result.184*185* Side effects:186* The path name is cached to avoid having to recompute it187* on future calls; if it is already cached, the cached188* value is returned.189*190*----------------------------------------------------------------------191*/192193char *194TclGetCwd(interp)195Tcl_Interp *interp; /* If non NULL, used for error reporting. */196{197char buffer[MAXPATHLEN+1];198199if (currentDir == NULL) {200if (!currentDirExitHandlerSet) {201currentDirExitHandlerSet = 1;202Tcl_CreateExitHandler(FreeCurrentDir, (ClientData) NULL);203}204if (getcwd(buffer, MAXPATHLEN+1) == NULL) {205if (interp != NULL) {206if (errno == ERANGE) {207interp->result = "working directory name is too long";208} else {209Tcl_AppendResult(interp,210"error getting working directory name: ",211Tcl_PosixError(interp), (char *) NULL);212}213}214return NULL;215}216currentDir = (char *) ckalloc((unsigned) (strlen(buffer) + 1));217strcpy(currentDir, buffer);218}219return currentDir;220}221222/*223*----------------------------------------------------------------------224*225* TclOpenFile --226*227* Implements a mechanism to open files on Unix systems.228*229* Results:230* The opened file.231*232* Side effects:233* May cause a file to be created on the file system.234*235*----------------------------------------------------------------------236*/237238Tcl_File239TclOpenFile(fname, mode)240char *fname; /* The name of the file to open. */241int mode; /* In what mode to open the file? */242{243int fd;244245fd = open(fname, mode, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);246if (fd != -1) {247fcntl(fd, F_SETFD, FD_CLOEXEC);248return Tcl_GetFile((ClientData)fd, TCL_UNIX_FD);249}250return NULL;251}252253/*254*----------------------------------------------------------------------255*256* TclCloseFile --257*258* Implements a mechanism to close a UNIX file.259*260* Results:261* Returns 0 on success, or -1 on error, setting errno.262*263* Side effects:264* The file is closed.265*266*----------------------------------------------------------------------267*/268269int270TclCloseFile(file)271Tcl_File file; /* The file to close. */272{273int type;274int fd;275int result;276277fd = (int) Tcl_GetFileInfo(file, &type);278if (type != TCL_UNIX_FD) {279panic("Tcl_CloseFile: unexpected file type");280}281282/*283* Refuse to close the fds for stdin, stdout and stderr.284*/285286if ((fd == 0) || (fd == 1) || (fd == 2)) {287return 0;288}289290result = close(fd);291Tcl_DeleteFileHandler(file);292Tcl_FreeFile(file);293return result;294}295296/*297*----------------------------------------------------------------------298*299* TclReadFile --300*301* Implements a mechanism to read from files on Unix systems. Also302* simulates blocking behavior on non-blocking files when asked to.303*304* Results:305* The number of characters read from the specified file.306*307* Side effects:308* May consume characters from the file.309*310*----------------------------------------------------------------------311*/312/* ARGSUSED */313int314TclReadFile(file, shouldBlock, buf, toRead)315Tcl_File file; /* The file to read from. */316int shouldBlock; /* Not used. */317char *buf; /* The buffer to store input in. */318int toRead; /* Number of characters to read. */319{320int type, fd;321322fd = (int) Tcl_GetFileInfo(file, &type);323if (type != TCL_UNIX_FD) {324panic("Tcl_ReadFile: unexpected file type");325}326327return read(fd, buf, (size_t) toRead);328}329330/*331*----------------------------------------------------------------------332*333* TclWriteFile --334*335* Implements a mechanism to write to files on Unix systems.336*337* Results:338* The number of characters written to the specified file.339*340* Side effects:341* May produce characters on the file.342*343*----------------------------------------------------------------------344*/345346/* ARGSUSED */347int348TclWriteFile(file, shouldBlock, buf, toWrite)349Tcl_File file; /* The file to write to. */350int shouldBlock; /* Not used. */351char *buf; /* Where output is stored. */352int toWrite; /* Number of characters to write. */353{354int type, fd;355356fd = (int) Tcl_GetFileInfo(file, &type);357if (type != TCL_UNIX_FD) {358panic("Tcl_WriteFile: unexpected file type");359}360return write(fd, buf, (size_t) toWrite);361}362363/*364*----------------------------------------------------------------------365*366* TclSeekFile --367*368* Sets the file pointer on the indicated UNIX file.369*370* Results:371* The new position at which the file will be accessed, or -1 on372* failure.373*374* Side effects:375* May change the position at which subsequent operations access the376* file designated by the file.377*378*----------------------------------------------------------------------379*/380381int382TclSeekFile(file, offset, whence)383Tcl_File file; /* The file to seek on. */384int offset; /* How far to seek? */385int whence; /* And from where to seek? */386{387int type, fd;388389fd = (int) Tcl_GetFileInfo(file, &type);390if (type != TCL_UNIX_FD) {391panic("Tcl_SeekFile: unexpected file type");392}393394return lseek(fd, offset, whence);395}396397/*398*----------------------------------------------------------------------399*400* TclCreateTempFile --401*402* This function creates a temporary file initialized with an403* optional string, and returns a file handle with the file pointer404* at the beginning of the file.405*406* Results:407* A handle to a file.408*409* Side effects:410* None.411*412*----------------------------------------------------------------------413*/414415Tcl_File416#ifdef TKSH_V5417TclCreateTempFile(contents)418#else419TclCreateTempFile(contents, namePtr)420#endif421char *contents; /* String to write into temp file, or NULL. */422#ifndef TKSH_V5423Tcl_DString *namePtr; /* If non-NULL, pointer to initialized424* DString that is filled with the name of425* the temp file that was created. */426#endif427{428char fileName[L_tmpnam];429Tcl_File file;430size_t length = (contents == NULL) ? 0 : strlen(contents);431432tmpnam(fileName);433file = TclOpenFile(fileName, O_RDWR|O_CREAT|O_TRUNC);434unlink(fileName);435436if ((file != NULL) && (length > 0)) {437int fd = (int)Tcl_GetFileInfo(file, NULL);438while (1) {439if (write(fd, contents, length) != -1) {440break;441} else if (errno != EINTR) {442close(fd);443Tcl_FreeFile(file);444return NULL;445}446}447lseek(fd, 0, SEEK_SET);448}449#ifndef TKSH_V5450if (namePtr != NULL) {451Tcl_DStringAppend(namePtr, fileName, -1);452}453#endif454return file;455}456457/*458*----------------------------------------------------------------------459*460* Tcl_FindExecutable --461*462* This procedure computes the absolute path name of the current463* application, given its argv[0] value.464*465* Results:466* None.467*468* Side effects:469* The variable tclExecutableName gets filled in with the file470* name for the application, if we figured it out. If we couldn't471* figure it out, Tcl_FindExecutable is set to NULL.472*473*----------------------------------------------------------------------474*/475476void477Tcl_FindExecutable(argv0)478char *argv0; /* The value of the application's argv[0]. */479{480char *name, *p, *cwd;481Tcl_DString buffer;482int length;483484Tcl_DStringInit(&buffer);485if (tclExecutableName != NULL) {486ckfree(tclExecutableName);487tclExecutableName = NULL;488}489490name = argv0;491for (p = name; *p != 0; p++) {492if (*p == '/') {493/*494* The name contains a slash, so use the name directly495* without doing a path search.496*/497498goto gotName;499}500}501502p = getenv("PATH");503if (p == NULL) {504/*505* There's no PATH environment variable; use the default that506* is used by sh.507*/508509p = ":/bin:/usr/bin";510}511512/*513* Search through all the directories named in the PATH variable514* to see if argv[0] is in one of them. If so, use that file515* name.516*/517518while (*p != 0) {519while (isspace(UCHAR(*p))) {520p++;521}522name = p;523while ((*p != ':') && (*p != 0)) {524p++;525}526Tcl_DStringSetLength(&buffer, 0);527if (p != name) {528Tcl_DStringAppend(&buffer, name, p-name);529if (p[-1] != '/') {530Tcl_DStringAppend(&buffer, "/", 1);531}532}533Tcl_DStringAppend(&buffer, argv0, -1);534if (access(Tcl_DStringValue(&buffer), X_OK) == 0) {535name = Tcl_DStringValue(&buffer);536goto gotName;537}538p++;539}540goto done;541542/*543* If the name starts with "/" then just copy it to tclExecutableName.544*/545546gotName:547if (name[0] == '/') {548tclExecutableName = (char *) ckalloc((unsigned) (strlen(name) + 1));549strcpy(tclExecutableName, name);550goto done;551}552553/*554* The name is relative to the current working directory. First555* strip off a leading "./", if any, then add the full path name of556* the current working directory.557*/558559if ((name[0] == '.') && (name[1] == '/')) {560name += 2;561}562cwd = TclGetCwd((Tcl_Interp *) NULL);563if (cwd == NULL) {564tclExecutableName = NULL;565goto done;566}567length = strlen(cwd);568tclExecutableName = (char *) ckalloc((unsigned)569(length + strlen(name) + 2));570strcpy(tclExecutableName, cwd);571tclExecutableName[length] = '/';572strcpy(tclExecutableName + length + 1, name);573574done:575Tcl_DStringFree(&buffer);576577if (!executableNameExitHandlerSet) {578executableNameExitHandlerSet = 1;579Tcl_CreateExitHandler(FreeExecutableName, (ClientData) NULL);580}581}582583/*584*----------------------------------------------------------------------585*586* TclGetUserHome --587*588* This function takes the passed in user name and finds the589* corresponding home directory specified in the password file.590*591* Results:592* The result is a pointer to a static string containing593* the new name. If there was an error in processing the594* user name then the return value is NULL. Otherwise the595* result is stored in bufferPtr, and the caller must call596* Tcl_DStringFree(bufferPtr) to free the result.597*598* Side effects:599* Information may be left in bufferPtr.600*601*----------------------------------------------------------------------602*/603604char *605TclGetUserHome(name, bufferPtr)606char *name; /* User name to use to find home directory. */607Tcl_DString *bufferPtr; /* May be used to hold result. Must not hold608* anything at the time of the call, and need609* not even be initialized. */610{611struct passwd *pwPtr;612613pwPtr = getpwnam(name);614if (pwPtr == NULL) {615endpwent();616return NULL;617}618Tcl_DStringInit(bufferPtr);619Tcl_DStringAppend(bufferPtr, pwPtr->pw_dir, -1);620endpwent();621return bufferPtr->string;622}623624#if 0625/*626*----------------------------------------------------------------------627*628* TclMatchFiles --629*630* This routine is used by the globbing code to search a631* directory for all files which match a given pattern.632*633* Results:634* If the tail argument is NULL, then the matching files are635* added to the interp->result. Otherwise, TclDoGlob is called636* recursively for each matching subdirectory. The return value637* is a standard Tcl result indicating whether an error occurred638* in globbing.639*640* Side effects:641* None.642*643*---------------------------------------------------------------------- */644645int646TclMatchFiles(interp, separators, dirPtr, pattern, tail)647Tcl_Interp *interp; /* Interpreter to receive results. */648char *separators; /* Path separators to pass to TclDoGlob. */649Tcl_DString *dirPtr; /* Contains path to directory to search. */650char *pattern; /* Pattern to match against. */651char *tail; /* Pointer to end of pattern. */652{653char *dirName, *patternEnd = tail;654char savedChar = 0; /* Initialization needed only to prevent655* compiler warning from gcc. */656DIR *d;657struct stat statBuf;658struct dirent *entryPtr;659int matchHidden;660int result = TCL_OK;661int baseLength = Tcl_DStringLength(dirPtr);662663/*664* Make sure that the directory part of the name really is a665* directory. If the directory name is "", use the name "."666* instead, because some UNIX systems don't treat "" like "."667* automatically. Keep the "" for use in generating file names,668* otherwise "glob foo.c" would return "./foo.c".669*/670671if (dirPtr->string[0] == '\0') {672dirName = ".";673} else {674dirName = dirPtr->string;675}676if ((stat(dirName, &statBuf) != 0) || !S_ISDIR(statBuf.st_mode)) {677return TCL_OK;678}679680/*681* Check to see if the pattern needs to compare with hidden files.682*/683684if ((pattern[0] == '.')685|| ((pattern[0] == '\\') && (pattern[1] == '.'))) {686matchHidden = 1;687} else {688matchHidden = 0;689}690691/*692* Now open the directory for reading and iterate over the contents.693*/694695d = opendir(dirName);696if (d == NULL) {697Tcl_ResetResult(interp);698699/*700* Strip off a trailing '/' if necessary, before reporting the error.701*/702703if (baseLength > 0) {704savedChar = dirPtr->string[baseLength-1];705if (savedChar == '/') {706dirPtr->string[baseLength-1] = '\0';707}708}709Tcl_AppendResult(interp, "couldn't read directory \"",710dirPtr->string, "\": ", Tcl_PosixError(interp), (char *) NULL);711if (baseLength > 0) {712dirPtr->string[baseLength-1] = savedChar;713}714return TCL_ERROR;715}716717/*718* Clean up the end of the pattern and the tail pointer. Leave719* the tail pointing to the first character after the path separator720* following the pattern, or NULL. Also, ensure that the pattern721* is null-terminated.722*/723724if (*tail == '\\') {725tail++;726}727if (*tail == '\0') {728tail = NULL;729} else {730tail++;731}732savedChar = *patternEnd;733*patternEnd = '\0';734735while (1) {736entryPtr = readdir(d);737if (entryPtr == NULL) {738break;739}740741/*742* Don't match names starting with "." unless the "." is743* present in the pattern.744*/745746if (!matchHidden && (*entryPtr->d_name == '.')) {747continue;748}749750/*751* Now check to see if the file matches. If there are more752* characters to be processed, then ensure matching files are753* directories before calling TclDoGlob. Otherwise, just add754* the file to the result.755*/756757if (Tcl_StringMatch(entryPtr->d_name, pattern)) {758Tcl_DStringSetLength(dirPtr, baseLength);759Tcl_DStringAppend(dirPtr, entryPtr->d_name, -1);760if (tail == NULL) {761Tcl_AppendElement(interp, dirPtr->string);762} else if ((stat(dirPtr->string, &statBuf) == 0)763&& S_ISDIR(statBuf.st_mode)) {764Tcl_DStringAppend(dirPtr, "/", 1);765result = TclDoGlob(interp, separators, dirPtr, tail);766if (result != TCL_OK) {767break;768}769}770}771}772*patternEnd = savedChar;773774closedir(d);775return result;776}777#endif778779780