/*
* tclWinTest.c --
*
* Contains commands for platform specific tests on Windows.
*
* Copyright (c) 1996 Sun Microsystems, Inc.
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
* RCS: @(#) $Id: tclWinTest.c,v 1.22.2.2 2010/01/31 23:51:37 nijtmans Exp $
*/
#include "tclInt.h"
/*
* For TestplatformChmod on Windows
*/
#ifdef __WIN32__
#include <aclapi.h>
#endif
/*
* MinGW 3.4.2 does not define this.
*/
#ifndef INHERITED_ACE
#define INHERITED_ACE (0x10)
#endif
/*
* Forward declarations of functions defined later in this file:
*/
int TclplatformtestInit(Tcl_Interp *interp);
static int TesteventloopCmd(ClientData dummy, Tcl_Interp *interp,
int argc, const char **argv);
static int TestvolumetypeCmd(ClientData dummy,
Tcl_Interp *interp, int objc,
Tcl_Obj *const objv[]);
static int TestwinclockCmd(ClientData dummy, Tcl_Interp* interp,
int objc, Tcl_Obj *const objv[]);
static int TestwinsleepCmd(ClientData dummy, Tcl_Interp* interp,
int objc, Tcl_Obj *const objv[]);
static Tcl_ObjCmdProc TestExceptionCmd;
static int TestwincpuidCmd(ClientData dummy, Tcl_Interp* interp,
int objc, Tcl_Obj *const objv[]);
static int TestplatformChmod(const char *nativePath, int pmode);
static int TestchmodCmd(ClientData dummy,
Tcl_Interp *interp, int argc, const char **argv);
/*
*----------------------------------------------------------------------
*
* TclplatformtestInit --
*
* Defines commands that test platform specific functionality for Windows
* platforms.
*
* Results:
* A standard Tcl result.
*
* Side effects:
* Defines new commands.
*
*----------------------------------------------------------------------
*/
int
TclplatformtestInit(
Tcl_Interp *interp) /* Interpreter to add commands to. */
{
/*
* Add commands for platform specific tests for Windows here.
*/
Tcl_CreateCommand(interp, "testchmod", TestchmodCmd, NULL, NULL);
Tcl_CreateCommand(interp, "testeventloop", TesteventloopCmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "testvolumetype", TestvolumetypeCmd,
NULL, NULL);
Tcl_CreateObjCommand(interp, "testwinclock", TestwinclockCmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "testwincpuid", TestwincpuidCmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "testwinsleep", TestwinsleepCmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "testexcept", TestExceptionCmd, NULL, NULL);
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* TesteventloopCmd --
*
* This function implements the "testeventloop" command. It is used to
* test the Tcl notifier from an "external" event loop (i.e. not
* Tcl_DoOneEvent()).
*
* Results:
* A standard Tcl result.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static int
TesteventloopCmd(
ClientData clientData, /* Not used. */
Tcl_Interp *interp, /* Current interpreter. */
int argc, /* Number of arguments. */
const char **argv) /* Argument strings. */
{
static int *framePtr = NULL;/* Pointer to integer on stack frame of
* innermost invocation of the "wait"
* subcommand. */
if (argc < 2) {
Tcl_AppendResult(interp, "wrong # arguments: should be \"", argv[0],
" option ... \"", NULL);
return TCL_ERROR;
}
if (strcmp(argv[1], "done") == 0) {
*framePtr = 1;
} else if (strcmp(argv[1], "wait") == 0) {
int *oldFramePtr, done;
int oldMode = Tcl_SetServiceMode(TCL_SERVICE_ALL);
/*
* Save the old stack frame pointer and set up the current frame.
*/
oldFramePtr = framePtr;
framePtr = &done;
/*
* Enter a standard Windows event loop until the flag changes. Note
* that we do not explicitly call Tcl_ServiceEvent().
*/
done = 0;
while (!done) {
MSG msg;
if (!GetMessage(&msg, NULL, 0, 0)) {
/*
* The application is exiting, so repost the quit message and
* start unwinding.
*/
PostQuitMessage((int) msg.wParam);
break;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
(void) Tcl_SetServiceMode(oldMode);
framePtr = oldFramePtr;
} else {
Tcl_AppendResult(interp, "bad option \"", argv[1],
"\": must be done or wait", NULL);
return TCL_ERROR;
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* Testvolumetype --
*
* This function implements the "testvolumetype" command. It is used to
* check the volume type (FAT, NTFS) of a volume.
*
* Results:
* A standard Tcl result.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static int
TestvolumetypeCmd(
ClientData clientData, /* Not used. */
Tcl_Interp *interp, /* Current interpreter. */
int objc, /* Number of arguments. */
Tcl_Obj *const objv[]) /* Argument objects. */
{
#define VOL_BUF_SIZE 32
int found;
char volType[VOL_BUF_SIZE];
char *path;
if (objc > 2) {
Tcl_WrongNumArgs(interp, 1, objv, "?name?");
return TCL_ERROR;
}
if (objc == 2) {
/*
* path has to be really a proper volume, but we don't get query APIs
* for that until NT5
*/
path = Tcl_GetString(objv[1]);
} else {
path = NULL;
}
found = GetVolumeInformationA(path, NULL, 0, NULL, NULL, NULL, volType,
VOL_BUF_SIZE);
if (found == 0) {
Tcl_AppendResult(interp, "could not get volume type for \"",
(path?path:""), "\"", NULL);
TclWinConvertError(GetLastError());
return TCL_ERROR;
}
Tcl_SetResult(interp, volType, TCL_VOLATILE);
return TCL_OK;
#undef VOL_BUF_SIZE
}
/*
*----------------------------------------------------------------------
*
* TestwinclockCmd --
*
* Command that returns the seconds and microseconds portions of the
* system clock and of the Tcl clock so that they can be compared to
* validate that the Tcl clock is staying in sync.
*
* Usage:
* testclock
*
* Parameters:
* None.
*
* Results:
* Returns a standard Tcl result comprising a four-element list: the
* seconds and microseconds portions of the system clock, and the seconds
* and microseconds portions of the Tcl clock.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static int
TestwinclockCmd(
ClientData dummy, /* Unused */
Tcl_Interp* interp, /* Tcl interpreter */
int objc, /* Argument count */
Tcl_Obj *const objv[]) /* Argument vector */
{
static const FILETIME posixEpoch = { 0xD53E8000, 0x019DB1DE };
/* The Posix epoch, expressed as a Windows
* FILETIME */
Tcl_Time tclTime; /* Tcl clock */
FILETIME sysTime; /* System clock */
Tcl_Obj *result; /* Result of the command */
LARGE_INTEGER t1, t2;
LARGE_INTEGER p1, p2;
if (objc != 1) {
Tcl_WrongNumArgs(interp, 1, objv, "");
return TCL_ERROR;
}
QueryPerformanceCounter(&p1);
Tcl_GetTime(&tclTime);
GetSystemTimeAsFileTime(&sysTime);
t1.LowPart = posixEpoch.dwLowDateTime;
t1.HighPart = posixEpoch.dwHighDateTime;
t2.LowPart = sysTime.dwLowDateTime;
t2.HighPart = sysTime.dwHighDateTime;
t2.QuadPart -= t1.QuadPart;
QueryPerformanceCounter(&p2);
result = Tcl_NewObj();
Tcl_ListObjAppendElement(interp, result,
Tcl_NewIntObj((int) (t2.QuadPart / 10000000)));
Tcl_ListObjAppendElement(interp, result,
Tcl_NewIntObj((int) ((t2.QuadPart / 10) % 1000000)));
Tcl_ListObjAppendElement(interp, result, Tcl_NewIntObj(tclTime.sec));
Tcl_ListObjAppendElement(interp, result, Tcl_NewIntObj(tclTime.usec));
Tcl_ListObjAppendElement(interp, result, Tcl_NewWideIntObj(p1.QuadPart));
Tcl_ListObjAppendElement(interp, result, Tcl_NewWideIntObj(p2.QuadPart));
Tcl_SetObjResult(interp, result);
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* TestwincpuidCmd --
*
* Retrieves CPU ID information.
*
* Usage:
* testwincpuid <eax>
*
* Parameters:
* eax - The value to pass in the EAX register to a CPUID instruction.
*
* Results:
* Returns a four-element list containing the values from the EAX, EBX,
* ECX and EDX registers returned from the CPUID instruction.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static int
TestwincpuidCmd(
ClientData dummy,
Tcl_Interp* interp, /* Tcl interpreter */
int objc, /* Parameter count */
Tcl_Obj *const * objv) /* Parameter vector */
{
int status, index, i;
unsigned int regs[4];
Tcl_Obj *regsObjs[4];
if (objc != 2) {
Tcl_WrongNumArgs(interp, 1, objv, "eax");
return TCL_ERROR;
}
if (Tcl_GetIntFromObj(interp, objv[1], &index) != TCL_OK) {
return TCL_ERROR;
}
status = TclWinCPUID((unsigned) index, regs);
if (status != TCL_OK) {
Tcl_SetObjResult(interp,
Tcl_NewStringObj("operation not available", -1));
return status;
}
for (i=0 ; i<4 ; ++i) {
regsObjs[i] = Tcl_NewIntObj((int) regs[i]);
}
Tcl_SetObjResult(interp, Tcl_NewListObj(4, regsObjs));
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* TestwinsleepCmd --
*
* Causes this process to wait for the given number of milliseconds by
* means of a direct call to Sleep.
*
* Usage:
* testwinsleep <n>
*
* Parameters:
* n - the number of milliseconds to sleep
*
* Results:
* None.
*
* Side effects:
* Sleeps for the requisite number of milliseconds.
*
*----------------------------------------------------------------------
*/
static int
TestwinsleepCmd(
ClientData clientData, /* Unused */
Tcl_Interp* interp, /* Tcl interpreter */
int objc, /* Parameter count */
Tcl_Obj *const * objv) /* Parameter vector */
{
int ms;
if (objc != 2) {
Tcl_WrongNumArgs(interp, 1, objv, "ms");
return TCL_ERROR;
}
if (Tcl_GetIntFromObj(interp, objv[1], &ms) != TCL_OK) {
return TCL_ERROR;
}
Sleep((DWORD) ms);
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* TestExceptionCmd --
*
* Causes this process to end with the named exception. Used for testing
* Tcl_WaitPid().
*
* Usage:
* testexcept <type>
*
* Parameters:
* Type of exception.
*
* Results:
* None, this process closes now and doesn't return.
*
* Side effects:
* This Tcl process closes, hard... Bang!
*
*----------------------------------------------------------------------
*/
static int
TestExceptionCmd(
ClientData dummy, /* Unused */
Tcl_Interp* interp, /* Tcl interpreter */
int objc, /* Argument count */
Tcl_Obj *const objv[]) /* Argument vector */
{
static const char *cmds[] = {
"access_violation", "datatype_misalignment", "array_bounds",
"float_denormal", "float_divbyzero", "float_inexact",
"float_invalidop", "float_overflow", "float_stack", "float_underflow",
"int_divbyzero", "int_overflow", "private_instruction", "inpageerror",
"illegal_instruction", "noncontinue", "stack_overflow",
"invalid_disp", "guard_page", "invalid_handle", "ctrl+c",
NULL
};
static DWORD exceptions[] = {
EXCEPTION_ACCESS_VIOLATION, EXCEPTION_DATATYPE_MISALIGNMENT,
EXCEPTION_ARRAY_BOUNDS_EXCEEDED, EXCEPTION_FLT_DENORMAL_OPERAND,
EXCEPTION_FLT_DIVIDE_BY_ZERO, EXCEPTION_FLT_INEXACT_RESULT,
EXCEPTION_FLT_INVALID_OPERATION, EXCEPTION_FLT_OVERFLOW,
EXCEPTION_FLT_STACK_CHECK, EXCEPTION_FLT_UNDERFLOW,
EXCEPTION_INT_DIVIDE_BY_ZERO, EXCEPTION_INT_OVERFLOW,
EXCEPTION_PRIV_INSTRUCTION, EXCEPTION_IN_PAGE_ERROR,
EXCEPTION_ILLEGAL_INSTRUCTION, EXCEPTION_NONCONTINUABLE_EXCEPTION,
EXCEPTION_STACK_OVERFLOW, EXCEPTION_INVALID_DISPOSITION,
EXCEPTION_GUARD_PAGE, EXCEPTION_INVALID_HANDLE, CONTROL_C_EXIT
};
int cmd;
if (objc != 2) {
Tcl_WrongNumArgs(interp, 0, objv, "<type-of-exception>");
return TCL_ERROR;
}
if (Tcl_GetIndexFromObj(interp, objv[1], cmds, "command", 0,
&cmd) != TCL_OK) {
return TCL_ERROR;
}
/*
* Make sure the GPF dialog doesn't popup.
*/
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
/*
* As Tcl does not handle structured exceptions, this falls all the way
* back up the instruction stack to the C run-time portion that called
* main() where the process will now be terminated with this exception
* code by the default handler the C run-time provides.
*/
/* SMASH! */
RaiseException(exceptions[cmd], EXCEPTION_NONCONTINUABLE, 0, NULL);
/* NOTREACHED */
return TCL_OK;
}
static int
TestplatformChmod(
const char *nativePath,
int pmode)
{
typedef DWORD (WINAPI *getSidLengthRequiredDef)(UCHAR);
typedef BOOL (WINAPI *initializeSidDef)(PSID, PSID_IDENTIFIER_AUTHORITY,
BYTE);
typedef PDWORD (WINAPI *getSidSubAuthorityDef)(PSID, DWORD);
typedef DWORD (WINAPI *setNamedSecurityInfoADef)(IN LPSTR,
IN SE_OBJECT_TYPE, IN SECURITY_INFORMATION, IN PSID, IN PSID,
IN PACL, IN PACL);
typedef BOOL (WINAPI *getAceDef)(PACL, DWORD, LPVOID *);
typedef BOOL (WINAPI *addAceDef)(PACL, DWORD, DWORD, LPVOID, DWORD);
typedef BOOL (WINAPI *equalSidDef)(PSID, PSID);
typedef BOOL (WINAPI *addAccessDeniedAceDef)(PACL, DWORD, DWORD, PSID);
typedef BOOL (WINAPI *initializeAclDef)(PACL, DWORD, DWORD);
typedef DWORD (WINAPI *getLengthSidDef)(PSID);
typedef BOOL (WINAPI *getAclInformationDef)(PACL, LPVOID, DWORD,
ACL_INFORMATION_CLASS);
typedef BOOL (WINAPI *getSecurityDescriptorDaclDef)(PSECURITY_DESCRIPTOR,
LPBOOL, PACL *, LPBOOL);
typedef BOOL (WINAPI *lookupAccountNameADef)(LPCSTR, LPCSTR, PSID,
PDWORD, LPSTR, LPDWORD, PSID_NAME_USE);
typedef BOOL (WINAPI *getFileSecurityADef)(LPCSTR, SECURITY_INFORMATION,
PSECURITY_DESCRIPTOR, DWORD, LPDWORD);
static const SECURITY_INFORMATION infoBits = OWNER_SECURITY_INFORMATION
| GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION;
static const DWORD readOnlyMask = FILE_DELETE_CHILD | FILE_ADD_FILE
| FILE_ADD_SUBDIRECTORY | FILE_WRITE_EA | FILE_APPEND_DATA
| FILE_WRITE_DATA | DELETE;
/*
* References to security functions (only available on NT and later).
*/
static getSidLengthRequiredDef getSidLengthRequiredProc;
static initializeSidDef initializeSidProc;
static getSidSubAuthorityDef getSidSubAuthorityProc;
static setNamedSecurityInfoADef setNamedSecurityInfoProc;
static getAceDef getAceProc;
static addAceDef addAceProc;
static equalSidDef equalSidProc;
static addAccessDeniedAceDef addAccessDeniedAceProc;
static initializeAclDef initializeAclProc;
static getLengthSidDef getLengthSidProc;
static getAclInformationDef getAclInformationProc;
static getSecurityDescriptorDaclDef getSecurityDescriptorDaclProc;
static lookupAccountNameADef lookupAccountNameProc;
static getFileSecurityADef getFileSecurityProc;
static int initialized = 0;
const BOOL set_readOnly = !(pmode & 0222);
BOOL acl_readOnly_found = FALSE, curAclPresent, curAclDefaulted;
SID_IDENTIFIER_AUTHORITY userSidAuthority = {
SECURITY_WORLD_SID_AUTHORITY
};
BYTE *secDesc = 0;
DWORD secDescLen, attr, newAclSize;
ACL_SIZE_INFORMATION ACLSize;
PACL curAcl, newAcl = 0;
WORD j;
SID *userSid = 0;
TCHAR *userDomain = 0;
int res = 0;
/*
* One time initialization, dynamically load Windows NT features
*/
if (!initialized) {
TCL_DECLARE_MUTEX(initializeMutex)
Tcl_MutexLock(&initializeMutex);
if (!initialized) {
HINSTANCE hInstance = LoadLibrary("Advapi32");
if (hInstance != NULL) {
setNamedSecurityInfoProc = (setNamedSecurityInfoADef)
GetProcAddress(hInstance, "SetNamedSecurityInfoA");
getFileSecurityProc = (getFileSecurityADef)
GetProcAddress(hInstance, "GetFileSecurityA");
getAceProc = (getAceDef)
GetProcAddress(hInstance, "GetAce");
addAceProc = (addAceDef)
GetProcAddress(hInstance, "AddAce");
equalSidProc = (equalSidDef)
GetProcAddress(hInstance, "EqualSid");
addAccessDeniedAceProc = (addAccessDeniedAceDef)
GetProcAddress(hInstance, "AddAccessDeniedAce");
initializeAclProc = (initializeAclDef)
GetProcAddress(hInstance, "InitializeAcl");
getLengthSidProc = (getLengthSidDef)
GetProcAddress(hInstance, "GetLengthSid");
getAclInformationProc = (getAclInformationDef)
GetProcAddress(hInstance, "GetAclInformation");
getSecurityDescriptorDaclProc = (getSecurityDescriptorDaclDef)
GetProcAddress(hInstance, "GetSecurityDescriptorDacl");
lookupAccountNameProc = (lookupAccountNameADef)
GetProcAddress(hInstance, "LookupAccountNameA");
getSidLengthRequiredProc = (getSidLengthRequiredDef)
GetProcAddress(hInstance, "GetSidLengthRequired");
initializeSidProc = (initializeSidDef)
GetProcAddress(hInstance, "InitializeSid");
getSidSubAuthorityProc = (getSidSubAuthorityDef)
GetProcAddress(hInstance, "GetSidSubAuthority");
if (setNamedSecurityInfoProc && getAceProc && addAceProc
&& equalSidProc && addAccessDeniedAceProc
&& initializeAclProc && getLengthSidProc
&& getAclInformationProc
&& getSecurityDescriptorDaclProc
&& lookupAccountNameProc && getFileSecurityProc
&& getSidLengthRequiredProc && initializeSidProc
&& getSidSubAuthorityProc) {
initialized = 1;
}
}
if (!initialized) {
initialized = -1;
}
}
Tcl_MutexUnlock(&initializeMutex);
}
/*
* Process the chmod request.
*/
attr = GetFileAttributes(nativePath);
/*
* nativePath not found
*/
if (attr == 0xffffffff) {
res = -1;
goto done;
}
/*
* If no ACL API is present or nativePath is not a directory, there is no
* special handling.
*/
if (initialized < 0 || !(attr & FILE_ATTRIBUTE_DIRECTORY)) {
goto done;
}
/*
* Set the result to error, if the ACL change is successful it will be
* reset to 0.
*/
res = -1;
/*
* Read the security descriptor for the directory. Note the first call
* obtains the size of the security descriptor.
*/
if (!getFileSecurityProc(nativePath, infoBits, NULL, 0, &secDescLen)) {
DWORD secDescLen2 = 0;
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
goto done;
}
secDesc = (BYTE *) ckalloc(secDescLen);
if (!getFileSecurityProc(nativePath, infoBits,
(PSECURITY_DESCRIPTOR) secDesc, secDescLen, &secDescLen2)
|| (secDescLen < secDescLen2)) {
goto done;
}
}
/*
* Get the World SID.
*/
userSid = (SID *) ckalloc(getSidLengthRequiredProc((UCHAR) 1));
initializeSidProc(userSid, &userSidAuthority, (BYTE) 1);
*(getSidSubAuthorityProc(userSid, 0)) = SECURITY_WORLD_RID;
/*
* If curAclPresent == false then curAcl and curAclDefaulted not valid.
*/
if (!getSecurityDescriptorDaclProc((PSECURITY_DESCRIPTOR) secDesc,
&curAclPresent, &curAcl, &curAclDefaulted)) {
goto done;
}
if (!curAclPresent || !curAcl) {
ACLSize.AclBytesInUse = 0;
ACLSize.AceCount = 0;
} else if (!getAclInformationProc(curAcl, &ACLSize, sizeof(ACLSize),
AclSizeInformation)) {
goto done;
}
/*
* Allocate memory for the new ACL.
*/
newAclSize = ACLSize.AclBytesInUse + sizeof(ACCESS_DENIED_ACE)
+ getLengthSidProc(userSid) - sizeof(DWORD);
newAcl = (ACL *) ckalloc(newAclSize);
/*
* Initialize the new ACL.
*/
if (!initializeAclProc(newAcl, newAclSize, ACL_REVISION)) {
goto done;
}
/*
* Add denied to make readonly, this will be known as a "read-only tag".
*/
if (set_readOnly && !addAccessDeniedAceProc(newAcl, ACL_REVISION,
readOnlyMask, userSid)) {
goto done;
}
acl_readOnly_found = FALSE;
for (j = 0; j < ACLSize.AceCount; j++) {
LPVOID pACE2;
ACE_HEADER *phACE2;
if (!getAceProc(curAcl, j, &pACE2)) {
goto done;
}
phACE2 = (ACE_HEADER *) pACE2;
/*
* Do NOT propagate inherited ACEs.
*/
if (phACE2->AceFlags & INHERITED_ACE) {
continue;
}
/*
* Skip the "read-only tag" restriction (either added above, or it is
* being removed).
*/
if (phACE2->AceType == ACCESS_DENIED_ACE_TYPE) {
ACCESS_DENIED_ACE *pACEd = (ACCESS_DENIED_ACE *) phACE2;
if (pACEd->Mask == readOnlyMask
&& equalSidProc(userSid, (PSID) &pACEd->SidStart)) {
acl_readOnly_found = TRUE;
continue;
}
}
/*
* Copy the current ACE from the old to the new ACL.
*/
if (!addAceProc(newAcl, ACL_REVISION, MAXDWORD, (PACL *)pACE2,
((PACE_HEADER) pACE2)->AceSize)) {
goto done;
}
}
/*
* Apply the new ACL.
*/
if (set_readOnly == acl_readOnly_found || setNamedSecurityInfoProc(
(LPSTR) nativePath, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION,
NULL, NULL, newAcl, NULL) == ERROR_SUCCESS) {
res = 0;
}
done:
if (secDesc) {
ckfree((char *) secDesc);
}
if (newAcl) {
ckfree((char *) newAcl);
}
if (userSid) {
ckfree((char *) userSid);
}
if (userDomain) {
ckfree(userDomain);
}
if (res != 0) {
return res;
}
/*
* Run normal chmod command.
*/
return chmod(nativePath, pmode);
}
/*
*---------------------------------------------------------------------------
*
* TestchmodCmd --
*
* Implements the "testchmod" cmd. Used when testing "file" command. The
* only attribute used by the Windows platform is the user write flag; if
* this is not set, the file is made read-only. Otherwise, the file is
* made read-write.
*
* Results:
* A standard Tcl result.
*
* Side effects:
* Changes permissions of specified files.
*
*---------------------------------------------------------------------------
*/
static int
TestchmodCmd(
ClientData dummy, /* Not used. */
Tcl_Interp *interp, /* Current interpreter. */
int argc, /* Number of arguments. */
const char **argv) /* Argument strings. */
{
int i, mode;
char *rest;
if (argc < 2) {
usage:
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
" mode file ?file ...?", NULL);
return TCL_ERROR;
}
mode = (int) strtol(argv[1], &rest, 8);
if ((rest == argv[1]) || (*rest != '\0')) {
goto usage;
}
for (i = 2; i < argc; i++) {
Tcl_DString buffer;
const char *translated;
translated = Tcl_TranslateFileName(interp, argv[i], &buffer);
if (translated == NULL) {
return TCL_ERROR;
}
if (TestplatformChmod(translated, mode) != 0) {
Tcl_AppendResult(interp, translated, ": ", Tcl_PosixError(interp),
NULL);
return TCL_ERROR;
}
Tcl_DStringFree(&buffer);
}
return TCL_OK;
}
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
* fill-column: 78
* End:
*/
|