home *** CD-ROM | disk | FTP | other *** search
- /* Copyright (C) 1996 DJ Delorie, see COPYING.DJ for details */
- /* Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details */
- /* ------------------------- rename() for DJGPP -------------------------- */
-
- /*
- * An implementation of rename() which can move both files AND
- * directories on the same filesystem (in the DOS world this means
- * the same logical drive). Most cases are simply passed to the
- * DOS Int 21h/AH=56h function. Special treatment is required for
- * renaming (moving) directories which don't share their parent
- * directory, because DOS won't allow this. This is called ``Prune
- * and graft'' operation. Most of the code below handles this
- * special case. It recursively creates subdirectories at the
- * target path, moves regular files there, then deletes the (empty)
- * directories at the source.
- *
- * An alternative (and much faster) implementation would be to access
- * the parent directories of the source and the target at the disk
- * sector level and rewrite them with BIOS calls. However, this won't
- * work for networked drives and will leave the file system in an
- * inconsistent state, should the machine crash before the operation
- * is completed. (A hybrid approach which uses the fast method when
- * possible and the slow one otherwise, is left as an exercise for the
- * ambitious readers. ;-)
- */
-
- #include <libc/stubs.h>
- #include <libc/bss.h>
- #include <stdio.h>
- #include <string.h>
- #include <stdlib.h>
- #include <ctype.h>
- #include <errno.h>
- #include <io.h>
- #include <sys/stat.h>
- #include <dir.h>
-
- /* -------------------------------------------------------
- Internal variables and static helper functions.
- ------------------------------------------------------- */
-
- /* A stack of directories queued for deletion as soon as they are
- emptied. Implemented as an array of structures; each element
- contains a ptr into a char array (the pool) where the names of
- the directories are stored, and the length of the pathname of
- that directory. */
-
- typedef struct _stacked_dir {
- char * dirname;
- int dirlen;
- } Stacked_Dir;
-
- static char * dirnames_pool; /* the pool of names */
- static int pool_size; /* size of the pool */
- static Stacked_Dir * dirstack; /* the stack itself */
- static Stacked_Dir * stack_top; /* top of stack */
- static int stack_size; /* current stack size */
-
- static char target[FILENAME_MAX];
- static char source[FILENAME_MAX];
- static int last;
-
- static int rename_count = -1;
-
- /*
- ** Initialize the stack. Make sure the storage is allocated.
- */
- static int
- init_stack(void)
- {
- /* Init static variables at program start, or if we have been
- restarted (emacs). */
- if (__bss_count != rename_count)
- {
- rename_count = __bss_count;
- dirstack = (Stacked_Dir *)0;
- pool_size = 1024;
- stack_size = 32;
- }
-
- if (dirstack == 0)
- {
- /* Initialize storage. */
- dirnames_pool = (char *)malloc(pool_size);
- if (dirnames_pool == 0)
- {
- errno = ENOMEM;
- return 0;
- }
-
- dirstack = (Stacked_Dir *)malloc(stack_size * sizeof(Stacked_Dir));
- if (dirstack == 0)
- {
- errno = ENOMEM;
- return 0;
- }
- }
-
- /* Forget the previous contents of the stack. */
- stack_top = dirstack;
- stack_top->dirname = dirnames_pool;
- stack_top->dirlen = 0;
-
- return 1;
- }
-
- /*
- ** Push a directory onto the stack, return non-zero in
- ** case of success.
- */
-
- static int
- push_dir(const char *dir)
- {
- int dspace = strlen(dir) + 1;
- char * pool_end; /* where unised space in pool begins */
-
- pool_end = stack_top->dirname + stack_top->dirlen;
-
- /* Ensure we have enough space in the name pool for this directory. */
- if (pool_end + dspace >= dirnames_pool + pool_size)
- {
- char * temp;
-
- /* Make its size doubled, plus a space for this directory. */
- pool_size += dspace + pool_size;
- temp = (char *)realloc(dirnames_pool, pool_size);
- if (temp == 0)
- {
- errno = ENOMEM;
- return 0;
- }
- pool_end += temp - dirnames_pool;
- dirnames_pool = temp;
- }
-
- /* Bump stack top and check for stack overflow. */
- if (++stack_top - dirstack >= stack_size)
- {
- /* Not enough storage--reallocate. */
- Stacked_Dir * temp;
-
- stack_size *= 2;
- temp = (Stacked_Dir *)realloc(dirstack,
- stack_size * sizeof(Stacked_Dir));
- if (temp == 0)
- {
- errno = ENOMEM;
- return 0;
- }
- stack_top += temp - dirstack;
- dirstack = temp;
- }
-
- /* Now push the directory onto the stack. */
- stack_top->dirname = strcpy(pool_end, dir);
- stack_top->dirlen = dspace;
-
- return 1;
- }
-
- /*
- ** Pop a directory off the stack, return its name.
- */
-
- static char *
- pop_dir(void)
- {
- char * retval;
-
- if (stack_top < dirstack || stack_top->dirlen == 0) /* empty stack */
- return (char *)0;
-
- retval = stack_top->dirname;
- stack_top--;
- return retval;
- }
-
- /*
- ** See if DIR1 is a parent of DIR2, return 1 if it is.
- ** Note that this is in no way a general solution: it only
- ** works in the context of mover() operation which always
- ** gets filenames rooted at the same directory. Otherwise,
- ** you must pass a fully-qualified pathnames for this to
- ** work.
- */
-
- static int
- is_parent(const char *dir1, const char *dir2)
- {
- if (dir1 == 0 || dir2 == 0 || *dir1 == 0)
- return 0;
- while (*dir1 && *dir2 && tolower(*dir1) == tolower(*dir2))
- {
- dir1++;
- dir2++;
- }
-
- return *dir1 == '\0' && (*dir2 == '/' || *dir2 == '\\');
- }
-
- /*
- ** Main workhorse. This will be passed to __file_tree_walk()
- ** to do the actual job of moving a subtree to another branch
- ** of the filesystem hierarchy.
- */
-
- static int
- mover(const char *from, const struct ffblk *ff)
- {
- char to[FILENAME_MAX];
-
- /* Did we just finish to empty a directory? */
- if (!is_parent(stack_top->dirname, from))
- {
- /* Remove an empty directory and pop it from stack. */
- if (remove(pop_dir()))
- return -1;
- }
-
- strcpy(to, target);
- strcat(to, from + last);
-
- if (ff->ff_attrib & 0x10)
- {
- /* A directory -- create its namesake and push it onto stack. */
- if (mkdir(to, ff->ff_attrib & 0xffe7) == -1 ||
- push_dir(from) == 0)
- return -1;
- }
- else /* a file -- move it */
- {
- if (_rename(from, to) == -1)
- return -1;
- }
-
- return 0;
- }
-
-
- /* -------------------------------------------------------
- Main entry point and the only exported function
- ------------------------------------------------------- */
-
- int
- rename(const char *old, const char *new)
- {
- int status;
- char *p;
- int source_attr, target_attr;
- int old_dev, new_dev;
- int e = errno;
- int simple_should_do = 0; /* _rename() enough? */
- int target_exists = 0;
-
- /* Much of the following quite tedious administrivia is necessary
- to return a meaningful code in errno. Otherwise, for most of
- the calamities DOS will feed us the ubiquitous EACCES which
- doesn't tell us much. */
-
- /* There are several conditions which we must check upfront,
- to ensure NEW isn't deleted unless rename() is to succeed. */
-
- /* Fail with EFAULT if one of OLD or NEW is a NULL ptr. */
- if (old == 0 || new == 0)
- {
- errno = EFAULT;
- return -1;
- }
-
- /* Fail with ENAMETOOLONG if either OLD or NEW are too long. This is
- explicitly checked so that DOS filename truncation won't fool us. */
- if (strlen(old) > FILENAME_MAX || strlen(new) > FILENAME_MAX)
- {
- errno = ENAMETOOLONG;
- return -1;
- }
-
- /* Fail with ENOENT if OLD doesn't exist or is otherwise
- inaccessible. */
- if ((source_attr = _chmod(old, 0)) == -1)
- return -1; /* errno set by _chmod() */
-
- /* Fail with EXDEV, if old and new aren't on the same device. */
- if (old[1] == ':')
- old_dev = toupper(old[0]) - 'A';
- else
- old_dev = getdisk();
- if (new[1] == ':')
- new_dev = toupper(new[0]) - 'A';
- else
- new_dev = getdisk();
- if (old_dev != new_dev)
- {
- errno = EXDEV;
- return -1;
- }
-
- /* Refuse to rename `.' or `..' or `d:.' or `d:..' */
- if ((old[0] == '.' && (old[1] == '\0' || (old[1] == '.' && old[2] == '\0'))) ||
- (old[1] == ':' && old[2] == '.' && (old[3] == '\0' || (old[3] == '.' && old[4] == '\0'))))
- {
- errno = EINVAL;
- return -1;
- }
-
- /* Some problems can only happen if NEW already exists. */
- errno = 0;
- target_attr = _chmod(new, 0);
- if (errno != ENOENT)
- {
- int old_is_dir = (source_attr & 0x10) == 0x10;
- int new_is_dir = (target_attr & 0x10) == 0x10;
-
- target_exists = 1;
-
- /* Fail if OLD is a directory while NEW isn't, or vice versa. */
- if (old_is_dir && !new_is_dir)
- {
- errno = ENOTDIR;
- return -1;
- }
- else if (new_is_dir && !old_is_dir)
- {
- errno = EISDIR;
- return -1;
- }
- else if (old_is_dir && new_is_dir)
- {
- char new_true[FILENAME_MAX], old_true[FILENAME_MAX];
-
- /* Fail if both OLD and NEW are directories and
- OLD is parent of NEW. */
- errno = 0;
- if (is_parent(_truename(old, old_true), _truename(new, new_true)))
- {
- errno = EINVAL;
- return -1;
- }
- else if (errno)
- return -1;
- else
- {
- /* See if these two directories share a common parent. If
- they do, then _rename() will do the job for us. */
-
- char *s = strrchr(old_true, '\\'), *t = strrchr(new_true, '\\');
-
- *s = '\0'; /* truncate at end of parent directory name */
- *t = '\0';
- if (strcmp(old_true, new_true) == 0)
- simple_should_do = 1;
- }
- }
- else
- {
- /* They both are files, _rename() must be enough. */
- simple_should_do = 1;
- }
- }
-
- /* On to some REAL work for a change. Let DOS do the simple job:
- moving a regular file, or renaming a directory. Note that on
- Windows 95 this will also move a directory to a subtree of
- another parent directory. */
- if ((status = _rename(old, new)) == 0)
- {
- errno = e; /* restore errno we inherited */
- return 0;
- }
- else if (simple_should_do)
- /* If _rename() fails and we KNOW it shouldn't, give up
- and return -1 with whatever errno we have. */
- return -1;
- else if (errno == EACCES && target_exists && (target_attr & 0x10) &&
- _chmod(new, 0) != -1)
- {
- /* If target still exists (i.e., it wasn't removed inside
- _rename()) and is a directory, assume it's not empty.
- (We could verify that with findfirst()/findnext(), but that
- would be too expensive. Besides, what other reason could DOS
- possibly have for not letting us remove a directory?? ;-) */
-
- errno = ENOTEMPTY;
- return -1;
- }
- else
- {
- /* We are in for some dirty work... Sigh...
- Recursively traverse the directory tree rooted at OLD,
- moving regular files and creating subdirectories at NEW. */
-
- int retval;
-
- strcpy(source, old);
- last = strlen(source) - 1;
-
- strcpy(target, new);
- if (strchr(target, '\\'))
- for (p = target; *p; p++)
- {
- if (*p == '\\')
- *p = '/';
- }
- if (source[last] == '/' || source[last] == '\\')
- source[last] = '\0';
- else
- ++last;
-
- /* Create NEW and push it onto the stack. */
- if (mkdir(new, source_attr & 0xffe7) == -1 ||
- init_stack() == 0 || push_dir(old) == 0)
- return -1;
-
- /* Process all of its siblings. */
- retval = __file_tree_walk(source, mover);
-
- if (retval)
- {
-
- /* If anything went wrong, remove NEW in a desperate attempt
- to leave everything as we found it. */
- int savederr = errno;
-
- remove(target);
- errno = savederr;
- }
- else
- {
- char *p2;
-
- /* Remove any (empty) directories that remain on the stack.
- This is required because we only detect that a subdirectory
- can be removed when we see the next entry in its parent dir.
- Subdirectories which are the LAST (or the only) entries in
- their parent directories, won't be removed. */
- while ((p2 = pop_dir()) != 0)
- {
- errno = e;
- if (remove(p2) != 0)
- {
- int se = errno;
-
- remove(target);
- errno = se;
- return -1;
- }
- }
- }
-
- return retval;
- }
- }
-
- #ifdef TEST
-
- #include <stdlib.h>
-
- int
- main(int argc, char *argv[])
- {
- if (argc > 2)
- {
- char msg[80];
-
- sprintf(msg, "movdir: %d", rename(argv[1], argv[2]));
- if (errno)
- perror(msg);
- else
- puts(msg);
- }
- else
- printf("Usage: %s from to\n", argv[0]);
-
- return 0;
- }
-
- #endif
-