home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: 10 Tools
/
10-Tools.zip
/
tcltk805.zip
/
tcl805s.zip
/
tcl8.0.5
/
os2
/
tclOS2File.c
< prev
next >
Wrap
C/C++ Source or Header
|
2001-02-09
|
24KB
|
778 lines
/*
* tclOS2File.c --
*
* This file contains temporary wrappers around UNIX file handling
* functions. These wrappers map the UNIX functions to OS/2 HFILE-style
* files, which can be manipulated through the OS/2 console redirection
* interfaces.
*
* Copyright (c) 1995-1996 Sun Microsystems, Inc.
* Copyright (c) 1996-2001 Illya Vaes
*
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*/
#include "tclOS2Int.h"
/*
* The variable below caches the name of the current working directory
* in order to avoid repeated calls to getcwd. The string is malloc-ed.
* NULL means the cache needs to be refreshed.
*/
static char *currentDir = NULL;
/*
* Mapping of drive numbers to drive letters
*/
static char drives[] = {'0', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
'V', 'W', 'X', 'Y', 'Z'};
/*
*----------------------------------------------------------------------
*
* Tcl_FindExecutable --
*
* This procedure computes the absolute path name of the current
* application, given its argv[0] value.
*
* Results:
* None.
*
* Side effects:
* The variable tclExecutableName gets filled in with the file
* name for the application, if we figured it out. If we couldn't
* figure it out, Tcl_FindExecutable is set to NULL.
*
*----------------------------------------------------------------------
*/
void
Tcl_FindExecutable(argv0)
char *argv0; /* The value of the application's argv[0]. */
{
char *p;
if (tclExecutableName != NULL) {
ckfree(tclExecutableName);
tclExecutableName = NULL;
}
tclExecutableName = (char *) ckalloc((unsigned) (strlen(argv0) + 1));
strcpy(tclExecutableName, argv0);
/* Convert backslahes to slashes */
for (p= tclExecutableName; *p != '\0'; p++) {
if (*p == '\\') *p = '/';
}
}
/*
*----------------------------------------------------------------------
*
* TclMatchFiles --
*
* This routine is used by the globbing code to search a
* directory for all files which match a given pattern.
*
* Results:
* If the tail argument is NULL, then the matching files are
* added to the interp->result. Otherwise, TclDoGlob is called
* recursively for each matching subdirectory. The return value
* is a standard Tcl result indicating whether an error occurred
* in globbing.
*
* Side effects:
* None.
*
*---------------------------------------------------------------------- */
int
TclMatchFiles(interp, separators, dirPtr, pattern, tail)
Tcl_Interp *interp; /* Interpreter to receive results. */
char *separators; /* Directory separators to pass to TclDoGlob. */
Tcl_DString *dirPtr; /* Contains path to directory to search. */
char *pattern; /* Pattern to match against. */
char *tail; /* Pointer to end of pattern. Tail must
* point to a location in pattern. */
{
char drivePattern[4] = "?:\\";
char *newPattern, *p, *dir, *root, c;
int length, matchDotFiles;
int result = TCL_OK;
int baseLength = Tcl_DStringLength(dirPtr);
Tcl_DString buffer;
ULONG volFlags;
HDIR handle;
FILESTATUS3 infoBuf;
FILEFINDBUF3 data;
ULONG filesAtATime = 1;
ULONG diskNum = 3; /* Assume C: for errors */
BYTE fsBuf[1024]; /* Info about file system */
ULONG bufSize;
#ifdef VERBOSE
printf("TclMatchFiles path [%s], pat [%s]\n", Tcl_DStringValue(dirPtr),
pattern);
#endif
/*
* Convert the path to normalized form since some interfaces only
* accept backslashes. Also, ensure that the directory ends with a
* separator character.
*/
Tcl_DStringInit(&buffer);
if (baseLength == 0) {
Tcl_DStringAppend(&buffer, ".", 1);
} else {
Tcl_DStringAppend(&buffer, Tcl_DStringValue(dirPtr),
Tcl_DStringLength(dirPtr));
}
for (p = Tcl_DStringValue(&buffer); *p != '\0'; p++) {
if (*p == '/') {
*p = '\\';
}
}
/*
p--;
if (*p != '\\' && (strcmp(Tcl_DStringValue(&buffer), ".") != 0)) {
Tcl_DStringAppend(&buffer, "\\", 1);
p++;
}
*/
p--;
/*
* DosQueryPathInfo can only handle a trailing (back)slash for the root
* of a drive, so cut it off in other case.
*/
if ((*p == '\\') && (*(p-1) != ':') && (*p != '.')) {
Tcl_DStringSetLength(&buffer, Tcl_DStringLength(&buffer)-1);
p--;
}
/*
* In cases of eg. "c:filespec", we need to put the current dir for that
* disk after the drive specification.
*/
if (*p == ':') {
char wd[256];
ULONG len = 256;
ULONG drive;
if (*(p-1) > 'Z') drive = *(p-1) - 'a' + 1;
else drive = *(p-1) - 'A' + 1;
rc = DosQueryCurrentDir(drive, (PBYTE)wd, &len);
#ifdef VERBOSE
printf("DosQueryCurrentDir drive %c (%d) returns %d [%s] (len %d)\n",
*(p-1), drive, rc, wd, len);
#endif
if (rc == NO_ERROR) {
Tcl_DStringAppend(&buffer, "\\", 1);
len = strlen(wd);
Tcl_DStringAppend(&buffer, wd, len);
p += len+1;
}
#ifdef VERBOSE
printf(" *p now %c\n", *p);
#endif
}
/*
* First verify that the specified path is actually a directory.
*/
dir = Tcl_DStringValue(&buffer);
rc = DosQueryPathInfo(dir, FIL_STANDARD, &infoBuf, sizeof(infoBuf));
#ifdef VERBOSE
printf("DosQueryPathInfo [%s] returned [%d]\n", dir, rc);
fflush(stdout);
#endif
if ( (rc != NO_ERROR) || ((infoBuf.attrFile & FILE_DIRECTORY) == 0)) {
Tcl_DStringFree(&buffer);
return TCL_OK;
}
if (*p != '\\') {
Tcl_DStringAppend(&buffer, "\\", 1);
}
dir = Tcl_DStringValue(&buffer);
/*
* Next check the volume information for the directory to see whether
* comparisons should be case sensitive or not. If the root is null, then
* we use the root of the current directory. If the root is just a drive
* specifier, we use the root directory of the given drive.
* There's no API for determining case sensitivity and preservation (that
* I've found) perse. We can determine the File System Driver though, and
* assume correct values for some file systems we know, eg. FAT, HPFS,
* NTFS, ext2fs.
*/
switch (Tcl_GetPathType(dir)) {
case TCL_PATH_RELATIVE: {
ULONG logical;
/* Determine current drive */
DosQueryCurrentDisk(&diskNum, &logical);
#ifdef VERBOSE
printf("TCL_PATH_RELATIVE, disk %d\n", diskNum);
#endif
break;
}
case TCL_PATH_VOLUME_RELATIVE: {
ULONG logical;
/* Determine current drive */
DosQueryCurrentDisk(&diskNum, &logical);
#ifdef VERBOSE
printf("TCL_PATH_VOLUME_RELATIVE, disk %d\n", diskNum);
#endif
if (*dir == '\\') {
root = NULL;
} else {
root = drivePattern;
*root = *dir;
}
break;
}
case TCL_PATH_ABSOLUTE:
/* Use given drive */
diskNum = (ULONG) dir[0] - 'A' + 1;
if (dir[0] >= 'a') {
diskNum -= ('a' - 'A');
}
#ifdef VERBOSE
printf("TCL_PATH_ABSOLUTE, disk %d\n", diskNum);
#endif
if (dir[1] == ':') {
root = drivePattern;
*root = *dir;
} else if (dir[1] == '\\') {
p = strchr(dir+2, '\\');
p = strchr(p+1, '\\');
p++;
c = *p;
*p = 0;
*p = c;
}
break;
}
/* Now determine file system driver name and hack the case stuff */
bufSize = sizeof(fsBuf);
rc = DosQueryFSAttach(NULL, diskNum, FSAIL_DRVNUMBER, ((PFSQBUFFER2)fsBuf),
&bufSize);
if (rc != NO_ERROR) {
/* Error, assume FAT */
#ifdef VERBOSE
printf("DosQueryFSAttach %d ERROR %d (bufsize %d)\n", diskNum, rc,
bufSize);
#endif
volFlags = 0;
} else {
USHORT cbName = ((PFSQBUFFER2) fsBuf)->cbName;
#ifdef VERBOSE
printf("DosQueryFSAttach %d OK, szN [%s], szFSDN [%s] (bufsize %d)\n",
diskNum, ((PFSQBUFFER2)fsBuf)->szName,
((PFSQBUFFER2)(fsBuf+cbName))->szFSDName, bufSize);
#endif
if (strcmp(((PFSQBUFFER2)(fsBuf+cbName))->szFSDName, "FAT") == 0) {
volFlags = 0;
} else
if (strcmp(((PFSQBUFFER2)(fsBuf+cbName))->szFSDName, "HPFS") == 0) {
volFlags = FS_CASE_IS_PRESERVED;
} else
if (strcmp(((PFSQBUFFER2)(fsBuf+cbName))->szFSDName, "NFS") == 0) {
volFlags = FS_CASE_SENSITIVE | FS_CASE_IS_PRESERVED;
} else
if (strcmp(((PFSQBUFFER2)(fsBuf+cbName))->szFSDName, "EXT2FS") == 0) {
volFlags = FS_CASE_SENSITIVE | FS_CASE_IS_PRESERVED;
} else
if (strcmp(((PFSQBUFFER2)(fsBuf+cbName))->szFSDName, "VINES") == 0) {
volFlags = 0;
} else
if (strcmp(((PFSQBUFFER2)(fsBuf+cbName))->szFSDName, "NTFS") == 0) {
volFlags = FS_CASE_IS_PRESERVED;
} else {
volFlags = 0;
}
}
/*
* If the volume is not case sensitive, then we need to convert the pattern
* to lower case.
*/
length = tail - pattern;
newPattern = ckalloc(length+1);
if (volFlags & FS_CASE_SENSITIVE) {
strncpy(newPattern, pattern, length);
newPattern[length] = '\0';
} else {
char *src, *dest;
for (src = pattern, dest = newPattern; src < tail; src++, dest++) {
*dest = (char) tolower(*src);
}
*dest = '\0';
}
/*
* We need to check all files in the directory, so append a *
* to the path. Not "*.*".
*/
dir = Tcl_DStringAppend(&buffer, "*", 3);
/*
* Now open the directory for reading and iterate over the contents.
*/
handle = HDIR_SYSTEM;
rc = DosFindFirst(dir, &handle, FILE_NORMAL | FILE_DIRECTORY, &data,
sizeof(data), &filesAtATime, FIL_STANDARD);
#ifdef VERBOSE
printf("DosFindFirst %s returns %x (%s)\n", dir, rc, data.achName);
#endif
Tcl_DStringFree(&buffer);
if (rc != NO_ERROR) {
TclOS2ConvertError(rc);
Tcl_ResetResult(interp);
Tcl_AppendResult(interp, "couldn't read directory \"",
dirPtr->string, "\": ", Tcl_PosixError(interp), (char *) NULL);
ckfree(newPattern);
return TCL_ERROR;
}
/*
* Clean up the tail pointer. Leave the tail pointing to the
* first character after the path separator or NULL.
*/
if (*tail == '\\') {
tail++;
}
if (*tail == '\0') {
tail = NULL;
} else {
tail++;
}
/*
* Check to see if the pattern needs to compare with dot files.
*/
if ((newPattern[0] == '.')
|| ((pattern[0] == '\\') && (pattern[1] == '.'))) {
matchDotFiles = 1;
} else {
matchDotFiles = 0;
}
/*
* Now iterate over all of the files in the directory.
*/
Tcl_DStringInit(&buffer);
#ifdef VERBOSE
for ( rc = NO_ERROR;
rc == NO_ERROR;
printf("DosFindNext returns %x (%s)\n",
rc = DosFindNext(handle, &data, sizeof(data), &filesAtATime),
data.achName)) {
#else
for ( rc = NO_ERROR;
rc == NO_ERROR;
rc = DosFindNext(handle, &data, sizeof(data), &filesAtATime)) {
#endif
char *matchResult;
/*
* Ignore hidden files.
* NB. The Windows port has removed the ignoring of files with
* attribute FILE_HIDDEN from 7.6 to 8.0 and therefore only considers
* dot files hidden. So why have we made all those files hidden?
* Remove '(data.attrFile & FILE_HIDDEN) ||' if you want that.
*/
if ((data.attrFile & FILE_HIDDEN)
|| (!matchDotFiles && (data.achName[0] == '.'))) {
continue;
}
/*
* Check to see if the file matches the pattern. If the volume is not
* case sensitive, we need to convert the file name to lower case. If
* the volume also doesn't preserve case, then we return the lower case
* form of the name, otherwise we return the system form.
*/
matchResult = NULL;
if (!(volFlags & FS_CASE_SENSITIVE)) {
Tcl_DStringSetLength(&buffer, 0);
Tcl_DStringAppend(&buffer, data.achName, -1);
for (p = buffer.string; *p != '\0'; p++) {
*p = (char) tolower(*p);
}
if (Tcl_StringMatch(buffer.string, newPattern)) {
if (volFlags & FS_CASE_IS_PRESERVED) {
matchResult = data.achName;
} else {
matchResult = buffer.string;
}
}
} else {
if (Tcl_StringMatch(data.achName, newPattern)) {
matchResult = data.achName;
}
}
if (matchResult == NULL) {
continue;
}
/*
* If the file matches, then we need to process the remainder of the
* path. If there are more characters to process, then ensure matching
* files are directories and call TclDoGlob. Otherwise, just add the
* file to the result.
*/
Tcl_DStringSetLength(dirPtr, baseLength);
Tcl_DStringAppend(dirPtr, matchResult, -1);
if (tail == NULL) {
Tcl_AppendElement(interp, dirPtr->string);
} else {
if ((DosQueryPathInfo(dirPtr->string, FIL_STANDARD, &infoBuf,
sizeof(infoBuf)) == NO_ERROR) &&
(infoBuf.attrFile & FILE_DIRECTORY)) {
Tcl_DStringAppend(dirPtr, "/", 1);
result = TclDoGlob(interp, separators, dirPtr, tail);
if (result != TCL_OK) {
break;
}
}
}
}
Tcl_DStringFree(&buffer);
DosFindClose(handle);
ckfree(newPattern);
return result;
}
/*
*----------------------------------------------------------------------
*
* TclChdir --
*
* Change the current working directory.
*
* Results:
* The result is a standard Tcl result. If an error occurs and
* interp isn't NULL, an error message is left in interp->result.
*
* Side effects:
* The working directory for this application is changed. Also
* the cache maintained used by TclGetCwd is deallocated and
* set to NULL.
*
*----------------------------------------------------------------------
*/
int
TclChdir(interp, dirName)
Tcl_Interp *interp; /* If non NULL, used for error reporting. */
char *dirName; /* Path to new working directory. */
{
#ifdef VERBOSE
printf("TclChDir %s\n", dirName);
#endif
if (currentDir != NULL) {
ckfree(currentDir);
currentDir = NULL;
}
/* Set drive, if present */
if (dirName[1] == ':') {
ULONG ulDriveNum;
/* Determine disk number */
for (ulDriveNum=1;
ulDriveNum < 27 && strnicmp(&drives[ulDriveNum], dirName, 1) != 0;
ulDriveNum++)
/* do nothing */;
if (ulDriveNum == 27) {
if (interp != NULL) {
Tcl_AppendResult(interp, "invalid drive specification \'",
dirName[0], "\': ",
Tcl_PosixError(interp), (char *) NULL);
}
return TCL_ERROR;
}
rc = DosSetDefaultDisk(ulDriveNum);
#ifdef VERBOSE
printf("DosSetDefaultDisk %c (%d) returned [%d]\n", dirName[0],
ulDriveNum, rc);
#endif
dirName += 2;
}
/* Set directory if specified (not just a drive spec) */
if (strcmp(dirName, "") != 0) {
rc = DosSetCurrentDir(dirName);
#ifdef VERBOSE
printf("DosSetCurrentDir [%s] returned [%d]\n", dirName, rc);
#endif
if (rc != NO_ERROR) {
TclOS2ConvertError(rc);
if (interp != NULL) {
Tcl_AppendResult(interp,
"couldn't change working directory to \"",
dirName, "\": ", Tcl_PosixError(interp), (char *) NULL);
}
return TCL_ERROR;
}
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* TclGetCwd --
*
* Return the path name of the current working directory.
*
* Results:
* The result is the full path name of the current working
* directory, or NULL if an error occurred while figuring it
* out. If an error occurs and interp isn't NULL, an error
* message is left in interp->result.
*
* Side effects:
* The path name is cached to avoid having to recompute it
* on future calls; if it is already cached, the cached
* value is returned.
*
*----------------------------------------------------------------------
*/
char *
TclGetCwd(interp)
Tcl_Interp *interp; /* If non NULL, used for error reporting. */
{
#define DRIVEPART 3 /* Drive letter, ':' and '/' */
static char buffer[MAXPATHLEN+1+DRIVEPART];
char *bufPtr = NULL, *p;
ULONG length = MAXPATHLEN+1;
ULONG ulDriveNum = 0; /* A=1, B=2, ... */
ULONG ulDriveMap = 0; /* Bitmap of valid drives */
#ifdef VERBOSE
printf("TclGetCwd\n");
#endif
if (currentDir == NULL) {
rc = DosQueryCurrentDisk(&ulDriveNum, &ulDriveMap);
#ifdef VERBOSE
printf("DosQueryCurrentDisk returned [%d], drive %d (%c)\n", rc,
ulDriveNum, drives[ulDriveNum]);
#endif
if (rc != NO_ERROR) {
TclOS2ConvertError(rc);
if (interp != NULL) {
Tcl_AppendResult(interp,
"error getting default drive: ",
Tcl_PosixError(interp), (char *) NULL);
}
return NULL;
}
/* OS/2 returns pwd *without* leading slash!, so add it */
buffer[0] = drives[ulDriveNum];
buffer[1] = ':';
buffer[2] = '/';
rc = DosQueryCurrentDir(0, buffer+3, &length);
#ifdef VERBOSE
printf("DosQueryCurrentDir returned [%d], dir %s\n", rc, buffer);
#endif
if (rc != NO_ERROR) {
TclOS2ConvertError(rc);
if (interp != NULL) {
if (errno == ERANGE) {
Tcl_SetResult(interp,
"working directory name is too long",
TCL_STATIC);
} else {
Tcl_AppendResult(interp,
"error getting working directory name: ",
Tcl_PosixError(interp), (char *) NULL);
}
}
return NULL;
}
bufPtr = buffer;
/*
* Convert to forward slashes for easier use in scripts.
*/
for (p = bufPtr; *p != '\0'; p++) {
if (*p == '\\') {
*p = '/';
}
}
}
return bufPtr;
}
/*
*----------------------------------------------------------------------
*
* TclpStat, TclpLstat --
*
* These functions replace the library versions of stat and lstat.
*
* The stat and lstat functions provided by some compilers
* are incomplete. Ideally, a complete rewrite of stat would go
* here; now, the only fix is that stat("c:") used to return an
* error instead infor for current dir on specified drive.
*
* Results:
* See stat documentation.
*
* Side effects:
* See stat documentation.
*
*----------------------------------------------------------------------
*/
int
TclpStat(path, buf)
CONST char *path; /* Path of file to stat (in current CP). */
struct stat *buf; /* Filled with results of stat call. */
{
char name[4];
int result;
if ((strlen(path) == 2) && (path[1] == ':')) {
strcpy(name, path);
name[2] = '.';
name[3] = '\0';
path = name;
}
#undef stat
result = stat(path, buf);
return result;
}
/*
*---------------------------------------------------------------------------
*
* TclpAccess --
*
* This function replaces the library version of access.
*
* The library version of access returns that all files have execute
* permission.
*
* Results:
* See access documentation.
*
* Side effects:
* See access documentation.
*
*---------------------------------------------------------------------------
*/
int
TclpAccess(
CONST char *path, /* Path of file to access (in current CP). */
int mode) /* Permission setting. */
{
int result;
CONST char *p;
#undef access
result = access(path, mode);
#ifdef VERBOSE
printf("TclpAccess [%s] [%d] returns %d\n", path, mode, result);
if (result == -1) {
printf(" errno %d\n", errno);
}
fflush(stdout);
#endif
if (result == 0) {
/*
FILESTATUS3 infoBuf;
rc = DosQueryPathInfo (path, FIL_STANDARD, &infoBuf, sizeof (infoBuf));
*/
#ifdef VERBOSE
printf("TclpAccess [%s] [%d] returns %d\n", path, mode, result);
/*
printf("TclpAccess [%s] [%d] returns %d\n", path, mode, rc);
if (rc != NO_ERROR) {
printf(" ERROR %d\n", rc);
} else {
printf(" infoBuf.attrFile %x\n", infoBuf.attrFile);
}
*/
fflush(stdout);
#endif
/*
if (rc == NO_ERROR) {
*/
if (mode & X_OK) {
FILESTATUS3 infoBuf;
if ((DosQueryPathInfo(path, FIL_STANDARD, &infoBuf,
sizeof(infoBuf)) == NO_ERROR) &&
(infoBuf.attrFile & FILE_DIRECTORY)) {
/*
* Directories are always executable.
*/
return 0;
}
p = strrchr(path, '.');
if (p != NULL) {
p++;
if ((stricmp(p, "exe") == 0)
|| (stricmp(p, "com") == 0)
|| (stricmp(p, "cmd") == 0)
|| (stricmp(p, "bat") == 0)) {
/*
* File that ends with .exe, .com, .cmd, or .bat
* is executable.
*/
return 0;
}
}
errno = EACCES;
return -1;
}
/*
if (mode & W_OK) {
if (infoBuf.attrFile & FILE_READONLY) {
errno = EACCES;
return -1;
}
}
if (mode & F_OK) {
if (infoBuf.attrFile & FILE_HIDDEN) {
errno = EACCES;
return -1;
}
}
*/
}
return result;
}