📄 tclwinfcmd.c
字号:
/* * tclWinFCmd.c * * This file implements the Windows specific portion of file manipulation * subcommands of the "file" command. * * Copyright (c) 1996-1997 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * * SCCS: @(#) tclWinFCmd.c 1.20 97/10/10 11:50:14 */#include "tclWinInt.h"/* * The following constants specify the type of callback when * TraverseWinTree() calls the traverseProc() */#define DOTREE_PRED 1 /* pre-order directory */#define DOTREE_POSTD 2 /* post-order directory */#define DOTREE_F 3 /* regular file *//* * Callbacks for file attributes code. */static int GetWinFileAttributes _ANSI_ARGS_((Tcl_Interp *interp, int objIndex, char *fileName, Tcl_Obj **attributePtrPtr));static int GetWinFileLongName _ANSI_ARGS_((Tcl_Interp *interp, int objIndex, char *fileName, Tcl_Obj **attributePtrPtr));static int GetWinFileShortName _ANSI_ARGS_((Tcl_Interp *interp, int objIndex, char *fileName, Tcl_Obj **attributePtrPtr));static int SetWinFileAttributes _ANSI_ARGS_((Tcl_Interp *interp, int objIndex, char *fileName, Tcl_Obj *attributePtr));static int CannotSetAttribute _ANSI_ARGS_((Tcl_Interp *interp, int objIndex, char *fileName, Tcl_Obj *attributePtr));/* * Constants and variables necessary for file attributes subcommand. */enum { WIN_ARCHIVE_ATTRIBUTE, WIN_HIDDEN_ATTRIBUTE, WIN_LONGNAME_ATTRIBUTE, WIN_READONLY_ATTRIBUTE, WIN_SHORTNAME_ATTRIBUTE, WIN_SYSTEM_ATTRIBUTE};static int attributeArray[] = {FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_HIDDEN, 0, FILE_ATTRIBUTE_READONLY, 0, FILE_ATTRIBUTE_SYSTEM};char *tclpFileAttrStrings[] = {"-archive", "-hidden", "-longname", "-readonly", "-shortname", "-system", (char *) NULL};CONST TclFileAttrProcs tclpFileAttrProcs[] = { {GetWinFileAttributes, SetWinFileAttributes}, {GetWinFileAttributes, SetWinFileAttributes}, {GetWinFileLongName, CannotSetAttribute}, {GetWinFileAttributes, SetWinFileAttributes}, {GetWinFileShortName, CannotSetAttribute}, {GetWinFileAttributes, SetWinFileAttributes}};/* * Prototype for the TraverseWinTree callback function. */typedef int (TraversalProc)(char *src, char *dst, DWORD attr, int type, Tcl_DString *errorPtr);/* * Declarations for local procedures defined in this file: */static void AttributesPosixError _ANSI_ARGS_((Tcl_Interp *interp, int objIndex, char *fileName, int getOrSet));static int ConvertFileNameFormat _ANSI_ARGS_((Tcl_Interp *interp, int objIndex, char *fileName, int longShort, Tcl_Obj **attributePtrPtr));static int TraversalCopy(char *src, char *dst, DWORD attr, int type, Tcl_DString *errorPtr);static int TraversalDelete(char *src, char *dst, DWORD attr, int type, Tcl_DString *errorPtr);static int TraverseWinTree(TraversalProc *traverseProc, Tcl_DString *sourcePtr, Tcl_DString *destPtr, Tcl_DString *errorPtr);/* *--------------------------------------------------------------------------- * * TclpRenameFile -- * * Changes the name of an existing file or directory, from src to dst. * If src and dst refer to the same file or directory, does nothing * and returns success. Otherwise if dst already exists, it will be * deleted and replaced by src subject to the following conditions: * If src is a directory, dst may be an empty directory. * If src is a file, dst may be a file. * In any other situation where dst already exists, the rename will * fail. * * Results: * If the directory was successfully created, returns TCL_OK. * Otherwise the return value is TCL_ERROR and errno is set to * indicate the error. Some possible values for errno are: * * EACCES: src or dst parent directory can't be read and/or written. * EEXIST: dst is a non-empty directory. * EINVAL: src is a root directory or dst is a subdirectory of src. * EISDIR: dst is a directory, but src is not. * ENOENT: src doesn't exist. src or dst is "". * ENOTDIR: src is a directory, but dst is not. * EXDEV: src and dst are on different filesystems. * * EACCES: exists an open file already referring to src or dst. * EACCES: src or dst specify the current working directory (NT). * EACCES: src specifies a char device (nul:, com1:, etc.) * EEXIST: dst specifies a char device (nul:, com1:, etc.) (NT) * EACCES: dst specifies a char device (nul:, com1:, etc.) (95) * * Side effects: * The implementation supports cross-filesystem renames of files, * but the caller should be prepared to emulate cross-filesystem * renames of directories if errno is EXDEV. * *--------------------------------------------------------------------------- */intTclpRenameFile( char *src, /* Pathname of file or dir to be renamed. */ char *dst) /* New pathname for file or directory. */{ DWORD srcAttr, dstAttr; /* * Would throw an exception under NT if one of the arguments is a * char block device. */ try { if (MoveFile(src, dst) != FALSE) { return TCL_OK; } } except (-1) {} TclWinConvertError(GetLastError()); srcAttr = GetFileAttributes(src); dstAttr = GetFileAttributes(dst); if (srcAttr == (DWORD) -1) { srcAttr = 0; } if (dstAttr == (DWORD) -1) { dstAttr = 0; } if (errno == EBADF) { errno = EACCES; return TCL_ERROR; } if ((errno == EACCES) && (TclWinGetPlatformId() == VER_PLATFORM_WIN32s)) { if ((srcAttr != 0) && (dstAttr != 0)) { /* * Win32s reports trying to overwrite an existing file or directory * as EACCES. */ errno = EEXIST; } } if (errno == EACCES) { decode: if (srcAttr & FILE_ATTRIBUTE_DIRECTORY) { char srcPath[MAX_PATH], dstPath[MAX_PATH]; int srcArgc, dstArgc; char **srcArgv, **dstArgv; char *srcRest, *dstRest; int size; size = GetFullPathName(src, sizeof(srcPath), srcPath, &srcRest); if ((size == 0) || (size > sizeof(srcPath))) { return TCL_ERROR; } size = GetFullPathName(dst, sizeof(dstPath), dstPath, &dstRest); if ((size == 0) || (size > sizeof(dstPath))) { return TCL_ERROR; } if (srcRest == NULL) { srcRest = srcPath + strlen(srcPath); } if (strnicmp(srcPath, dstPath, srcRest - srcPath) == 0) { /* * Trying to move a directory into itself. */ errno = EINVAL; return TCL_ERROR; } Tcl_SplitPath(srcPath, &srcArgc, &srcArgv); Tcl_SplitPath(dstPath, &dstArgc, &dstArgv); if (srcArgc == 1) { /* * They are trying to move a root directory. Whether * or not it is across filesystems, this cannot be * done. */ errno = EINVAL; } else if ((srcArgc > 0) && (dstArgc > 0) && (stricmp(srcArgv[0], dstArgv[0]) != 0)) { /* * If src is a directory and dst filesystem != src * filesystem, errno should be EXDEV. It is very * important to get this behavior, so that the caller * can respond to a cross filesystem rename by * simulating it with copy and delete. The MoveFile * system call already handles the case of moving a * file between filesystems. */ errno = EXDEV; } ckfree((char *) srcArgv); ckfree((char *) dstArgv); } /* * Other types of access failure is that dst is a read-only * filesystem, that an open file referred to src or dest, or that * src or dest specified the current working directory on the * current filesystem. EACCES is returned for those cases. */ } else if (errno == EEXIST) { /* * Reports EEXIST any time the target already exists. If it makes * sense, remove the old file and try renaming again. */ if (srcAttr & FILE_ATTRIBUTE_DIRECTORY) { if (dstAttr & FILE_ATTRIBUTE_DIRECTORY) { /* * Overwrite empty dst directory with src directory. The * following call will remove an empty directory. If it * fails, it's because it wasn't empty. */ if (TclpRemoveDirectory(dst, 0, NULL) == TCL_OK) { /* * Now that that empty directory is gone, we can try * renaming again. If that fails, we'll put this empty * directory back, for completeness. */ if (MoveFile(src, dst) != FALSE) { return TCL_OK; } /* * Some new error has occurred. Don't know what it * could be, but report this one. */ TclWinConvertError(GetLastError()); CreateDirectory(dst, NULL); SetFileAttributes(dst, dstAttr); if (errno == EACCES) { /* * Decode the EACCES to a more meaningful error. */ goto decode; } } } else { /* (dstAttr & FILE_ATTRIBUTE_DIRECTORY) == 0 */ errno = ENOTDIR; } } else { /* (srcAttr & FILE_ATTRIBUTE_DIRECTORY) == 0 */ if (dstAttr & FILE_ATTRIBUTE_DIRECTORY) { errno = EISDIR; } else { /* * Overwrite existing file by: * * 1. Rename existing file to temp name. * 2. Rename old file to new name. * 3. If success, delete temp file. If failure, * put temp file back to old name. */ char tempName[MAX_PATH]; int result, size; char *rest; size = GetFullPathName(dst, sizeof(tempName), tempName, &rest); if ((size == 0) || (size > sizeof(tempName)) || (rest == NULL)) { return TCL_ERROR; } *rest = '\0'; result = TCL_ERROR; if (GetTempFileName(tempName, "tclr", 0, tempName) != 0) { /* * Strictly speaking, need the following DeleteFile and * MoveFile to be joined as an atomic operation so no * other app comes along in the meantime and creates the * same temp file. */ DeleteFile(tempName); if (MoveFile(dst, tempName) != FALSE) { if (MoveFile(src, dst) != FALSE) { SetFileAttributes(tempName, FILE_ATTRIBUTE_NORMAL); DeleteFile(tempName); return TCL_OK; } else { DeleteFile(dst); MoveFile(tempName, dst); } } /* * Can't backup dst file or move src file. Return that * error. Could happen if an open file refers to dst. */ TclWinConvertError(GetLastError()); if (errno == EACCES) { /* * Decode the EACCES to a more meaningful error. */ goto decode; } } return result; } } } return TCL_ERROR;}/* *--------------------------------------------------------------------------- * * TclpCopyFile -- * * Copy a single file (not a directory). If dst already exists and * is not a directory, it is removed. * * Results: * If the file was successfully copied, returns TCL_OK. Otherwise * the return value is TCL_ERROR and errno is set to indicate the * error. Some possible values for errno are: * * EACCES: src or dst parent directory can't be read and/or written. * EISDIR: src or dst is a directory. * ENOENT: src doesn't exist. src or dst is "". * * EACCES: exists an open file already referring to dst (95). * EACCES: src specifies a char device (nul:, com1:, etc.) (NT) * ENOENT: src specifies a char device (nul:, com1:, etc.) (95) * * Side effects: * It is not an error to copy to a char device. * *--------------------------------------------------------------------------- */int TclpCopyFile( char *src, /* Pathname of file to be copied. */ char *dst) /* Pathname of file to copy to. */{ /* * Would throw an exception under NT if one of the arguments is a char * block device. */ try { if (CopyFile(src, dst, 0) != FALSE) { return TCL_OK; } } except (-1) {} TclWinConvertError(GetLastError()); if (errno == EBADF) { errno = EACCES; return TCL_ERROR; } if (errno == EACCES) { DWORD srcAttr, dstAttr; srcAttr = GetFileAttributes(src); dstAttr = GetFileAttributes(dst); if (srcAttr != (DWORD) -1) { if (dstAttr == (DWORD) -1) { dstAttr = 0; } if ((srcAttr & FILE_ATTRIBUTE_DIRECTORY) || (dstAttr & FILE_ATTRIBUTE_DIRECTORY)) { errno = EISDIR; } if (dstAttr & FILE_ATTRIBUTE_READONLY) { SetFileAttributes(dst, dstAttr & ~FILE_ATTRIBUTE_READONLY); if (CopyFile(src, dst, 0) != FALSE) { return TCL_OK; } /* * Still can't copy onto dst. Return that error, and * restore attributes of dst. */ TclWinConvertError(GetLastError()); SetFileAttributes(dst, dstAttr); } } } return TCL_ERROR;}/* *--------------------------------------------------------------------------- * * TclpDeleteFile -- * * Removes a single file (not a directory). * * Results: * If the file was successfully deleted, returns TCL_OK. Otherwise * the return value is TCL_ERROR and errno is set to indicate the * error. Some possible values for errno are: * * EACCES: a parent directory can't be read and/or written. * EISDIR: path is a directory. * ENOENT: path doesn't exist or is "". * * EACCES: exists an open file already referring to path. * EACCES: path is a char device (nul:, com1:, etc.) * * Side effects: * The file is deleted, even if it is read-only. * *--------------------------------------------------------------------------- */intTclpDeleteFile( char *path) /* Pathname of file to be removed. */{ DWORD attr; if (DeleteFile(path) != FALSE) { return TCL_OK; } TclWinConvertError(GetLastError()); if (path[0] == '\0') {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -