Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: downloads/tcl8.5.2/unix/tclUnixFCmd.c @ 25

Last change on this file since 25 was 25, checked in by landauf, 16 years ago

added tcl to libs

File size: 57.3 KB
Line 
1/*
2 * tclUnixFCmd.c
3 *
4 *      This file implements the unix specific portion of file manipulation
5 *      subcommands of the "file" command. All filename arguments should
6 *      already be translated to native format.
7 *
8 * Copyright (c) 1996-1998 Sun Microsystems, Inc.
9 *
10 * See the file "license.terms" for information on usage and redistribution of
11 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
12 *
13 * RCS: @(#) $Id: tclUnixFCmd.c,v 1.65 2007/12/13 15:28:42 dgp Exp $
14 *
15 * Portions of this code were derived from NetBSD source code which has the
16 * following copyright notice:
17 *
18 * Copyright (c) 1988, 1993, 1994
19 *      The Regents of the University of California. All rights reserved.
20 *
21 * Redistribution and use in source and binary forms, with or without
22 * modification, are permitted provided that the following conditions are met:
23 * 1. Redistributions of source code must retain the above copyright notice,
24 *    this list of conditions and the following disclaimer.
25 * 2. Redistributions in binary form must reproduce the above copyright
26 *    notice, this list of conditions and the following disclaimer in the
27 *    documentation and/or other materials provided with the distribution.
28 * 3. All advertising materials mentioning features or use of this software
29 *    must display the following acknowledgement:
30 *      This product includes software developed by the University of
31 *      California, Berkeley and its contributors.
32 * 4. Neither the name of the University nor the names of its contributors may
33 *    be used to endorse or promote products derived from this software
34 *    without specific prior written permission.
35 *
36 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
37 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
38 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
40 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
41 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
42 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
43 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
44 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
45 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
46 * DAMAGE.
47 */
48
49#include "tclInt.h"
50#include <utime.h>
51#include <grp.h>
52#ifndef HAVE_ST_BLKSIZE
53#ifndef NO_FSTATFS
54#include <sys/statfs.h>
55#endif
56#endif
57#ifdef HAVE_FTS
58#include <fts.h>
59#endif
60
61/*
62 * The following constants specify the type of callback when
63 * TraverseUnixTree() calls the traverseProc()
64 */
65
66#define DOTREE_PRED     1       /* pre-order directory */
67#define DOTREE_POSTD    2       /* post-order directory */
68#define DOTREE_F        3       /* regular file */
69
70/*
71 * Callbacks for file attributes code.
72 */
73
74static int              GetGroupAttribute(Tcl_Interp *interp, int objIndex,
75                            Tcl_Obj *fileName, Tcl_Obj **attributePtrPtr);
76static int              GetOwnerAttribute(Tcl_Interp *interp, int objIndex,
77                            Tcl_Obj *fileName, Tcl_Obj **attributePtrPtr);
78static int              GetPermissionsAttribute(Tcl_Interp *interp,
79                            int objIndex, Tcl_Obj *fileName,
80                            Tcl_Obj **attributePtrPtr);
81static int              SetGroupAttribute(Tcl_Interp *interp, int objIndex,
82                            Tcl_Obj *fileName, Tcl_Obj *attributePtr);
83static int              SetOwnerAttribute(Tcl_Interp *interp, int objIndex,
84                            Tcl_Obj *fileName, Tcl_Obj *attributePtr);
85static int              SetPermissionsAttribute(Tcl_Interp *interp,
86                            int objIndex, Tcl_Obj *fileName,
87                            Tcl_Obj *attributePtr);
88static int              GetModeFromPermString(Tcl_Interp *interp,
89                            char *modeStringPtr, mode_t *modePtr);
90#if defined(HAVE_CHFLAGS) && defined(UF_IMMUTABLE)
91static int              GetReadOnlyAttribute(Tcl_Interp *interp, int objIndex,
92                            Tcl_Obj *fileName, Tcl_Obj **attributePtrPtr);
93static int              SetReadOnlyAttribute(Tcl_Interp *interp, int objIndex,
94                            Tcl_Obj *fileName, Tcl_Obj *attributePtr);
95#endif
96
97/*
98 * Prototype for the TraverseUnixTree callback function.
99 */
100
101typedef int (TraversalProc)(Tcl_DString *srcPtr, Tcl_DString *dstPtr,
102        CONST Tcl_StatBuf *statBufPtr, int type, Tcl_DString *errorPtr);
103
104/*
105 * Constants and variables necessary for file attributes subcommand.
106 *
107 * IMPORTANT: The permissions attribute is assumed to be the third item (i.e.
108 * to be indexed with '2' in arrays) in code in tclIOUtil.c and possibly
109 * elsewhere in Tcl's core.
110 */
111
112#ifdef DJGPP
113
114/*
115 * See contrib/djgpp/tclDjgppFCmd.c for definition.
116 */
117
118extern TclFileAttrProcs tclpFileAttrProcs[];
119extern char *tclpFileAttrStrings[];
120
121#else
122enum {
123    UNIX_GROUP_ATTRIBUTE, UNIX_OWNER_ATTRIBUTE, UNIX_PERMISSIONS_ATTRIBUTE,
124#if defined(HAVE_CHFLAGS) && defined(UF_IMMUTABLE)
125    UNIX_READONLY_ATTRIBUTE,
126#endif
127#ifdef MAC_OSX_TCL
128    MACOSX_CREATOR_ATTRIBUTE, MACOSX_TYPE_ATTRIBUTE, MACOSX_HIDDEN_ATTRIBUTE,
129    MACOSX_RSRCLENGTH_ATTRIBUTE,
130#endif
131    UNIX_INVALID_ATTRIBUTE /* lint - last enum value needs no trailing , */
132};
133
134MODULE_SCOPE CONST char *tclpFileAttrStrings[];
135CONST char *tclpFileAttrStrings[] = {
136    "-group", "-owner", "-permissions",
137#if defined(HAVE_CHFLAGS) && defined(UF_IMMUTABLE)
138    "-readonly",
139#endif
140#ifdef MAC_OSX_TCL
141    "-creator", "-type", "-hidden", "-rsrclength",
142#endif
143    NULL
144};
145
146MODULE_SCOPE CONST TclFileAttrProcs tclpFileAttrProcs[];
147CONST TclFileAttrProcs tclpFileAttrProcs[] = {
148    {GetGroupAttribute, SetGroupAttribute},
149    {GetOwnerAttribute, SetOwnerAttribute},
150    {GetPermissionsAttribute, SetPermissionsAttribute},
151#if defined(HAVE_CHFLAGS) && defined(UF_IMMUTABLE)
152    {GetReadOnlyAttribute, SetReadOnlyAttribute},
153#endif
154#ifdef MAC_OSX_TCL
155    {TclMacOSXGetFileAttribute, TclMacOSXSetFileAttribute},
156    {TclMacOSXGetFileAttribute, TclMacOSXSetFileAttribute},
157    {TclMacOSXGetFileAttribute, TclMacOSXSetFileAttribute},
158    {TclMacOSXGetFileAttribute, TclMacOSXSetFileAttribute},
159#endif
160};
161#endif
162
163/*
164 * This is the maximum number of consecutive readdir/unlink calls that can be
165 * made (with no intervening rewinddir or closedir/opendir) before triggering
166 * a bug that makes readdir return NULL even though some directory entries
167 * have not been processed. The bug afflicts SunOS's readdir when applied to
168 * ufs file systems and Darwin 6.5's (and OSX v.10.3.8's) HFS+. JH found the
169 * Darwin readdir to reset at 147, so 130 is chosen to be conservative. We
170 * can't do a general rewind on failure as NFS can create special files that
171 * recreate themselves when you try and delete them. 8.4.8 added a solution
172 * that was affected by a single such NFS file, this solution should not be
173 * affected by less than THRESHOLD such files. [Bug 1034337]
174 */
175
176#define MAX_READDIR_UNLINK_THRESHOLD 130
177
178/*
179 * Declarations for local procedures defined in this file:
180 */
181
182static int              CopyFileAtts(CONST char *src,
183                            CONST char *dst, CONST Tcl_StatBuf *statBufPtr);
184static int              DoCopyFile(CONST char *srcPtr, CONST char *dstPtr,
185                            CONST Tcl_StatBuf *statBufPtr);
186static int              DoCreateDirectory(CONST char *pathPtr);
187static int              DoRemoveDirectory(Tcl_DString *pathPtr,
188                            int recursive, Tcl_DString *errorPtr);
189static int              DoRenameFile(CONST char *src, CONST char *dst);
190static int              TraversalCopy(Tcl_DString *srcPtr,
191                            Tcl_DString *dstPtr, CONST Tcl_StatBuf *statBufPtr,
192                            int type, Tcl_DString *errorPtr);
193static int              TraversalDelete(Tcl_DString *srcPtr,
194                            Tcl_DString *dstPtr, CONST Tcl_StatBuf *statBufPtr,
195                            int type, Tcl_DString *errorPtr);
196static int              TraverseUnixTree(TraversalProc *traversalProc,
197                            Tcl_DString *sourcePtr, Tcl_DString *destPtr,
198                            Tcl_DString *errorPtr, int doRewind);
199
200#ifdef PURIFY
201/*
202 * realpath and purify don't mix happily. It has been noted that realpath
203 * should not be used with purify because of bogus warnings, but just
204 * memset'ing the resolved path will squelch those. This assumes we are
205 * passing the standard MAXPATHLEN size resolved arg.
206 */
207
208static char *           Realpath(CONST char *path, char *resolved);
209
210char *
211Realpath(
212    CONST char *path,
213    char *resolved)
214{
215    memset(resolved, 0, MAXPATHLEN);
216    return realpath(path, resolved);
217}
218#else
219#define Realpath realpath
220#endif
221
222#ifndef NO_REALPATH
223#if defined(__APPLE__) && defined(TCL_THREADS) && \
224        defined(MAC_OS_X_VERSION_MIN_REQUIRED) && \
225        MAC_OS_X_VERSION_MIN_REQUIRED < 1030
226/*
227 * Prior to Darwin 7, realpath is not thread-safe, c.f. Bug 711232; if we
228 * might potentially be running on pre-10.3 OSX, check Darwin release at
229 * runtime before using realpath.
230 */
231
232MODULE_SCOPE long tclMacOSXDarwinRelease;
233#define haveRealpath (tclMacOSXDarwinRelease >= 7)
234#else
235#define haveRealpath 1
236#endif
237#endif /* NO_REALPATH */
238
239#ifdef HAVE_FTS
240#ifdef HAVE_STRUCT_STAT64
241/* fts doesn't do stat64 */
242#define noFtsStat 1
243#elif defined(__APPLE__) && defined(__LP64__) && \
244        defined(MAC_OS_X_VERSION_MIN_REQUIRED) && \
245        MAC_OS_X_VERSION_MIN_REQUIRED < 1050
246/*
247 * Prior to Darwin 9, 64bit fts_open() without FTS_NOSTAT may crash (due to a
248 * 64bit-unsafe ALIGN macro); if we could be running on pre-10.5 OSX, check
249 * Darwin release at runtime and do a separate stat() if necessary.
250 */
251
252MODULE_SCOPE long tclMacOSXDarwinRelease;
253#define noFtsStat (tclMacOSXDarwinRelease < 9)
254#else
255#define noFtsStat 0
256#endif
257#endif /* HAVE_FTS */
258
259/*
260 *---------------------------------------------------------------------------
261 *
262 * TclpObjRenameFile, DoRenameFile --
263 *
264 *      Changes the name of an existing file or directory, from src to dst. If
265 *      src and dst refer to the same file or directory, does nothing and
266 *      returns success. Otherwise if dst already exists, it will be deleted
267 *      and replaced by src subject to the following conditions:
268 *          If src is a directory, dst may be an empty directory.
269 *          If src is a file, dst may be a file.
270 *      In any other situation where dst already exists, the rename will fail.
271 *
272 * Results:
273 *      If the directory was successfully created, returns TCL_OK. Otherwise
274 *      the return value is TCL_ERROR and errno is set to indicate the error.
275 *      Some possible values for errno are:
276 *
277 *      EACCES:     src or dst parent directory can't be read and/or written.
278 *      EEXIST:     dst is a non-empty directory.
279 *      EINVAL:     src is a root directory or dst is a subdirectory of src.
280 *      EISDIR:     dst is a directory, but src is not.
281 *      ENOENT:     src doesn't exist, or src or dst is "".
282 *      ENOTDIR:    src is a directory, but dst is not.
283 *      EXDEV:      src and dst are on different filesystems.
284 *
285 * Side effects:
286 *      The implementation of rename may allow cross-filesystem renames, but
287 *      the caller should be prepared to emulate it with copy and delete if
288 *      errno is EXDEV.
289 *
290 *---------------------------------------------------------------------------
291 */
292
293int
294TclpObjRenameFile(
295    Tcl_Obj *srcPathPtr,
296    Tcl_Obj *destPathPtr)
297{
298    return DoRenameFile(Tcl_FSGetNativePath(srcPathPtr),
299            Tcl_FSGetNativePath(destPathPtr));
300}
301
302static int
303DoRenameFile(
304    CONST char *src,            /* Pathname of file or dir to be renamed
305                                 * (native). */
306    CONST char *dst)            /* New pathname of file or directory
307                                 * (native). */
308{
309    if (rename(src, dst) == 0) {                        /* INTL: Native. */
310        return TCL_OK;
311    }
312    if (errno == ENOTEMPTY) {
313        errno = EEXIST;
314    }
315
316    /*
317     * IRIX returns EIO when you attept to move a directory into itself. We
318     * just map EIO to EINVAL get the right message on SGI. Most platforms
319     * don't return EIO except in really strange cases.
320     */
321
322    if (errno == EIO) {
323        errno = EINVAL;
324    }
325
326#ifndef NO_REALPATH
327    /*
328     * SunOS 4.1.4 reports overwriting a non-empty directory with a directory
329     * as EINVAL instead of EEXIST (first rule out the correct EINVAL result
330     * code for moving a directory into itself). Must be conditionally
331     * compiled because realpath() not defined on all systems.
332     */
333
334    if (errno == EINVAL && haveRealpath) {
335        char srcPath[MAXPATHLEN], dstPath[MAXPATHLEN];
336        DIR *dirPtr;
337        Tcl_DirEntry *dirEntPtr;
338
339        if ((Realpath((char *) src, srcPath) != NULL)   /* INTL: Native. */
340                && (Realpath((char *) dst, dstPath) != NULL) /* INTL: Native */
341                && (strncmp(srcPath, dstPath, strlen(srcPath)) != 0)) {
342            dirPtr = opendir(dst);                      /* INTL: Native. */
343            if (dirPtr != NULL) {
344                while (1) {
345                    dirEntPtr = TclOSreaddir(dirPtr);   /* INTL: Native. */
346                    if (dirEntPtr == NULL) {
347                        break;
348                    }
349                    if ((strcmp(dirEntPtr->d_name, ".") != 0) &&
350                            (strcmp(dirEntPtr->d_name, "..") != 0)) {
351                        errno = EEXIST;
352                        closedir(dirPtr);
353                        return TCL_ERROR;
354                    }
355                }
356                closedir(dirPtr);
357            }
358        }
359        errno = EINVAL;
360    }
361#endif  /* !NO_REALPATH */
362
363    if (strcmp(src, "/") == 0) {
364        /*
365         * Alpha reports renaming / as EBUSY and Linux reports it as EACCES,
366         * instead of EINVAL.
367         */
368
369        errno = EINVAL;
370    }
371
372    /*
373     * DEC Alpha OSF1 V3.0 returns EACCES when attempting to move a file
374     * across filesystems and the parent directory of that file is not
375     * writable. Most other systems return EXDEV. Does nothing to correct this
376     * behavior.
377     */
378
379    return TCL_ERROR;
380}
381
382/*
383 *---------------------------------------------------------------------------
384 *
385 * TclpObjCopyFile, DoCopyFile --
386 *
387 *      Copy a single file (not a directory). If dst already exists and is not
388 *      a directory, it is removed.
389 *
390 * Results:
391 *      If the file was successfully copied, returns TCL_OK. Otherwise the
392 *      return value is TCL_ERROR and errno is set to indicate the error. Some
393 *      possible values for errno are:
394 *
395 *      EACCES:     src or dst parent directory can't be read and/or written.
396 *      EISDIR:     src or dst is a directory.
397 *      ENOENT:     src doesn't exist. src or dst is "".
398 *
399 * Side effects:
400 *      This procedure will also copy symbolic links, block, and character
401 *      devices, and fifos. For symbolic links, the links themselves will be
402 *      copied and not what they point to. For the other special file types,
403 *      the directory entry will be copied and not the contents of the device
404 *      that it refers to.
405 *
406 *---------------------------------------------------------------------------
407 */
408
409int
410TclpObjCopyFile(
411    Tcl_Obj *srcPathPtr,
412    Tcl_Obj *destPathPtr)
413{
414    CONST char *src = Tcl_FSGetNativePath(srcPathPtr);
415    Tcl_StatBuf srcStatBuf;
416
417    if (TclOSlstat(src, &srcStatBuf) != 0) {            /* INTL: Native. */
418        return TCL_ERROR;
419    }
420
421    return DoCopyFile(src, Tcl_FSGetNativePath(destPathPtr), &srcStatBuf);
422}
423
424static int
425DoCopyFile(
426    CONST char *src,            /* Pathname of file to be copied (native). */
427    CONST char *dst,            /* Pathname of file to copy to (native). */
428    CONST Tcl_StatBuf *statBufPtr)
429                                /* Used to determine filetype. */
430{
431    Tcl_StatBuf dstStatBuf;
432
433    if (S_ISDIR(statBufPtr->st_mode)) {
434        errno = EISDIR;
435        return TCL_ERROR;
436    }
437
438    /*
439     * Symlink, and some of the other calls will fail if the target exists, so
440     * we remove it first.
441     */
442
443    if (TclOSlstat(dst, &dstStatBuf) == 0) {            /* INTL: Native. */
444        if (S_ISDIR(dstStatBuf.st_mode)) {
445            errno = EISDIR;
446            return TCL_ERROR;
447        }
448    }
449    if (unlink(dst) != 0) {                             /* INTL: Native. */
450        if (errno != ENOENT) {
451            return TCL_ERROR;
452        }
453    }
454
455    switch ((int) (statBufPtr->st_mode & S_IFMT)) {
456#ifndef DJGPP
457    case S_IFLNK: {
458        char link[MAXPATHLEN];
459        int length;
460
461        length = readlink(src, link, sizeof(link));     /* INTL: Native. */
462        if (length == -1) {
463            return TCL_ERROR;
464        }
465        link[length] = '\0';
466        if (symlink(link, dst) < 0) {                   /* INTL: Native. */
467            return TCL_ERROR;
468        }
469#ifdef MAC_OSX_TCL
470        TclMacOSXCopyFileAttributes(src, dst, statBufPtr);
471#endif
472        break;
473    }
474#endif
475    case S_IFBLK:
476    case S_IFCHR:
477        if (mknod(dst, statBufPtr->st_mode,             /* INTL: Native. */
478                statBufPtr->st_rdev) < 0) {
479            return TCL_ERROR;
480        }
481        return CopyFileAtts(src, dst, statBufPtr);
482    case S_IFIFO:
483        if (mkfifo(dst, statBufPtr->st_mode) < 0) {     /* INTL: Native. */
484            return TCL_ERROR;
485        }
486        return CopyFileAtts(src, dst, statBufPtr);
487    default:
488        return TclUnixCopyFile(src, dst, statBufPtr, 0);
489    }
490    return TCL_OK;
491}
492
493/*
494 *----------------------------------------------------------------------
495 *
496 * TclUnixCopyFile -
497 *
498 *      Helper function for TclpCopyFile. Copies one regular file, using
499 *      read() and write().
500 *
501 * Results:
502 *      A standard Tcl result.
503 *
504 * Side effects:
505 *      A file is copied. Dst will be overwritten if it exists.
506 *
507 *----------------------------------------------------------------------
508 */
509
510int
511TclUnixCopyFile(
512    CONST char *src,            /* Pathname of file to copy (native). */
513    CONST char *dst,            /* Pathname of file to create/overwrite
514                                 * (native). */
515    CONST Tcl_StatBuf *statBufPtr,
516                                /* Used to determine mode and blocksize. */
517    int dontCopyAtts)           /* If flag set, don't copy attributes. */
518{
519    int srcFd, dstFd;
520    unsigned blockSize;         /* Optimal I/O blocksize for filesystem */
521    char *buffer;               /* Data buffer for copy */
522    size_t nread;
523
524#ifdef DJGPP
525#define BINMODE |O_BINARY
526#else
527#define BINMODE
528#endif
529
530    if ((srcFd = TclOSopen(src, O_RDONLY BINMODE, 0)) < 0) { /* INTL: Native */
531        return TCL_ERROR;
532    }
533
534    dstFd = TclOSopen(dst, O_CREAT|O_TRUNC|O_WRONLY BINMODE, /* INTL: Native */
535            statBufPtr->st_mode);
536    if (dstFd < 0) {
537        close(srcFd);
538        return TCL_ERROR;
539    }
540
541    /*
542     * Try to work out the best size of buffer to use for copying. If we
543     * can't, it's no big deal as we can just use a (32-bit) page, since
544     * that's likely to be fairly efficient anyway.
545     */
546
547#ifdef HAVE_ST_BLKSIZE
548    blockSize = statBufPtr->st_blksize;
549#elif !defined(NO_FSTATFS)
550    {
551        struct statfs fs;
552
553        if (fstatfs(srcFd, &fs, sizeof(fs), 0) == 0) {
554            blockSize = fs.f_bsize;
555        } else {
556            blockSize = 4096;
557        }
558    }
559#else
560    blockSize = 4096;
561#endif /* HAVE_ST_BLKSIZE */
562
563    /*
564     * [SF Tcl Bug 1586470] Even if we HAVE_ST_BLKSIZE, there are filesystems
565     * which report a bogus value for the blocksize. An example is the Andrew
566     * Filesystem (afs), reporting a blocksize of 0. When detecting such a
567     * situation we now simply fall back to a hardwired default size.
568     */
569
570    if (blockSize <= 0) {
571        blockSize = 4096;
572    }
573    buffer = ckalloc(blockSize);
574    while (1) {
575        nread = (size_t) read(srcFd, buffer, blockSize);
576        if ((nread == (size_t) -1) || (nread == 0)) {
577            break;
578        }
579        if ((size_t) write(dstFd, buffer, nread) != nread) {
580            nread = (size_t) -1;
581            break;
582        }
583    }
584
585    ckfree(buffer);
586    close(srcFd);
587    if ((close(dstFd) != 0) || (nread == (size_t) -1)) {
588        unlink(dst);                                    /* INTL: Native. */
589        return TCL_ERROR;
590    }
591    if (!dontCopyAtts && CopyFileAtts(src, dst, statBufPtr) == TCL_ERROR) {
592        /*
593         * The copy succeeded, but setting the permissions failed, so be in a
594         * consistent state, we remove the file that was created by the copy.
595         */
596
597        unlink(dst);                                    /* INTL: Native. */
598        return TCL_ERROR;
599    }
600    return TCL_OK;
601}
602
603/*
604 *---------------------------------------------------------------------------
605 *
606 * TclpObjDeleteFile, TclpDeleteFile --
607 *
608 *      Removes a single file (not a directory).
609 *
610 * Results:
611 *      If the file was successfully deleted, returns TCL_OK. Otherwise the
612 *      return value is TCL_ERROR and errno is set to indicate the error. Some
613 *      possible values for errno are:
614 *
615 *      EACCES:     a parent directory can't be read and/or written.
616 *      EISDIR:     path is a directory.
617 *      ENOENT:     path doesn't exist or is "".
618 *
619 * Side effects:
620 *      The file is deleted, even if it is read-only.
621 *
622 *---------------------------------------------------------------------------
623 */
624
625int
626TclpObjDeleteFile(
627    Tcl_Obj *pathPtr)
628{
629    return TclpDeleteFile(Tcl_FSGetNativePath(pathPtr));
630}
631
632int
633TclpDeleteFile(
634    CONST char *path)           /* Pathname of file to be removed (native). */
635{
636    if (unlink(path) != 0) {                            /* INTL: Native. */
637        return TCL_ERROR;
638    }
639    return TCL_OK;
640}
641
642/*
643 *---------------------------------------------------------------------------
644 *
645 * TclpCreateDirectory, DoCreateDirectory --
646 *
647 *      Creates the specified directory. All parent directories of the
648 *      specified directory must already exist. The directory is automatically
649 *      created with permissions so that user can access the new directory and
650 *      create new files or subdirectories in it.
651 *
652 * Results:
653 *      If the directory was successfully created, returns TCL_OK. Otherwise
654 *      the return value is TCL_ERROR and errno is set to indicate the error.
655 *      Some possible values for errno are:
656 *
657 *      EACCES:     a parent directory can't be read and/or written.
658 *      EEXIST:     path already exists.
659 *      ENOENT:     a parent directory doesn't exist.
660 *
661 * Side effects:
662 *      A directory is created with the current umask, except that permission
663 *      for u+rwx will always be added.
664 *
665 *---------------------------------------------------------------------------
666 */
667
668int
669TclpObjCreateDirectory(
670    Tcl_Obj *pathPtr)
671{
672    return DoCreateDirectory(Tcl_FSGetNativePath(pathPtr));
673}
674
675static int
676DoCreateDirectory(
677    CONST char *path)           /* Pathname of directory to create (native). */
678{
679    mode_t mode;
680
681    mode = umask(0);
682    umask(mode);
683
684    /*
685     * umask return value is actually the inverse of the permissions.
686     */
687
688    mode = (0777 & ~mode) | S_IRUSR | S_IWUSR | S_IXUSR;
689
690    if (mkdir(path, mode) != 0) {                       /* INTL: Native. */
691        return TCL_ERROR;
692    }
693    return TCL_OK;
694}
695
696/*
697 *---------------------------------------------------------------------------
698 *
699 * TclpObjCopyDirectory --
700 *
701 *      Recursively copies a directory. The target directory dst must not
702 *      already exist. Note that this function does not merge two directory
703 *      hierarchies, even if the target directory is an an empty directory.
704 *
705 * Results:
706 *      If the directory was successfully copied, returns TCL_OK. Otherwise
707 *      the return value is TCL_ERROR, errno is set to indicate the error, and
708 *      the pathname of the file that caused the error is stored in errorPtr.
709 *      See TclpObjCreateDirectory and TclpObjCopyFile for a description of
710 *      possible values for errno.
711 *
712 * Side effects:
713 *      An exact copy of the directory hierarchy src will be created with the
714 *      name dst. If an error occurs, the error will be returned immediately,
715 *      and remaining files will not be processed.
716 *
717 *---------------------------------------------------------------------------
718 */
719
720int
721TclpObjCopyDirectory(
722    Tcl_Obj *srcPathPtr,
723    Tcl_Obj *destPathPtr,
724    Tcl_Obj **errorPtr)
725{
726    Tcl_DString ds;
727    Tcl_DString srcString, dstString;
728    int ret;
729    Tcl_Obj *transPtr;
730
731    transPtr = Tcl_FSGetTranslatedPath(NULL,srcPathPtr);
732    Tcl_UtfToExternalDString(NULL,
733            (transPtr != NULL ? TclGetString(transPtr) : NULL),
734            -1, &srcString);
735    if (transPtr != NULL) {
736        Tcl_DecrRefCount(transPtr);
737    }
738    transPtr = Tcl_FSGetTranslatedPath(NULL,destPathPtr);
739    Tcl_UtfToExternalDString(NULL,
740            (transPtr != NULL ? TclGetString(transPtr) : NULL),
741            -1, &dstString);
742    if (transPtr != NULL) {
743        Tcl_DecrRefCount(transPtr);
744    }
745
746    ret = TraverseUnixTree(TraversalCopy, &srcString, &dstString, &ds, 0);
747
748    Tcl_DStringFree(&srcString);
749    Tcl_DStringFree(&dstString);
750
751    if (ret != TCL_OK) {
752        *errorPtr = Tcl_NewStringObj(Tcl_DStringValue(&ds), -1);
753        Tcl_DStringFree(&ds);
754        Tcl_IncrRefCount(*errorPtr);
755    }
756    return ret;
757}
758
759/*
760 *---------------------------------------------------------------------------
761 *
762 * TclpRemoveDirectory, DoRemoveDirectory --
763 *
764 *      Removes directory (and its contents, if the recursive flag is set).
765 *
766 * Results:
767 *      If the directory was successfully removed, returns TCL_OK. Otherwise
768 *      the return value is TCL_ERROR, errno is set to indicate the error, and
769 *      the pathname of the file that caused the error is stored in errorPtr.
770 *      Some possible values for errno are:
771 *
772 *      EACCES:     path directory can't be read and/or written.
773 *      EEXIST:     path is a non-empty directory.
774 *      EINVAL:     path is a root directory.
775 *      ENOENT:     path doesn't exist or is "".
776 *      ENOTDIR:    path is not a directory.
777 *
778 * Side effects:
779 *      Directory removed. If an error occurs, the error will be returned
780 *      immediately, and remaining files will not be deleted.
781 *
782 *---------------------------------------------------------------------------
783 */
784
785int
786TclpObjRemoveDirectory(
787    Tcl_Obj *pathPtr,
788    int recursive,
789    Tcl_Obj **errorPtr)
790{
791    Tcl_DString ds;
792    Tcl_DString pathString;
793    int ret;
794    Tcl_Obj *transPtr = Tcl_FSGetTranslatedPath(NULL, pathPtr);
795
796    Tcl_UtfToExternalDString(NULL,
797            (transPtr != NULL ? TclGetString(transPtr) : NULL),
798            -1, &pathString);
799    if (transPtr != NULL) {
800        Tcl_DecrRefCount(transPtr);
801    }
802    ret = DoRemoveDirectory(&pathString, recursive, &ds);
803    Tcl_DStringFree(&pathString);
804
805    if (ret != TCL_OK) {
806        *errorPtr = Tcl_NewStringObj(Tcl_DStringValue(&ds), -1);
807        Tcl_DStringFree(&ds);
808        Tcl_IncrRefCount(*errorPtr);
809    }
810    return ret;
811}
812
813static int
814DoRemoveDirectory(
815    Tcl_DString *pathPtr,       /* Pathname of directory to be removed
816                                 * (native). */
817    int recursive,              /* If non-zero, removes directories that are
818                                 * nonempty. Otherwise, will only remove empty
819                                 * directories. */
820    Tcl_DString *errorPtr)      /* If non-NULL, uninitialized or free DString
821                                 * filled with UTF-8 name of file causing
822                                 * error. */
823{
824    CONST char *path;
825    mode_t oldPerm = 0;
826    int result;
827
828    path = Tcl_DStringValue(pathPtr);
829
830    if (recursive != 0) {
831        /*
832         * We should try to change permissions so this can be deleted.
833         */
834
835        Tcl_StatBuf statBuf;
836        int newPerm;
837
838        if (TclOSstat(path, &statBuf) == 0) {
839            oldPerm = (mode_t) (statBuf.st_mode & 0x00007FFF);
840        }
841
842        newPerm = oldPerm | (64+128+256);
843        chmod(path, (mode_t) newPerm);
844    }
845
846    if (rmdir(path) == 0) {                             /* INTL: Native. */
847        return TCL_OK;
848    }
849    if (errno == ENOTEMPTY) {
850        errno = EEXIST;
851    }
852
853    result = TCL_OK;
854    if ((errno != EEXIST) || (recursive == 0)) {
855        if (errorPtr != NULL) {
856            Tcl_ExternalToUtfDString(NULL, path, -1, errorPtr);
857        }
858        result = TCL_ERROR;
859    }
860
861    /*
862     * The directory is nonempty, but the recursive flag has been specified,
863     * so we recursively remove all the files in the directory.
864     */
865
866    if (result == TCL_OK) {
867        result = TraverseUnixTree(TraversalDelete, pathPtr, NULL, errorPtr, 1);
868    }
869
870    if ((result != TCL_OK) && (recursive != 0)) {
871        /*
872         * Try to restore permissions.
873         */
874
875        chmod(path, oldPerm);
876    }
877    return result;
878}
879
880/*
881 *---------------------------------------------------------------------------
882 *
883 * TraverseUnixTree --
884 *
885 *      Traverse directory tree specified by sourcePtr, calling the function
886 *      traverseProc for each file and directory encountered. If destPtr is
887 *      non-null, each of name in the sourcePtr directory is appended to the
888 *      directory specified by destPtr and passed as the second argument to
889 *      traverseProc().
890 *
891 * Results:
892 *      Standard Tcl result.
893 *
894 * Side effects:
895 *      None caused by TraverseUnixTree, however the user specified
896 *      traverseProc() may change state. If an error occurs, the error will be
897 *      returned immediately, and remaining files will not be processed.
898 *
899 *---------------------------------------------------------------------------
900 */
901
902static int
903TraverseUnixTree(
904    TraversalProc *traverseProc,/* Function to call for every file and
905                                 * directory in source hierarchy. */
906    Tcl_DString *sourcePtr,     /* Pathname of source directory to be
907                                 * traversed (native). */
908    Tcl_DString *targetPtr,     /* Pathname of directory to traverse in
909                                 * parallel with source directory (native). */
910    Tcl_DString *errorPtr,      /* If non-NULL, uninitialized or free DString
911                                 * filled with UTF-8 name of file causing
912                                 * error. */
913    int doRewind)               /* Flag indicating that to ensure complete
914                                 * traversal of source hierarchy, the readdir
915                                 * loop should be rewound whenever
916                                 * traverseProc has returned TCL_OK; this is
917                                 * required when traverseProc modifies the
918                                 * source hierarchy, e.g. by deleting
919                                 * files. */
920{
921    Tcl_StatBuf statBuf;
922    CONST char *source, *errfile;
923    int result, sourceLen;
924    int targetLen;
925#ifndef HAVE_FTS
926    int numProcessed = 0;
927    Tcl_DirEntry *dirEntPtr;
928    DIR *dirPtr;
929#else
930    CONST char *paths[2] = {NULL, NULL};
931    FTS *fts = NULL;
932    FTSENT *ent;
933#endif
934
935    errfile = NULL;
936    result = TCL_OK;
937    targetLen = 0;              /* lint. */
938
939    source = Tcl_DStringValue(sourcePtr);
940    if (TclOSlstat(source, &statBuf) != 0) {            /* INTL: Native. */
941        errfile = source;
942        goto end;
943    }
944    if (!S_ISDIR(statBuf.st_mode)) {
945        /*
946         * Process the regular file
947         */
948
949        return (*traverseProc)(sourcePtr, targetPtr, &statBuf, DOTREE_F,
950                errorPtr);
951    }
952#ifndef HAVE_FTS
953    dirPtr = opendir(source);                           /* INTL: Native. */
954    if (dirPtr == NULL) {
955        /*
956         * Can't read directory
957         */
958
959        errfile = source;
960        goto end;
961    }
962    result = (*traverseProc)(sourcePtr, targetPtr, &statBuf, DOTREE_PRED,
963            errorPtr);
964    if (result != TCL_OK) {
965        closedir(dirPtr);
966        return result;
967    }
968
969    Tcl_DStringAppend(sourcePtr, "/", 1);
970    sourceLen = Tcl_DStringLength(sourcePtr);
971
972    if (targetPtr != NULL) {
973        Tcl_DStringAppend(targetPtr, "/", 1);
974        targetLen = Tcl_DStringLength(targetPtr);
975    }
976
977    while ((dirEntPtr = TclOSreaddir(dirPtr)) != NULL) { /* INTL: Native. */
978        if ((dirEntPtr->d_name[0] == '.')
979                && ((dirEntPtr->d_name[1] == '\0')
980                        || (strcmp(dirEntPtr->d_name, "..") == 0))) {
981            continue;
982        }
983
984        /*
985         * Append name after slash, and recurse on the file.
986         */
987
988        Tcl_DStringAppend(sourcePtr, dirEntPtr->d_name, -1);
989        if (targetPtr != NULL) {
990            Tcl_DStringAppend(targetPtr, dirEntPtr->d_name, -1);
991        }
992        result = TraverseUnixTree(traverseProc, sourcePtr, targetPtr,
993                errorPtr, doRewind);
994        if (result != TCL_OK) {
995            break;
996        } else {
997            numProcessed++;
998        }
999
1000        /*
1001         * Remove name after slash.
1002         */
1003
1004        Tcl_DStringSetLength(sourcePtr, sourceLen);
1005        if (targetPtr != NULL) {
1006            Tcl_DStringSetLength(targetPtr, targetLen);
1007        }
1008        if (doRewind && (numProcessed > MAX_READDIR_UNLINK_THRESHOLD)) {
1009            /*
1010             * Call rewinddir if we've called unlink or rmdir so many times
1011             * (since the opendir or the previous rewinddir), to avoid a
1012             * NULL-return that may a symptom of a buggy readdir.
1013             */
1014
1015            rewinddir(dirPtr);
1016            numProcessed = 0;
1017        }
1018    }
1019    closedir(dirPtr);
1020
1021    /*
1022     * Strip off the trailing slash we added
1023     */
1024
1025    Tcl_DStringSetLength(sourcePtr, sourceLen - 1);
1026    if (targetPtr != NULL) {
1027        Tcl_DStringSetLength(targetPtr, targetLen - 1);
1028    }
1029
1030    if (result == TCL_OK) {
1031        /*
1032         * Call traverseProc() on a directory after visiting all the files in
1033         * that directory.
1034         */
1035
1036        result = (*traverseProc)(sourcePtr, targetPtr, &statBuf, DOTREE_POSTD,
1037                errorPtr);
1038    }
1039#else /* HAVE_FTS */
1040    paths[0] = source;
1041    fts = fts_open((char**)paths, FTS_PHYSICAL | FTS_NOCHDIR |
1042            (noFtsStat || doRewind ? FTS_NOSTAT : 0), NULL);
1043    if (fts == NULL) {
1044        errfile = source;
1045        goto end;
1046    }
1047
1048    sourceLen = Tcl_DStringLength(sourcePtr);
1049    if (targetPtr != NULL) {
1050        targetLen = Tcl_DStringLength(targetPtr);
1051    }
1052
1053    while ((ent = fts_read(fts)) != NULL) {
1054        unsigned short info = ent->fts_info;
1055        char *path = ent->fts_path + sourceLen;
1056        unsigned short pathlen = ent->fts_pathlen - sourceLen;
1057        int type;
1058        Tcl_StatBuf *statBufPtr = NULL;
1059       
1060        if (info == FTS_DNR || info == FTS_ERR || info == FTS_NS) {
1061            errfile = ent->fts_path;
1062            break;
1063        }
1064        Tcl_DStringAppend(sourcePtr, path, pathlen);
1065        if (targetPtr != NULL) {
1066            Tcl_DStringAppend(targetPtr, path, pathlen);
1067        }
1068        switch (info) {
1069        case FTS_D:
1070            type = DOTREE_PRED;
1071            break;
1072        case FTS_DP:
1073            type = DOTREE_POSTD;
1074            break;
1075        default:
1076            type = DOTREE_F;
1077            break;
1078        }
1079        if (!doRewind) { /* no need to stat for delete */
1080            if (noFtsStat) {
1081                statBufPtr = &statBuf;
1082                if (TclOSlstat(ent->fts_path, statBufPtr) != 0) {
1083                    errfile = ent->fts_path;
1084                    break;
1085                }
1086            } else {
1087                statBufPtr = (Tcl_StatBuf *) ent->fts_statp;
1088            }
1089        }
1090        result = (*traverseProc)(sourcePtr, targetPtr, statBufPtr, type,
1091                errorPtr);
1092        if (result != TCL_OK) {
1093            break;
1094        }
1095        Tcl_DStringSetLength(sourcePtr, sourceLen);
1096        if (targetPtr != NULL) {
1097            Tcl_DStringSetLength(targetPtr, targetLen);
1098        }
1099    }
1100#endif /* HAVE_FTS */
1101
1102  end:
1103    if (errfile != NULL) {
1104        if (errorPtr != NULL) {
1105            Tcl_ExternalToUtfDString(NULL, errfile, -1, errorPtr);
1106        }
1107        result = TCL_ERROR;
1108    }
1109#ifdef HAVE_FTS
1110    if (fts != NULL) {
1111        fts_close(fts);
1112    }
1113#endif
1114
1115    return result;
1116}
1117
1118/*
1119 *----------------------------------------------------------------------
1120 *
1121 * TraversalCopy
1122 *
1123 *      Called from TraverseUnixTree in order to execute a recursive copy of a
1124 *      directory.
1125 *
1126 * Results:
1127 *      Standard Tcl result.
1128 *
1129 * Side effects:
1130 *      The file or directory src may be copied to dst, depending on the value
1131 *      of type.
1132 *
1133 *----------------------------------------------------------------------
1134 */
1135
1136static int
1137TraversalCopy(
1138    Tcl_DString *srcPtr,        /* Source pathname to copy (native). */
1139    Tcl_DString *dstPtr,        /* Destination pathname of copy (native). */
1140    CONST Tcl_StatBuf *statBufPtr,
1141                                /* Stat info for file specified by srcPtr. */
1142    int type,                   /* Reason for call - see TraverseUnixTree(). */
1143    Tcl_DString *errorPtr)      /* If non-NULL, uninitialized or free DString
1144                                 * filled with UTF-8 name of file causing
1145                                 * error. */
1146{
1147    switch (type) {
1148    case DOTREE_F:
1149        if (DoCopyFile(Tcl_DStringValue(srcPtr), Tcl_DStringValue(dstPtr),
1150                statBufPtr) == TCL_OK) {
1151            return TCL_OK;
1152        }
1153        break;
1154
1155    case DOTREE_PRED:
1156        if (DoCreateDirectory(Tcl_DStringValue(dstPtr)) == TCL_OK) {
1157            return TCL_OK;
1158        }
1159        break;
1160
1161    case DOTREE_POSTD:
1162        if (CopyFileAtts(Tcl_DStringValue(srcPtr),
1163                Tcl_DStringValue(dstPtr), statBufPtr) == TCL_OK) {
1164            return TCL_OK;
1165        }
1166        break;
1167    }
1168
1169    /*
1170     * There shouldn't be a problem with src, because we already checked it to
1171     * get here.
1172     */
1173
1174    if (errorPtr != NULL) {
1175        Tcl_ExternalToUtfDString(NULL, Tcl_DStringValue(dstPtr),
1176                Tcl_DStringLength(dstPtr), errorPtr);
1177    }
1178    return TCL_ERROR;
1179}
1180
1181/*
1182 *---------------------------------------------------------------------------
1183 *
1184 * TraversalDelete --
1185 *
1186 *      Called by procedure TraverseUnixTree for every file and directory that
1187 *      it encounters in a directory hierarchy. This procedure unlinks files,
1188 *      and removes directories after all the containing files have been
1189 *      processed.
1190 *
1191 * Results:
1192 *      Standard Tcl result.
1193 *
1194 * Side effects:
1195 *      Files or directory specified by src will be deleted.
1196 *
1197 *----------------------------------------------------------------------
1198 */
1199
1200static int
1201TraversalDelete(
1202    Tcl_DString *srcPtr,        /* Source pathname (native). */
1203    Tcl_DString *ignore,        /* Destination pathname (not used). */
1204    CONST Tcl_StatBuf *statBufPtr,
1205                                /* Stat info for file specified by srcPtr. */
1206    int type,                   /* Reason for call - see TraverseUnixTree(). */
1207    Tcl_DString *errorPtr)      /* If non-NULL, uninitialized or free DString
1208                                 * filled with UTF-8 name of file causing
1209                                 * error. */
1210{
1211    switch (type) {
1212    case DOTREE_F:
1213        if (TclpDeleteFile(Tcl_DStringValue(srcPtr)) == 0) {
1214            return TCL_OK;
1215        }
1216        break;
1217    case DOTREE_PRED:
1218        return TCL_OK;
1219    case DOTREE_POSTD:
1220        if (DoRemoveDirectory(srcPtr, 0, NULL) == 0) {
1221            return TCL_OK;
1222        }
1223        break;
1224    }
1225    if (errorPtr != NULL) {
1226        Tcl_ExternalToUtfDString(NULL, Tcl_DStringValue(srcPtr),
1227                Tcl_DStringLength(srcPtr), errorPtr);
1228    }
1229    return TCL_ERROR;
1230}
1231
1232/*
1233 *---------------------------------------------------------------------------
1234 *
1235 * CopyFileAtts --
1236 *
1237 *      Copy the file attributes such as owner, group, permissions, and
1238 *      modification date from one file to another.
1239 *
1240 * Results:
1241 *      Standard Tcl result.
1242 *
1243 * Side effects:
1244 *      User id, group id, permission bits, last modification time, and last
1245 *      access time are updated in the new file to reflect the old file.
1246 *
1247 *---------------------------------------------------------------------------
1248 */
1249
1250static int
1251CopyFileAtts(
1252    CONST char *src,            /* Path name of source file (native). */
1253    CONST char *dst,            /* Path name of target file (native). */
1254    CONST Tcl_StatBuf *statBufPtr)
1255                                /* Stat info for source file */
1256{
1257    struct utimbuf tval;
1258    mode_t newMode;
1259
1260    newMode = statBufPtr->st_mode
1261            & (S_ISUID | S_ISGID | S_IRWXU | S_IRWXG | S_IRWXO);
1262
1263    /*
1264     * Note that if you copy a setuid file that is owned by someone else, and
1265     * you are not root, then the copy will be setuid to you. The most correct
1266     * implementation would probably be to have the copy not setuid to anyone
1267     * if the original file was owned by someone else, but this corner case
1268     * isn't currently handled. It would require another lstat(), or getuid().
1269     */
1270
1271    if (chmod(dst, newMode)) {                          /* INTL: Native. */
1272        newMode &= ~(S_ISUID | S_ISGID);
1273        if (chmod(dst, newMode)) {                      /* INTL: Native. */
1274            return TCL_ERROR;
1275        }
1276    }
1277
1278    tval.actime = statBufPtr->st_atime;
1279    tval.modtime = statBufPtr->st_mtime;
1280
1281    if (utime(dst, &tval)) {                            /* INTL: Native. */
1282        return TCL_ERROR;
1283    }
1284#ifdef MAC_OSX_TCL
1285    TclMacOSXCopyFileAttributes(src, dst, statBufPtr);
1286#endif
1287    return TCL_OK;
1288}
1289
1290/*
1291 *----------------------------------------------------------------------
1292 *
1293 * GetGroupAttribute
1294 *
1295 *      Gets the group attribute of a file.
1296 *
1297 * Results:
1298 *      Standard TCL result. Returns a new Tcl_Obj in attributePtrPtr if there
1299 *      is no error.
1300 *
1301 * Side effects:
1302 *      A new object is allocated.
1303 *
1304 *----------------------------------------------------------------------
1305 */
1306
1307static int
1308GetGroupAttribute(
1309    Tcl_Interp *interp,         /* The interp we are using for errors. */
1310    int objIndex,               /* The index of the attribute. */
1311    Tcl_Obj *fileName,          /* The name of the file (UTF-8). */
1312    Tcl_Obj **attributePtrPtr)  /* A pointer to return the object with. */
1313{
1314    Tcl_StatBuf statBuf;
1315    struct group *groupPtr;
1316    int result;
1317
1318    result = TclpObjStat(fileName, &statBuf);
1319
1320    if (result != 0) {
1321        if (interp != NULL) {
1322            Tcl_AppendResult(interp, "could not read \"",
1323                    TclGetString(fileName), "\": ",
1324                    Tcl_PosixError(interp), NULL);
1325        }
1326        return TCL_ERROR;
1327    }
1328
1329    groupPtr = TclpGetGrGid(statBuf.st_gid);
1330
1331    if (groupPtr == NULL) {
1332        *attributePtrPtr = Tcl_NewIntObj((int) statBuf.st_gid);
1333    } else {
1334        Tcl_DString ds;
1335        CONST char *utf;
1336
1337        utf = Tcl_ExternalToUtfDString(NULL, groupPtr->gr_name, -1, &ds);
1338        *attributePtrPtr = Tcl_NewStringObj(utf, -1);
1339        Tcl_DStringFree(&ds);
1340    }
1341    endgrent();
1342    return TCL_OK;
1343}
1344
1345/*
1346 *----------------------------------------------------------------------
1347 *
1348 * GetOwnerAttribute
1349 *
1350 *      Gets the owner attribute of a file.
1351 *
1352 * Results:
1353 *      Standard TCL result. Returns a new Tcl_Obj in attributePtrPtr if there
1354 *      is no error.
1355 *
1356 * Side effects:
1357 *      A new object is allocated.
1358 *
1359 *----------------------------------------------------------------------
1360 */
1361
1362static int
1363GetOwnerAttribute(
1364    Tcl_Interp *interp,         /* The interp we are using for errors. */
1365    int objIndex,               /* The index of the attribute. */
1366    Tcl_Obj *fileName,          /* The name of the file (UTF-8). */
1367    Tcl_Obj **attributePtrPtr)  /* A pointer to return the object with. */
1368{
1369    Tcl_StatBuf statBuf;
1370    struct passwd *pwPtr;
1371    int result;
1372
1373    result = TclpObjStat(fileName, &statBuf);
1374
1375    if (result != 0) {
1376        if (interp != NULL) {
1377            Tcl_AppendResult(interp, "could not read \"",
1378                    TclGetString(fileName), "\": ",
1379                    Tcl_PosixError(interp), NULL);
1380        }
1381        return TCL_ERROR;
1382    }
1383
1384    pwPtr = TclpGetPwUid(statBuf.st_uid);
1385
1386    if (pwPtr == NULL) {
1387        *attributePtrPtr = Tcl_NewIntObj((int) statBuf.st_uid);
1388    } else {
1389        Tcl_DString ds;
1390        CONST char *utf;
1391
1392        utf = Tcl_ExternalToUtfDString(NULL, pwPtr->pw_name, -1, &ds);
1393        *attributePtrPtr = Tcl_NewStringObj(utf, Tcl_DStringLength(&ds));
1394        Tcl_DStringFree(&ds);
1395    }
1396    endpwent();
1397    return TCL_OK;
1398}
1399
1400/*
1401 *----------------------------------------------------------------------
1402 *
1403 * GetPermissionsAttribute
1404 *
1405 *      Gets the group attribute of a file.
1406 *
1407 * Results:
1408 *      Standard TCL result. Returns a new Tcl_Obj in attributePtrPtr if there
1409 *      is no error. The object will have ref count 0.
1410 *
1411 * Side effects:
1412 *      A new object is allocated.
1413 *
1414 *----------------------------------------------------------------------
1415 */
1416
1417static int
1418GetPermissionsAttribute(
1419    Tcl_Interp *interp,             /* The interp we are using for errors. */
1420    int objIndex,                   /* The index of the attribute. */
1421    Tcl_Obj *fileName,              /* The name of the file (UTF-8). */
1422    Tcl_Obj **attributePtrPtr)      /* A pointer to return the object with. */
1423{
1424    Tcl_StatBuf statBuf;
1425    int result;
1426
1427    result = TclpObjStat(fileName, &statBuf);
1428
1429    if (result != 0) {
1430        if (interp != NULL) {
1431            Tcl_AppendResult(interp, "could not read \"",
1432                    TclGetString(fileName), "\": ",
1433                    Tcl_PosixError(interp), NULL);
1434        }
1435        return TCL_ERROR;
1436    }
1437
1438    *attributePtrPtr = Tcl_ObjPrintf(
1439            "%0#5lo", (long) (statBuf.st_mode & 0x00007FFF));
1440    return TCL_OK;
1441}
1442
1443/*
1444 *---------------------------------------------------------------------------
1445 *
1446 * SetGroupAttribute --
1447 *
1448 *      Sets the group of the file to the specified group.
1449 *
1450 * Results:
1451 *      Standard TCL result.
1452 *
1453 * Side effects:
1454 *      As above.
1455 *
1456 *---------------------------------------------------------------------------
1457 */
1458
1459static int
1460SetGroupAttribute(
1461    Tcl_Interp *interp,         /* The interp for error reporting. */
1462    int objIndex,               /* The index of the attribute. */
1463    Tcl_Obj *fileName,          /* The name of the file (UTF-8). */
1464    Tcl_Obj *attributePtr)      /* New group for file. */
1465{
1466    long gid;
1467    int result;
1468    CONST char *native;
1469
1470    if (Tcl_GetLongFromObj(NULL, attributePtr, &gid) != TCL_OK) {
1471        Tcl_DString ds;
1472        struct group *groupPtr = NULL;
1473        CONST char *string;
1474        int length;
1475
1476        string = Tcl_GetStringFromObj(attributePtr, &length);
1477
1478        native = Tcl_UtfToExternalDString(NULL, string, length, &ds);
1479        groupPtr = TclpGetGrNam(native); /* INTL: Native. */
1480        Tcl_DStringFree(&ds);
1481
1482        if (groupPtr == NULL) {
1483            endgrent();
1484            if (interp != NULL) {
1485                Tcl_AppendResult(interp, "could not set group for file \"",
1486                        TclGetString(fileName), "\": group \"", string,
1487                        "\" does not exist", NULL);
1488            }
1489            return TCL_ERROR;
1490        }
1491        gid = groupPtr->gr_gid;
1492    }
1493
1494    native = Tcl_FSGetNativePath(fileName);
1495    result = chown(native, (uid_t) -1, (gid_t) gid);    /* INTL: Native. */
1496
1497    endgrent();
1498    if (result != 0) {
1499        if (interp != NULL) {
1500            Tcl_AppendResult(interp, "could not set group for file \"",
1501                    TclGetString(fileName), "\": ", Tcl_PosixError(interp),
1502                    NULL);
1503        }
1504        return TCL_ERROR;
1505    }
1506    return TCL_OK;
1507}
1508
1509/*
1510 *---------------------------------------------------------------------------
1511 *
1512 * SetOwnerAttribute --
1513 *
1514 *      Sets the owner of the file to the specified owner.
1515 *
1516 * Results:
1517 *      Standard TCL result.
1518 *
1519 * Side effects:
1520 *      As above.
1521 *
1522 *---------------------------------------------------------------------------
1523 */
1524
1525static int
1526SetOwnerAttribute(
1527    Tcl_Interp *interp,         /* The interp for error reporting. */
1528    int objIndex,               /* The index of the attribute. */
1529    Tcl_Obj *fileName,          /* The name of the file (UTF-8). */
1530    Tcl_Obj *attributePtr)      /* New owner for file. */
1531{
1532    long uid;
1533    int result;
1534    CONST char *native;
1535
1536    if (Tcl_GetLongFromObj(NULL, attributePtr, &uid) != TCL_OK) {
1537        Tcl_DString ds;
1538        struct passwd *pwPtr = NULL;
1539        CONST char *string;
1540        int length;
1541
1542        string = Tcl_GetStringFromObj(attributePtr, &length);
1543
1544        native = Tcl_UtfToExternalDString(NULL, string, length, &ds);
1545        pwPtr = TclpGetPwNam(native); /* INTL: Native. */
1546        Tcl_DStringFree(&ds);
1547
1548        if (pwPtr == NULL) {
1549            if (interp != NULL) {
1550                Tcl_AppendResult(interp, "could not set owner for file \"",
1551                        TclGetString(fileName), "\": user \"", string,
1552                        "\" does not exist", NULL);
1553            }
1554            return TCL_ERROR;
1555        }
1556        uid = pwPtr->pw_uid;
1557    }
1558
1559    native = Tcl_FSGetNativePath(fileName);
1560    result = chown(native, (uid_t) uid, (gid_t) -1);    /* INTL: Native. */
1561
1562    if (result != 0) {
1563        if (interp != NULL) {
1564            Tcl_AppendResult(interp, "could not set owner for file \"",
1565                    TclGetString(fileName), "\": ", Tcl_PosixError(interp),
1566                    NULL);
1567        }
1568        return TCL_ERROR;
1569    }
1570    return TCL_OK;
1571}
1572
1573/*
1574 *---------------------------------------------------------------------------
1575 *
1576 * SetPermissionsAttribute
1577 *
1578 *      Sets the file to the given permission.
1579 *
1580 * Results:
1581 *      Standard TCL result.
1582 *
1583 * Side effects:
1584 *      The permission of the file is changed.
1585 *
1586 *---------------------------------------------------------------------------
1587 */
1588
1589static int
1590SetPermissionsAttribute(
1591    Tcl_Interp *interp,         /* The interp we are using for errors. */
1592    int objIndex,               /* The index of the attribute. */
1593    Tcl_Obj *fileName,          /* The name of the file (UTF-8). */
1594    Tcl_Obj *attributePtr)      /* The attribute to set. */
1595{
1596    long mode;
1597    mode_t newMode;
1598    int result = TCL_ERROR;
1599    CONST char *native;
1600    char *modeStringPtr = TclGetString(attributePtr);
1601    int scanned = TclParseAllWhiteSpace(modeStringPtr, -1);
1602
1603    /*
1604     * First supply support for octal number format
1605     */
1606
1607    if ((modeStringPtr[scanned] == '0')
1608            && (modeStringPtr[scanned+1] >= '0')
1609            && (modeStringPtr[scanned+1] <= '7')) {
1610        /* Leading zero - attempt octal interpretation */
1611        Tcl_Obj *modeObj;
1612
1613        TclNewLiteralStringObj(modeObj, "0o");
1614        Tcl_AppendToObj(modeObj, modeStringPtr+scanned+1, -1);
1615        result = Tcl_GetLongFromObj(NULL, modeObj, &mode);
1616        Tcl_DecrRefCount(modeObj);
1617    }
1618    if (result == TCL_OK
1619            || Tcl_GetLongFromObj(NULL, attributePtr, &mode) == TCL_OK) {
1620        newMode = (mode_t) (mode & 0x00007FFF);
1621    } else {
1622        Tcl_StatBuf buf;
1623
1624        /*
1625         * Try the forms "rwxrwxrwx" and "ugo=rwx"
1626         *
1627         * We get the current mode of the file, in order to allow for ug+-=rwx
1628         * style chmod strings.
1629         */
1630
1631        result = TclpObjStat(fileName, &buf);
1632        if (result != 0) {
1633            if (interp != NULL) {
1634                Tcl_AppendResult(interp, "could not read \"",
1635                        TclGetString(fileName), "\": ",
1636                        Tcl_PosixError(interp), NULL);
1637            }
1638            return TCL_ERROR;
1639        }
1640        newMode = (mode_t) (buf.st_mode & 0x00007FFF);
1641
1642        if (GetModeFromPermString(NULL, modeStringPtr, &newMode) != TCL_OK) {
1643            if (interp != NULL) {
1644                Tcl_AppendResult(interp, "unknown permission string format \"",
1645                        modeStringPtr, "\"", NULL);
1646            }
1647            return TCL_ERROR;
1648        }
1649    }
1650
1651    native = Tcl_FSGetNativePath(fileName);
1652    result = chmod(native, newMode);            /* INTL: Native. */
1653    if (result != 0) {
1654        if (interp != NULL) {
1655            Tcl_AppendResult(interp, "could not set permissions for file \"",
1656                    TclGetString(fileName), "\": ",
1657                    Tcl_PosixError(interp), NULL);
1658        }
1659        return TCL_ERROR;
1660    }
1661    return TCL_OK;
1662}
1663
1664#ifndef DJGPP
1665/*
1666 *---------------------------------------------------------------------------
1667 *
1668 * TclpObjListVolumes --
1669 *
1670 *      Lists the currently mounted volumes, which on UNIX is just /.
1671 *
1672 * Results:
1673 *      The list of volumes.
1674 *
1675 * Side effects:
1676 *      None.
1677 *
1678 *---------------------------------------------------------------------------
1679 */
1680
1681Tcl_Obj *
1682TclpObjListVolumes(void)
1683{
1684    Tcl_Obj *resultPtr = Tcl_NewStringObj("/", 1);
1685
1686    Tcl_IncrRefCount(resultPtr);
1687    return resultPtr;
1688}
1689#endif
1690
1691/*
1692 *----------------------------------------------------------------------
1693 *
1694 * GetModeFromPermString --
1695 *
1696 *      This procedure is invoked to process the "file permissions" Tcl
1697 *      command, to check for a "rwxrwxrwx" or "ugoa+-=rwxst" string. See the
1698 *      user documentation for details on what it does.
1699 *
1700 * Results:
1701 *      A standard Tcl result.
1702 *
1703 * Side effects:
1704 *      See the user documentation.
1705 *
1706 *----------------------------------------------------------------------
1707 */
1708
1709static int
1710GetModeFromPermString(
1711    Tcl_Interp *interp,         /* The interp we are using for errors. */
1712    char *modeStringPtr,        /* Permissions string */
1713    mode_t *modePtr)            /* pointer to the mode value */
1714{
1715    mode_t newMode;
1716    mode_t oldMode;             /* Storage for the value of the old mode (that
1717                                 * is passed in), to allow for the chmod style
1718                                 * manipulation. */
1719    int i,n, who, op, what, op_found, who_found;
1720
1721    /*
1722     * We start off checking for an "rwxrwxrwx" style permissions string
1723     */
1724
1725    if (strlen(modeStringPtr) != 9) {
1726        goto chmodStyleCheck;
1727    }
1728
1729    newMode = 0;
1730    for (i = 0; i < 9; i++) {
1731        switch (*(modeStringPtr+i)) {
1732        case 'r':
1733            if ((i%3) != 0) {
1734                goto chmodStyleCheck;
1735            }
1736            newMode |= (1<<(8-i));
1737            break;
1738        case 'w':
1739            if ((i%3) != 1) {
1740                goto chmodStyleCheck;
1741            }
1742            newMode |= (1<<(8-i));
1743            break;
1744        case 'x':
1745            if ((i%3) != 2) {
1746                goto chmodStyleCheck;
1747            }
1748            newMode |= (1<<(8-i));
1749            break;
1750        case 's':
1751            if (((i%3) != 2) || (i > 5)) {
1752                goto chmodStyleCheck;
1753            }
1754            newMode |= (1<<(8-i));
1755            newMode |= (1<<(11-(i/3)));
1756            break;
1757        case 'S':
1758            if (((i%3) != 2) || (i > 5)) {
1759                goto chmodStyleCheck;
1760            }
1761            newMode |= (1<<(11-(i/3)));
1762            break;
1763        case 't':
1764            if (i != 8) {
1765                goto chmodStyleCheck;
1766            }
1767            newMode |= (1<<(8-i));
1768            newMode |= (1<<9);
1769            break;
1770        case 'T':
1771            if (i != 8) {
1772                goto chmodStyleCheck;
1773            }
1774            newMode |= (1<<9);
1775            break;
1776        case '-':
1777            break;
1778        default:
1779            /*
1780             * Oops, not what we thought it was, so go on
1781             */
1782            goto chmodStyleCheck;
1783        }
1784    }
1785    *modePtr = newMode;
1786    return TCL_OK;
1787
1788  chmodStyleCheck:
1789    /*
1790     * We now check for an "ugoa+-=rwxst" style permissions string
1791     */
1792
1793    for (n = 0 ; *(modeStringPtr+n) != '\0' ; n = n + i) {
1794        oldMode = *modePtr;
1795        who = op = what = op_found = who_found = 0;
1796        for (i = 0 ; *(modeStringPtr+n+i) != '\0' ; i++ ) {
1797            if (!who_found) {
1798                /* who */
1799                switch (*(modeStringPtr+n+i)) {
1800                case 'u':
1801                    who |= 0x9c0;
1802                    continue;
1803                case 'g':
1804                    who |= 0x438;
1805                    continue;
1806                case 'o':
1807                    who |= 0x207;
1808                    continue;
1809                case 'a':
1810                    who |= 0xfff;
1811                    continue;
1812                }
1813            }
1814            who_found = 1;
1815            if (who == 0) {
1816                who = 0xfff;
1817            }
1818            if (!op_found) {
1819                /* op */
1820                switch (*(modeStringPtr+n+i)) {
1821                case '+':
1822                    op = 1;
1823                    op_found = 1;
1824                    continue;
1825                case '-':
1826                    op = 2;
1827                    op_found = 1;
1828                    continue;
1829                case '=':
1830                    op = 3;
1831                    op_found = 1;
1832                    continue;
1833                default:
1834                    return TCL_ERROR;
1835                }
1836            }
1837            /* what */
1838            switch (*(modeStringPtr+n+i)) {
1839            case 'r':
1840                what |= 0x124;
1841                continue;
1842            case 'w':
1843                what |= 0x92;
1844                continue;
1845            case 'x':
1846                what |= 0x49;
1847                continue;
1848            case 's':
1849                what |= 0xc00;
1850                continue;
1851            case 't':
1852                what |= 0x200;
1853                continue;
1854            case ',':
1855                break;
1856            default:
1857                return TCL_ERROR;
1858            }
1859            if (*(modeStringPtr+n+i) == ',') {
1860                i++;
1861                break;
1862            }
1863        }
1864        switch (op) {
1865        case 1:
1866            *modePtr = oldMode | (who & what);
1867            continue;
1868        case 2:
1869            *modePtr = oldMode & ~(who & what);
1870            continue;
1871        case 3:
1872            *modePtr = (oldMode & ~who) | (who & what);
1873            continue;
1874        }
1875    }
1876    return TCL_OK;
1877}
1878
1879/*
1880 *---------------------------------------------------------------------------
1881 *
1882 * TclpObjNormalizePath --
1883 *
1884 *      This function scans through a path specification and replaces it, in
1885 *      place, with a normalized version. A normalized version is one in which
1886 *      all symlinks in the path are replaced with their expanded form (except
1887 *      a symlink at the very end of the path).
1888 *
1889 * Results:
1890 *      The new 'nextCheckpoint' value, giving as far as we could understand
1891 *      in the path.
1892 *
1893 * Side effects:
1894 *      The pathPtr string, is modified.
1895 *
1896 *---------------------------------------------------------------------------
1897 */
1898
1899int
1900TclpObjNormalizePath(
1901    Tcl_Interp *interp,
1902    Tcl_Obj *pathPtr,
1903    int nextCheckpoint)
1904{
1905    char *currentPathEndPosition;
1906    int pathLen;
1907    char cur;
1908    char *path = Tcl_GetStringFromObj(pathPtr, &pathLen);
1909#ifndef NO_REALPATH
1910    char normPath[MAXPATHLEN];
1911    Tcl_DString ds;
1912    CONST char *nativePath;
1913#endif
1914
1915    /*
1916     * We add '1' here because if nextCheckpoint is zero we know that '/'
1917     * exists, and if it isn't zero, it must point at a directory separator
1918     * which we also know exists.
1919     */
1920
1921    currentPathEndPosition = path + nextCheckpoint;
1922    if (*currentPathEndPosition == '/') {
1923        currentPathEndPosition++;
1924    }
1925
1926#ifndef NO_REALPATH
1927    /*
1928     * For speed, try to get the entire path in one go.
1929     */
1930
1931    if (nextCheckpoint == 0 && haveRealpath) {
1932        char *lastDir = strrchr(currentPathEndPosition, '/');
1933
1934        if (lastDir != NULL) {
1935            nativePath = Tcl_UtfToExternalDString(NULL, path,
1936                    lastDir-path, &ds);
1937            if (Realpath(nativePath, normPath) != NULL) {
1938                if (*nativePath != '/' && *normPath == '/') {
1939                    /*
1940                     * realpath has transformed a relative path into an
1941                     * absolute path, we do not know how to handle this.
1942                     */
1943                } else {
1944                    nextCheckpoint = lastDir - path;
1945                    goto wholeStringOk;
1946                }
1947            }
1948            Tcl_DStringFree(&ds);
1949        }
1950    }
1951
1952    /*
1953     * Else do it the slow way.
1954     */
1955#endif
1956
1957    while (1) {
1958        cur = *currentPathEndPosition;
1959        if ((cur == '/') && (path != currentPathEndPosition)) {
1960            /*
1961             * Reached directory separator.
1962             */
1963
1964            Tcl_DString ds;
1965            CONST char *nativePath;
1966            int accessOk;
1967
1968            nativePath = Tcl_UtfToExternalDString(NULL, path,
1969                    currentPathEndPosition - path, &ds);
1970            accessOk = access(nativePath, F_OK);
1971            Tcl_DStringFree(&ds);
1972
1973            if (accessOk != 0) {
1974                /*
1975                 * File doesn't exist.
1976                 */
1977
1978                break;
1979            }
1980
1981            /*
1982             * Update the acceptable point.
1983             */
1984
1985            nextCheckpoint = currentPathEndPosition - path;
1986        } else if (cur == 0) {
1987            /*
1988             * Reached end of string.
1989             */
1990
1991            break;
1992        }
1993        currentPathEndPosition++;
1994    }
1995
1996    /*
1997     * We should really now convert this to a canonical path. We do that with
1998     * 'realpath' if we have it available. Otherwise we could step through
1999     * every single path component, checking whether it is a symlink, but that
2000     * would be a lot of work, and most modern OSes have 'realpath'.
2001     */
2002
2003#ifndef NO_REALPATH
2004    if (haveRealpath) {
2005        /*
2006         * If we only had '/foo' or '/' then we never increment nextCheckpoint
2007         * and we don't need or want to go through 'Realpath'. Also, on some
2008         * platforms, passing an empty string to 'Realpath' will give us the
2009         * normalized pwd, which is not what we want at all!
2010         */
2011
2012        if (nextCheckpoint == 0) {
2013            return 0;
2014        }
2015
2016        nativePath = Tcl_UtfToExternalDString(NULL, path, nextCheckpoint, &ds);
2017        if (Realpath(nativePath, normPath) != NULL) {
2018            int newNormLen;
2019
2020        wholeStringOk:
2021            newNormLen = strlen(normPath);
2022            if ((newNormLen == Tcl_DStringLength(&ds))
2023                    && (strcmp(normPath, nativePath) == 0)) {
2024                /*
2025                 * String is unchanged.
2026                 */
2027
2028                Tcl_DStringFree(&ds);
2029
2030                /*
2031                 * Enable this to have the native FS claim normalization of
2032                 * the whole path for existing files. That would permit the
2033                 * caller to declare normalization complete without calls to
2034                 * additional filesystems. Saving lots of calls is probably
2035                 * worth the extra access() time here. When no other FS's are
2036                 * registered though, things are less clear.
2037                 *
2038                if (0 == access(normPath, F_OK)) {
2039                    return pathLen;
2040                }
2041                 */
2042
2043                return nextCheckpoint;
2044            }
2045
2046            /*
2047             * Free up the native path and put in its place the converted,
2048             * normalized path.
2049             */
2050
2051            Tcl_DStringFree(&ds);
2052            Tcl_ExternalToUtfDString(NULL, normPath, (int) newNormLen, &ds);
2053
2054            if (path[nextCheckpoint] != '\0') {
2055                /*
2056                 * Not at end, append remaining path.
2057                 */
2058
2059                int normLen = Tcl_DStringLength(&ds);
2060
2061                Tcl_DStringAppend(&ds, path + nextCheckpoint,
2062                        pathLen - nextCheckpoint);
2063
2064                /*
2065                 * We recognise up to and including the directory separator.
2066                 */
2067
2068                nextCheckpoint = normLen + 1;
2069            } else {
2070                /*
2071                 * We recognise the whole string.
2072                 */
2073
2074                nextCheckpoint = Tcl_DStringLength(&ds);
2075            }
2076
2077            /*
2078             * Overwrite with the normalized path.
2079             */
2080
2081            Tcl_SetStringObj(pathPtr, Tcl_DStringValue(&ds),
2082                    Tcl_DStringLength(&ds));
2083        }
2084        Tcl_DStringFree(&ds);
2085    }
2086#endif  /* !NO_REALPATH */
2087
2088    return nextCheckpoint;
2089}
2090
2091#if defined(HAVE_CHFLAGS) && defined(UF_IMMUTABLE)
2092/*
2093 *----------------------------------------------------------------------
2094 *
2095 * GetReadOnlyAttribute
2096 *
2097 *      Gets the readonly attribute (user immutable flag) of a file.
2098 *
2099 * Results:
2100 *      Standard TCL result. Returns a new Tcl_Obj in attributePtrPtr if there
2101 *      is no error. The object will have ref count 0.
2102 *
2103 * Side effects:
2104 *      A new object is allocated.
2105 *
2106 *----------------------------------------------------------------------
2107 */
2108
2109static int
2110GetReadOnlyAttribute(
2111    Tcl_Interp *interp,         /* The interp we are using for errors. */
2112    int objIndex,               /* The index of the attribute. */
2113    Tcl_Obj *fileName,          /* The name of the file (UTF-8). */
2114    Tcl_Obj **attributePtrPtr)  /* A pointer to return the object with. */
2115{
2116    Tcl_StatBuf statBuf;
2117    int result;
2118
2119    result = TclpObjStat(fileName, &statBuf);
2120
2121    if (result != 0) {
2122        if (interp != NULL) {
2123            Tcl_AppendResult(interp, "could not read \"",
2124                    TclGetString(fileName), "\": ", Tcl_PosixError(interp),
2125                    NULL);
2126        }
2127        return TCL_ERROR;
2128    }
2129
2130    *attributePtrPtr = Tcl_NewBooleanObj((statBuf.st_flags&UF_IMMUTABLE) != 0);
2131
2132    return TCL_OK;
2133}
2134
2135/*
2136 *---------------------------------------------------------------------------
2137 *
2138 * SetReadOnlyAttribute
2139 *
2140 *      Sets the readonly attribute (user immutable flag) of a file.
2141 *
2142 * Results:
2143 *      Standard TCL result.
2144 *
2145 * Side effects:
2146 *      The readonly attribute of the file is changed.
2147 *
2148 *---------------------------------------------------------------------------
2149 */
2150
2151static int
2152SetReadOnlyAttribute(
2153    Tcl_Interp *interp,         /* The interp we are using for errors. */
2154    int objIndex,               /* The index of the attribute. */
2155    Tcl_Obj *fileName,          /* The name of the file (UTF-8). */
2156    Tcl_Obj *attributePtr)      /* The attribute to set. */
2157{
2158    Tcl_StatBuf statBuf;
2159    int result;
2160    int readonly;
2161    CONST char *native;
2162
2163    if (Tcl_GetBooleanFromObj(interp, attributePtr, &readonly) != TCL_OK) {
2164        return TCL_ERROR;
2165    }
2166
2167    result = TclpObjStat(fileName, &statBuf);
2168
2169    if (result != 0) {
2170        if (interp != NULL) {
2171            Tcl_AppendResult(interp, "could not read \"",
2172                    TclGetString(fileName), "\": ", Tcl_PosixError(interp),
2173                    NULL);
2174        }
2175        return TCL_ERROR;
2176    }
2177
2178    if (readonly) {
2179        statBuf.st_flags |= UF_IMMUTABLE;
2180    } else {
2181        statBuf.st_flags &= ~UF_IMMUTABLE;
2182    }
2183
2184    native = Tcl_FSGetNativePath(fileName);
2185    result = chflags(native, statBuf.st_flags);         /* INTL: Native. */
2186    if (result != 0) {
2187        if (interp != NULL) {
2188            Tcl_AppendResult(interp, "could not set flags for file \"",
2189                    TclGetString(fileName), "\": ", Tcl_PosixError(interp),
2190                    NULL);
2191        }
2192        return TCL_ERROR;
2193    }
2194    return TCL_OK;
2195}
2196#endif /* defined(HAVE_CHFLAGS) && defined(UF_IMMUTABLE) */
2197
2198/*
2199 * Local Variables:
2200 * mode: c
2201 * c-basic-offset: 4
2202 * fill-column: 78
2203 * End:
2204 */
Note: See TracBrowser for help on using the repository browser.