/* * tclMacOSXFCmd.c * * This file implements the MacOSX specific portion of file manipulation * subcommands of the "file" command. * * Copyright (c) 2003-2007 Daniel A. Steffen * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #ifdef HAVE_GETATTRLIST #include #include #include #endif /* Darwin 8 copyfile API. */ #ifdef HAVE_COPYFILE #ifdef HAVE_COPYFILE_H #include #if defined(HAVE_WEAK_IMPORT) && (MAC_OS_X_VERSION_MIN_REQUIRED < 1040) /* Support for weakly importing copyfile. */ #define WEAK_IMPORT_COPYFILE extern int copyfile(const char *from, const char *to, copyfile_state_t state, copyfile_flags_t flags) WEAK_IMPORT_ATTRIBUTE; #endif /* HAVE_WEAK_IMPORT */ #else /* HAVE_COPYFILE_H */ int copyfile(const char *from, const char *to, void *state, uint32_t flags); #define COPYFILE_ACL (1<<0) #define COPYFILE_XATTR (1<<2) #define COPYFILE_NOFOLLOW_SRC (1<<18) #if defined(HAVE_WEAK_IMPORT) && (MAC_OS_X_VERSION_MIN_REQUIRED < 1040) /* Support for weakly importing copyfile. */ #define WEAK_IMPORT_COPYFILE extern int copyfile(const char *from, const char *to, void *state, uint32_t flags) WEAK_IMPORT_ATTRIBUTE; #endif /* HAVE_WEAK_IMPORT */ #endif /* HAVE_COPYFILE_H */ #endif /* HAVE_COPYFILE */ #ifdef WEAK_IMPORT_COPYFILE #define MayUseCopyFile() (copyfile != NULL) #elif defined(HAVE_COPYFILE) #define MayUseCopyFile() (1) #else #define MayUseCopyFile() (0) #endif #include /* * Constants for file attributes subcommand. Need to be kept in sync with * tclUnixFCmd.c ! */ enum { UNIX_GROUP_ATTRIBUTE, UNIX_OWNER_ATTRIBUTE, UNIX_PERMISSIONS_ATTRIBUTE, #ifdef HAVE_CHFLAGS UNIX_READONLY_ATTRIBUTE, #endif #ifdef MAC_OSX_TCL MACOSX_CREATOR_ATTRIBUTE, MACOSX_TYPE_ATTRIBUTE, MACOSX_HIDDEN_ATTRIBUTE, MACOSX_RSRCLENGTH_ATTRIBUTE, #endif }; typedef u_int32_t OSType; static int GetOSTypeFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, OSType *osTypePtr); static Tcl_Obj * NewOSTypeObj(const OSType newOSType); static int SetOSTypeFromAny(Tcl_Interp *interp, Tcl_Obj *objPtr); static void UpdateStringOfOSType(Tcl_Obj *objPtr); static const Tcl_ObjType tclOSTypeType = { "osType", /* name */ NULL, /* freeIntRepProc */ NULL, /* dupIntRepProc */ UpdateStringOfOSType, /* updateStringProc */ SetOSTypeFromAny /* setFromAnyProc */ }; enum { kIsInvisible = 0x4000, }; #define kFinfoIsInvisible (OSSwapHostToBigConstInt16(kIsInvisible)) typedef struct finderinfo { u_int32_t type; u_int32_t creator; u_int16_t fdFlags; u_int32_t location; u_int16_t reserved; u_int32_t extendedFileInfo[4]; } __attribute__ ((__packed__)) finderinfo; typedef struct { u_int32_t info_length; u_int32_t data[8]; } fileinfobuf; /* *---------------------------------------------------------------------- * * TclMacOSXGetFileAttribute * * Gets a MacOSX attribute of a file. Which attribute is controlled by * objIndex. The object will have ref count 0. * * Results: * Standard TCL result. Returns a new Tcl_Obj in attributePtrPtr if there * is no error. * * Side effects: * A new object is allocated. * *---------------------------------------------------------------------- */ int TclMacOSXGetFileAttribute( Tcl_Interp *interp, /* The interp we are using for errors. */ int objIndex, /* The index of the attribute. */ Tcl_Obj *fileName, /* The name of the file (UTF-8). */ Tcl_Obj **attributePtrPtr) /* A pointer to return the object with. */ { #ifdef HAVE_GETATTRLIST int result; Tcl_StatBuf statBuf; struct attrlist alist; fileinfobuf finfo; finderinfo *finder = (finderinfo *) &finfo.data; off_t *rsrcForkSize = (off_t *) &finfo.data; const char *native; result = TclpObjStat(fileName, &statBuf); if (result != 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not read \"%s\": %s", TclGetString(fileName), Tcl_PosixError(interp))); return TCL_ERROR; } if (S_ISDIR(statBuf.st_mode) && objIndex != MACOSX_HIDDEN_ATTRIBUTE) { /* * Directories only support attribute "-hidden". */ errno = EISDIR; Tcl_SetObjResult(interp, Tcl_ObjPrintf( "invalid attribute: %s", Tcl_PosixError(interp))); return TCL_ERROR; } bzero(&alist, sizeof(struct attrlist)); alist.bitmapcount = ATTR_BIT_MAP_COUNT; if (objIndex == MACOSX_RSRCLENGTH_ATTRIBUTE) { alist.fileattr = ATTR_FILE_RSRCLENGTH; } else { alist.commonattr = ATTR_CMN_FNDRINFO; } native = Tcl_FSGetNativePath(fileName); result = getattrlist(native, &alist, &finfo, sizeof(fileinfobuf), 0); if (result != 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not read attributes of \"%s\": %s", TclGetString(fileName), Tcl_PosixError(interp))); return TCL_ERROR; } switch (objIndex) { case MACOSX_CREATOR_ATTRIBUTE: *attributePtrPtr = NewOSTypeObj( OSSwapBigToHostInt32(finder->creator)); break; case MACOSX_TYPE_ATTRIBUTE: *attributePtrPtr = NewOSTypeObj( OSSwapBigToHostInt32(finder->type)); break; case MACOSX_HIDDEN_ATTRIBUTE: *attributePtrPtr = Tcl_NewBooleanObj( (finder->fdFlags & kFinfoIsInvisible) != 0); break; case MACOSX_RSRCLENGTH_ATTRIBUTE: *attributePtrPtr = Tcl_NewWideIntObj(*rsrcForkSize); break; } return TCL_OK; #else Tcl_SetObjResult(interp, Tcl_NewStringObj( "Mac OS X file attributes not supported", -1)); Tcl_SetErrorCode(interp, "TCL", "UNSUPPORTED", NULL); return TCL_ERROR; #endif /* HAVE_GETATTRLIST */ } /* *--------------------------------------------------------------------------- * * TclMacOSXSetFileAttribute -- * * Sets a MacOSX attribute of a file. Which attribute is controlled by * objIndex. * * Results: * Standard TCL result. * * Side effects: * As above. * *--------------------------------------------------------------------------- */ int TclMacOSXSetFileAttribute( Tcl_Interp *interp, /* The interp for error reporting. */ int objIndex, /* The index of the attribute. */ Tcl_Obj *fileName, /* The name of the file (UTF-8). */ Tcl_Obj *attributePtr) /* New owner for file. */ { #ifdef HAVE_GETATTRLIST int result; Tcl_StatBuf statBuf; struct attrlist alist; fileinfobuf finfo; finderinfo *finder = (finderinfo *) &finfo.data; off_t *rsrcForkSize = (off_t *) &finfo.data; const char *native; result = TclpObjStat(fileName, &statBuf); if (result != 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not read \"%s\": %s", TclGetString(fileName), Tcl_PosixError(interp))); return TCL_ERROR; } if (S_ISDIR(statBuf.st_mode) && objIndex != MACOSX_HIDDEN_ATTRIBUTE) { /* * Directories only support attribute "-hidden". */ errno = EISDIR; Tcl_SetObjResult(interp, Tcl_ObjPrintf( "invalid attribute: %s", Tcl_PosixError(interp))); return TCL_ERROR; } bzero(&alist, sizeof(struct attrlist)); alist.bitmapcount = ATTR_BIT_MAP_COUNT; if (objIndex == MACOSX_RSRCLENGTH_ATTRIBUTE) { alist.fileattr = ATTR_FILE_RSRCLENGTH; } else { alist.commonattr = ATTR_CMN_FNDRINFO; } native = Tcl_FSGetNativePath(fileName); result = getattrlist(native, &alist, &finfo, sizeof(fileinfobuf), 0); if (result != 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not read attributes of \"%s\": %s", TclGetString(fileName), Tcl_PosixError(interp))); return TCL_ERROR; } if (objIndex != MACOSX_RSRCLENGTH_ATTRIBUTE) { OSType t; int h; switch (objIndex) { case MACOSX_CREATOR_ATTRIBUTE: if (GetOSTypeFromObj(interp, attributePtr, &t) != TCL_OK) { return TCL_ERROR; } finder->creator = OSSwapHostToBigInt32(t); break; case MACOSX_TYPE_ATTRIBUTE: if (GetOSTypeFromObj(interp, attributePtr, &t) != TCL_OK) { return TCL_ERROR; } finder->type = OSSwapHostToBigInt32(t); break; case MACOSX_HIDDEN_ATTRIBUTE: if (Tcl_GetBooleanFromObj(interp, attributePtr, &h) != TCL_OK) { return TCL_ERROR; } if (h) { finder->fdFlags |= kFinfoIsInvisible; } else { finder->fdFlags &= ~kFinfoIsInvisible; } break; } result = setattrlist(native, &alist, &finfo.data, sizeof(finfo.data), 0); if (result != 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not set attributes of \"%s\": %s", TclGetString(fileName), Tcl_PosixError(interp))); return TCL_ERROR; } } else { Tcl_WideInt newRsrcForkSize; if (TclGetWideIntFromObj(interp, attributePtr, &newRsrcForkSize) != TCL_OK) { return TCL_ERROR; } if (newRsrcForkSize != *rsrcForkSize) { Tcl_DString ds; /* * Only setting rsrclength to 0 to strip a file's resource fork is * supported. */ if (newRsrcForkSize != 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "setting nonzero rsrclength not supported", -1)); Tcl_SetErrorCode(interp, "TCL", "UNSUPPORTED", NULL); return TCL_ERROR; } /* * Construct path to resource fork. */ Tcl_DStringInit(&ds); Tcl_DStringAppend(&ds, native, -1); Tcl_DStringAppend(&ds, _PATH_RSRCFORKSPEC, -1); result = truncate(Tcl_DStringValue(&ds), (off_t)0); if (result != 0) { /* * truncate() on a valid resource fork path may fail with a * permission error in some OS releases, try truncating with * open() instead: */ int fd = open(Tcl_DStringValue(&ds), O_WRONLY | O_TRUNC); if (fd > 0) { result = close(fd); } } Tcl_DStringFree(&ds); if (result != 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not truncate resource fork of \"%s\": %s", TclGetString(fileName), Tcl_PosixError(interp))); return TCL_ERROR; } } } return TCL_OK; #else Tcl_SetObjResult(interp, Tcl_NewStringObj( "Mac OS X file attributes not supported", -1)); Tcl_SetErrorCode(interp, "TCL", "UNSUPPORTED", NULL); return TCL_ERROR; #endif } /* *--------------------------------------------------------------------------- * * TclMacOSXCopyFileAttributes -- * * Copy the MacOSX attributes and resource fork (if present) from one * file to another. * * Results: * Standard Tcl result. * * Side effects: * MacOSX attributes and resource fork are updated in the new file to * reflect the old file. * *--------------------------------------------------------------------------- */ int TclMacOSXCopyFileAttributes( const char *src, /* Path name of source file (native). */ const char *dst, /* Path name of target file (native). */ const Tcl_StatBuf *statBufPtr) /* Stat info for source file */ { if (MayUseCopyFile()) { #ifdef HAVE_COPYFILE if (0 == copyfile(src, dst, NULL, (S_ISLNK(statBufPtr->st_mode) ? COPYFILE_XATTR | COPYFILE_NOFOLLOW_SRC : COPYFILE_XATTR | COPYFILE_ACL))) { return TCL_OK; } #endif /* HAVE_COPYFILE */ } else { #if (!defined(HAVE_COPYFILE) || defined(WEAK_IMPORT_COPYFILE)) && defined(HAVE_GETATTRLIST) struct attrlist alist; fileinfobuf finfo; off_t *rsrcForkSize = (off_t *) &finfo.data; Tcl_DString srcBuf, dstBuf; int result; bzero(&alist, sizeof(struct attrlist)); alist.bitmapcount = ATTR_BIT_MAP_COUNT; alist.commonattr = ATTR_CMN_FNDRINFO; if (getattrlist(src, &alist, &finfo, sizeof(fileinfobuf), 0)) { return TCL_ERROR; } if (setattrlist(dst, &alist, &finfo.data, sizeof(finfo.data), 0)) { return TCL_ERROR; } /* * If we're a directory, we're done as they never have resource forks. */ if (S_ISDIR(statBufPtr->st_mode)) { return TCL_OK; } /* * We only copy a non-empty resource fork, so determine if that's the * case first. */ alist.commonattr = 0; alist.fileattr = ATTR_FILE_RSRCLENGTH; if (getattrlist(src, &alist, &finfo, sizeof(fileinfobuf), 0)) { return TCL_ERROR; } else if (*rsrcForkSize == 0) { return TCL_OK; } /* * Construct paths to resource forks. */ Tcl_DStringInit(&srcBuf); Tcl_DStringAppend(&srcBuf, src, -1); Tcl_DStringAppend(&srcBuf, _PATH_RSRCFORKSPEC, -1); Tcl_DStringInit(&dstBuf); Tcl_DStringAppend(&dstBuf, dst, -1); Tcl_DStringAppend(&dstBuf, _PATH_RSRCFORKSPEC, -1); /* * Do the copy. */ result = TclUnixCopyFile(Tcl_DStringValue(&srcBuf), Tcl_DStringValue(&dstBuf), statBufPtr, 1); Tcl_DStringFree(&srcBuf); Tcl_DStringFree(&dstBuf); if (result == 0) { return TCL_OK; } #endif /* (!HAVE_COPYFILE || WEAK_IMPORT_COPYFILE) && HAVE_GETATTRLIST */ } return TCL_ERROR; } /* *---------------------------------------------------------------------- * * TclMacOSXMatchType -- * * This routine is used by the globbing code to check if a file matches a * given mac type and/or creator code. * * Results: * The return value is 1, 0 or -1 indicating whether the file matches the * given criteria, does not match them, or an error occurred (in wich * case an error is left in interp). * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclMacOSXMatchType( Tcl_Interp *interp, /* Interpreter to receive errors. */ const char *pathName, /* Native path to check. */ const char *fileName, /* Native filename to check. */ Tcl_StatBuf *statBufPtr, /* Stat info for file to check */ Tcl_GlobTypeData *types) /* Type description to match against. */ { #ifdef HAVE_GETATTRLIST struct attrlist alist; fileinfobuf finfo; finderinfo *finder = (finderinfo *) &finfo.data; OSType osType; bzero(&alist, sizeof(struct attrlist)); alist.bitmapcount = ATTR_BIT_MAP_COUNT; alist.commonattr = ATTR_CMN_FNDRINFO; if (getattrlist(pathName, &alist, &finfo, sizeof(fileinfobuf), 0) != 0) { return 0; } if ((types->perm & TCL_GLOB_PERM_HIDDEN) && !((finder->fdFlags & kFinfoIsInvisible) || (*fileName == '.'))) { return 0; } if (S_ISDIR(statBufPtr->st_mode) && (types->macType || types->macCreator)) { /* * Directories don't support types or creators. */ return 0; } if (types->macType) { if (GetOSTypeFromObj(interp, types->macType, &osType) != TCL_OK) { return -1; } if (osType != OSSwapBigToHostInt32(finder->type)) { return 0; } } if (types->macCreator) { if (GetOSTypeFromObj(interp, types->macCreator, &osType) != TCL_OK) { return -1; } if (osType != OSSwapBigToHostInt32(finder->creator)) { return 0; } } #endif return 1; } /* *---------------------------------------------------------------------- * * GetOSTypeFromObj -- * * Attempt to return an OSType from the Tcl object "objPtr". * * Results: * Standard TCL result. If an error occurs during conversion, an error * message is left in interp->objResult. * * Side effects: * The string representation of objPtr will be updated if necessary. * *---------------------------------------------------------------------- */ static int GetOSTypeFromObj( Tcl_Interp *interp, /* Used for error reporting if not NULL. */ Tcl_Obj *objPtr, /* The object from which to get an OSType. */ OSType *osTypePtr) /* Place to store resulting OSType. */ { int result = TCL_OK; if (objPtr->typePtr != &tclOSTypeType) { result = SetOSTypeFromAny(interp, objPtr); } *osTypePtr = (OSType) objPtr->internalRep.longValue; return result; } /* *---------------------------------------------------------------------- * * NewOSTypeObj -- * * Create a new OSType object. * * Results: * The newly created OSType object is returned, it has ref count 0. * * Side effects: * None. * *---------------------------------------------------------------------- */ static Tcl_Obj * NewOSTypeObj( const OSType osType) /* OSType used to initialize the new * object. */ { Tcl_Obj *objPtr; TclNewObj(objPtr); TclInvalidateStringRep(objPtr); objPtr->internalRep.longValue = (long) osType; objPtr->typePtr = &tclOSTypeType; return objPtr; } /* *---------------------------------------------------------------------- * * SetOSTypeFromAny -- * * Attempts to force the internal representation for a Tcl object to * tclOSTypeType, specifically. * * Results: * The return value is a standard object Tcl result. If an error occurs * during conversion, an error message is left in the interpreter's * result unless "interp" is NULL. * *---------------------------------------------------------------------- */ static int SetOSTypeFromAny( Tcl_Interp *interp, /* Tcl interpreter */ Tcl_Obj *objPtr) /* Pointer to the object to convert */ { const char *string; int result = TCL_OK; Tcl_DString ds; Tcl_Encoding encoding = Tcl_GetEncoding(NULL, "macRoman"); string = TclGetString(objPtr); Tcl_UtfToExternalDString(encoding, string, objPtr->length, &ds); if (Tcl_DStringLength(&ds) > 4) { if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "expected Macintosh OS type but got \"%s\": ", string)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "MAC_OSTYPE", NULL); } result = TCL_ERROR; } else { OSType osType; char bytes[4] = {'\0','\0','\0','\0'}; memcpy(bytes, Tcl_DStringValue(&ds), (size_t)Tcl_DStringLength(&ds)); osType = (OSType) bytes[0] << 24 | (OSType) bytes[1] << 16 | (OSType) bytes[2] << 8 | (OSType) bytes[3]; TclFreeIntRep(objPtr); objPtr->internalRep.longValue = (long) osType; objPtr->typePtr = &tclOSTypeType; } Tcl_DStringFree(&ds); Tcl_FreeEncoding(encoding); return result; } /* *---------------------------------------------------------------------- * * UpdateStringOfOSType -- * * Update the string representation for an OSType object. Note: This * function does not free an existing old string rep so storage will be * lost if this has not already been done. * * Results: * None. * * Side effects: * The object's string is set to a valid string that results from the * OSType-to-string conversion. * *---------------------------------------------------------------------- */ static void UpdateStringOfOSType( register Tcl_Obj *objPtr) /* OSType object whose string rep to * update. */ { const int size = TCL_UTF_MAX * 4; char *dst = Tcl_InitStringRep(objPtr, NULL, size); OSType osType = (OSType) objPtr->internalRep.longValue; int written = 0; Tcl_Encoding encoding; char src[5]; TclOOM(dst, size); src[0] = (char) (osType >> 24); src[1] = (char) (osType >> 16); src[2] = (char) (osType >> 8); src[3] = (char) (osType); src[4] = '\0'; encoding = Tcl_GetEncoding(NULL, "macRoman"); Tcl_ExternalToUtf(NULL, encoding, src, -1, /* flags */ 0, /* statePtr */ NULL, dst, size, /* srcReadPtr */ NULL, /* dstWrotePtr */ &written, /* dstCharsPtr */ NULL); Tcl_FreeEncoding(encoding); (void)Tcl_InitStringRep(objPtr, NULL, written); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */