home *** CD-ROM | disk | FTP | other *** search
/ PC Extra Super CD 1998 January / PCPLUS131.iso / DJGPP / V2 / DJLSR201.ZIP / src / libc / ansi / stdio / rename.c < prev    next >
Encoding:
C/C++ Source or Header  |  1996-08-31  |  13.4 KB  |  479 lines

  1. /* Copyright (C) 1996 DJ Delorie, see COPYING.DJ for details */
  2. /* Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details */
  3. /* ------------------------- rename() for DJGPP -------------------------- */
  4.  
  5. /*
  6.  *  An implementation of rename() which can move both files AND
  7.  *  directories on the same filesystem (in the DOS world this means
  8.  *  the same logical drive).  Most cases are simply passed to the
  9.  *  DOS Int 21h/AH=56h function.  Special treatment is required for
  10.  *  renaming (moving) directories which don't share their parent
  11.  *  directory, because DOS won't allow this.  This is called ``Prune
  12.  *  and graft'' operation.  Most of the code below handles this
  13.  *  special case.  It recursively creates subdirectories at the
  14.  *  target path, moves regular files there, then deletes the (empty)
  15.  *  directories at the source.
  16.  *
  17.  *  An alternative (and much faster) implementation would be to access
  18.  *  the parent directories of the source and the target at the disk
  19.  *  sector level and rewrite them with BIOS calls.  However, this won't
  20.  *  work for networked drives and will leave the file system in an
  21.  *  inconsistent state, should the machine crash before the operation
  22.  *  is completed.  (A hybrid approach which uses the fast method when
  23.  *  possible and the slow one otherwise, is left as an exercise for the
  24.  *  ambitious readers. ;-)
  25.  */
  26.  
  27. #include <libc/stubs.h>
  28. #include <libc/bss.h>
  29. #include <stdio.h>
  30. #include <string.h>
  31. #include <stdlib.h>
  32. #include <ctype.h>
  33. #include <errno.h>
  34. #include <io.h>
  35. #include <sys/stat.h>
  36. #include <dir.h>
  37.  
  38. /* -------------------------------------------------------
  39.        Internal variables and static helper functions.
  40.    ------------------------------------------------------- */
  41.  
  42. /* A stack of directories queued for deletion as soon as they are
  43.    emptied.  Implemented as an array of structures; each element
  44.    contains a ptr into a char array (the pool) where the names of
  45.    the directories are stored, and the length of the pathname of
  46.    that directory.  */
  47.  
  48. typedef struct _stacked_dir {
  49.   char * dirname;
  50.   int    dirlen;
  51. } Stacked_Dir;
  52.  
  53. static char        * dirnames_pool;    /* the pool of names */
  54. static int           pool_size;        /* size of the pool */
  55. static Stacked_Dir * dirstack;         /* the stack itself */
  56. static Stacked_Dir * stack_top;        /* top of stack */
  57. static int           stack_size;       /* current stack size */
  58.  
  59. static char          target[FILENAME_MAX];
  60. static char          source[FILENAME_MAX];
  61. static int           last;
  62.  
  63. static int           rename_count = -1;
  64.  
  65. /*
  66. ** Initialize the stack.  Make sure the storage is allocated.
  67. */
  68. static int
  69. init_stack(void)
  70. {
  71.   /* Init static variables at program start, or if we have been
  72.      restarted (emacs).  */
  73.   if (__bss_count != rename_count)
  74.     {
  75.       rename_count = __bss_count;
  76.       dirstack = (Stacked_Dir *)0;
  77.       pool_size = 1024;
  78.       stack_size = 32;
  79.     }
  80.  
  81.   if (dirstack == 0)
  82.     {
  83.       /* Initialize storage.  */
  84.       dirnames_pool = (char *)malloc(pool_size);
  85.       if (dirnames_pool == 0)
  86.         {
  87.           errno = ENOMEM;
  88.           return 0;
  89.         }
  90.  
  91.       dirstack = (Stacked_Dir *)malloc(stack_size * sizeof(Stacked_Dir));
  92.       if (dirstack == 0)
  93.         {
  94.           errno = ENOMEM;
  95.           return 0;
  96.         }
  97.     }
  98.  
  99.   /* Forget the previous contents of the stack.  */
  100.   stack_top = dirstack;
  101.   stack_top->dirname = dirnames_pool;
  102.   stack_top->dirlen  = 0;
  103.  
  104.   return 1;
  105. }
  106.  
  107. /*
  108. ** Push a directory onto the stack, return non-zero in
  109. ** case of success.
  110. */
  111.  
  112. static int
  113. push_dir(const char *dir)
  114. {
  115.   int dspace = strlen(dir) + 1;
  116.   char * pool_end;      /* where unised space in pool begins */
  117.  
  118.   pool_end = stack_top->dirname + stack_top->dirlen;
  119.  
  120.   /* Ensure we have enough space in the name pool for this directory.  */
  121.   if (pool_end + dspace >= dirnames_pool + pool_size)
  122.     {
  123.       char * temp;
  124.  
  125.       /* Make its size doubled, plus a space for this directory.  */
  126.       pool_size += dspace + pool_size;
  127.       temp = (char *)realloc(dirnames_pool, pool_size);
  128.       if (temp == 0)
  129.         {
  130.           errno = ENOMEM;
  131.           return 0;
  132.         }
  133.       pool_end += temp - dirnames_pool;
  134.       dirnames_pool = temp;
  135.     }
  136.  
  137.   /* Bump stack top and check for stack overflow.  */
  138.   if (++stack_top - dirstack >= stack_size)
  139.     {
  140.       /* Not enough storage--reallocate.  */
  141.       Stacked_Dir * temp;
  142.  
  143.       stack_size *= 2;
  144.       temp = (Stacked_Dir *)realloc(dirstack,
  145.                                     stack_size * sizeof(Stacked_Dir));
  146.       if (temp == 0)
  147.         {
  148.           errno = ENOMEM;
  149.           return 0;
  150.         }
  151.       stack_top += temp - dirstack;
  152.       dirstack = temp;
  153.     }
  154.  
  155.   /* Now push the directory onto the stack.  */
  156.   stack_top->dirname = strcpy(pool_end, dir);
  157.   stack_top->dirlen = dspace;
  158.  
  159.   return 1;
  160. }
  161.  
  162. /*
  163. ** Pop a directory off the stack, return its name.
  164. */
  165.  
  166. static char *
  167. pop_dir(void)
  168. {
  169.   char * retval;
  170.  
  171.   if (stack_top < dirstack || stack_top->dirlen == 0) /* empty stack */
  172.     return (char *)0;
  173.  
  174.   retval = stack_top->dirname;
  175.   stack_top--;
  176.   return retval;
  177. }
  178.  
  179. /*
  180. ** See if DIR1 is a parent of DIR2, return 1 if it is.
  181. ** Note that this is in no way a general solution: it only
  182. ** works in the context of mover() operation which always
  183. ** gets filenames rooted at the same directory.  Otherwise,
  184. ** you must pass a fully-qualified pathnames for this to
  185. ** work.
  186. */
  187.  
  188. static int
  189. is_parent(const char *dir1, const char *dir2)
  190. {
  191.   if (dir1 == 0 || dir2 == 0 || *dir1 == 0)
  192.     return 0;
  193.   while (*dir1 && *dir2 && tolower(*dir1) == tolower(*dir2))
  194.     {
  195.       dir1++;
  196.       dir2++;
  197.     }
  198.  
  199.   return *dir1 == '\0' && (*dir2 == '/' || *dir2 == '\\');
  200. }
  201.  
  202. /*
  203. ** Main workhorse.  This will be passed to __file_tree_walk()
  204. ** to do the actual job of moving a subtree to another branch
  205. ** of the filesystem hierarchy.
  206. */
  207.  
  208. static int
  209. mover(const char *from, const struct ffblk *ff)
  210. {
  211.   char  to[FILENAME_MAX];
  212.  
  213.   /* Did we just finish to empty a directory?  */
  214.   if (!is_parent(stack_top->dirname, from))
  215.     {
  216.       /* Remove an empty directory and pop it from stack.  */
  217.       if (remove(pop_dir()))
  218.         return -1;
  219.     }
  220.  
  221.   strcpy(to, target);
  222.   strcat(to, from + last);
  223.  
  224.   if (ff->ff_attrib & 0x10)
  225.     {
  226.       /* A directory -- create its namesake and push it onto stack.   */
  227.       if (mkdir(to, ff->ff_attrib & 0xffe7) == -1 ||
  228.           push_dir(from) == 0)
  229.         return -1;
  230.     }
  231.   else  /* a file -- move it */
  232.     {
  233.       if (_rename(from, to) == -1)
  234.         return -1;
  235.     }
  236.  
  237.   return 0;
  238. }
  239.  
  240.  
  241. /* -------------------------------------------------------
  242.        Main entry point and the only exported function
  243.    ------------------------------------------------------- */
  244.  
  245. int
  246. rename(const char *old, const char *new)
  247. {
  248.   int status;
  249.   char *p;
  250.   int source_attr, target_attr;
  251.   int old_dev, new_dev;
  252.   int e = errno;
  253.   int simple_should_do = 0; /* _rename() enough? */
  254.   int target_exists = 0;
  255.  
  256.   /* Much of the following quite tedious administrivia is necessary
  257.      to return a meaningful code in errno.  Otherwise, for most of
  258.      the calamities DOS will feed us the ubiquitous EACCES which
  259.      doesn't tell us much.  */
  260.  
  261.   /* There are several conditions which we must check upfront,
  262.      to ensure NEW isn't deleted unless rename() is to succeed.  */
  263.  
  264.   /* Fail with EFAULT if one of OLD or NEW is a NULL ptr.  */
  265.   if (old == 0 || new == 0)
  266.     {
  267.       errno = EFAULT;
  268.       return -1;
  269.     }
  270.  
  271.   /* Fail with ENAMETOOLONG if either OLD or NEW are too long.  This is
  272.      explicitly checked so that DOS filename truncation won't fool us.  */
  273.   if (strlen(old) > FILENAME_MAX || strlen(new) > FILENAME_MAX)
  274.     {
  275.       errno = ENAMETOOLONG;
  276.       return -1;
  277.     }
  278.  
  279.   /* Fail with ENOENT if OLD doesn't exist or is otherwise
  280.      inaccessible.  */
  281.   if ((source_attr = _chmod(old, 0)) == -1)
  282.     return -1;      /* errno set by _chmod() */
  283.  
  284.   /* Fail with EXDEV, if old and new aren't on the same device.  */
  285.   if (old[1] == ':')
  286.     old_dev = toupper(old[0]) - 'A';
  287.   else
  288.     old_dev = getdisk();
  289.   if (new[1] == ':')
  290.     new_dev = toupper(new[0]) - 'A';
  291.   else
  292.     new_dev = getdisk();
  293.   if (old_dev != new_dev)
  294.     {
  295.       errno = EXDEV;
  296.       return -1;
  297.     }
  298.  
  299.   /* Refuse to rename `.' or `..' or `d:.' or `d:..'  */
  300.   if ((old[0] == '.' && (old[1] == '\0' || (old[1] == '.' && old[2] == '\0'))) ||
  301.       (old[1] == ':' && old[2] == '.' && (old[3] == '\0' || (old[3] == '.' && old[4] == '\0'))))
  302.     {
  303.       errno = EINVAL;
  304.       return -1;
  305.     }
  306.  
  307.   /* Some problems can only happen if NEW already exists.  */
  308.   errno = 0;
  309.   target_attr = _chmod(new, 0);
  310.   if (errno != ENOENT)
  311.     {
  312.       int old_is_dir = (source_attr & 0x10) == 0x10;
  313.       int new_is_dir = (target_attr & 0x10) == 0x10;
  314.  
  315.       target_exists = 1;
  316.  
  317.       /* Fail if OLD is a directory while NEW isn't, or vice versa.  */
  318.       if (old_is_dir && !new_is_dir)
  319.         {
  320.           errno = ENOTDIR;
  321.           return -1;
  322.         }
  323.       else if (new_is_dir && !old_is_dir)
  324.         {
  325.           errno = EISDIR;
  326.           return -1;
  327.         }
  328.       else if (old_is_dir && new_is_dir)
  329.         {
  330.           char new_true[FILENAME_MAX], old_true[FILENAME_MAX];
  331.  
  332.           /* Fail if both OLD and NEW are directories and
  333.              OLD is parent of NEW.  */
  334.           errno = 0;
  335.           if (is_parent(_truename(old, old_true), _truename(new, new_true)))
  336.             {
  337.               errno = EINVAL;
  338.               return -1;
  339.             }
  340.           else if (errno)
  341.             return -1;
  342.           else
  343.             {
  344.               /* See if these two directories share a common parent.  If
  345.                  they do, then _rename() will do the job for us.  */
  346.  
  347.               char *s = strrchr(old_true, '\\'), *t = strrchr(new_true, '\\');
  348.  
  349.               *s = '\0'; /* truncate at end of parent directory name */
  350.               *t = '\0';
  351.               if (strcmp(old_true, new_true) == 0)
  352.                 simple_should_do = 1;
  353.             }
  354.         }
  355.       else
  356.         {
  357.           /* They both are files, _rename() must be enough.  */
  358.           simple_should_do = 1;
  359.         }
  360.     }
  361.  
  362.   /* On to some REAL work for a change.  Let DOS do the simple job:
  363.      moving a regular file, or renaming a directory.  Note that on
  364.      Windows 95 this will also move a directory to a subtree of
  365.      another parent directory.  */
  366.   if ((status = _rename(old, new)) == 0)
  367.     {
  368.       errno = e;    /* restore errno we inherited */
  369.       return 0;
  370.     }
  371.   else if (simple_should_do)
  372.     /* If _rename() fails and we KNOW it shouldn't, give up
  373.        and return -1 with whatever errno we have.  */
  374.     return -1;
  375.   else if (errno == EACCES && target_exists && (target_attr & 0x10) &&
  376.            _chmod(new, 0) != -1)
  377.     {
  378.       /* If target still exists (i.e., it wasn't removed inside
  379.          _rename()) and is a directory, assume it's not empty.
  380.          (We could verify that with findfirst()/findnext(), but that
  381.          would be too expensive.  Besides, what other reason could DOS
  382.          possibly have for not letting us remove a directory?? ;-)  */
  383.  
  384.       errno = ENOTEMPTY;
  385.       return -1;
  386.     }
  387.   else
  388.     {
  389.       /* We are in for some dirty work...  Sigh...
  390.          Recursively traverse the directory tree rooted at OLD,
  391.          moving regular files and creating subdirectories at NEW.  */
  392.  
  393.       int retval;
  394.  
  395.       strcpy(source, old);
  396.       last = strlen(source) - 1;
  397.  
  398.       strcpy(target, new);
  399.       if (strchr(target, '\\'))
  400.         for (p = target; *p; p++)
  401.           {
  402.             if (*p == '\\')
  403.               *p = '/';
  404.           }
  405.       if (source[last] == '/' || source[last] == '\\')
  406.         source[last] = '\0';
  407.       else
  408.         ++last;
  409.  
  410.       /* Create NEW and push it onto the stack.  */
  411.       if (mkdir(new, source_attr & 0xffe7) == -1 ||
  412.           init_stack() == 0 || push_dir(old) == 0)
  413.         return -1;
  414.  
  415.       /* Process all of its siblings.  */
  416.       retval = __file_tree_walk(source, mover);
  417.  
  418.       if (retval)
  419.         {
  420.  
  421.           /* If anything went wrong, remove NEW in a desperate attempt
  422.              to leave everything as we found it.  */
  423.           int savederr = errno;
  424.  
  425.           remove(target);
  426.           errno = savederr;
  427.         }
  428.       else
  429.         {
  430.           char *p2;
  431.  
  432.           /* Remove any (empty) directories that remain on the stack.
  433.              This is required because we only detect that a subdirectory
  434.              can be removed when we see the next entry in its parent dir.
  435.              Subdirectories which are the LAST (or the only) entries in
  436.              their parent directories, won't be removed.  */
  437.           while ((p2 = pop_dir()) != 0)
  438.             {
  439.               errno = e;
  440.               if (remove(p2) != 0)
  441.                 {
  442.                   int se = errno;
  443.  
  444.                   remove(target);
  445.                   errno = se;
  446.                   return -1;
  447.                 }
  448.             }
  449.         }
  450.  
  451.       return retval;
  452.     }
  453. }
  454.  
  455. #ifdef  TEST
  456.  
  457. #include <stdlib.h>
  458.  
  459. int
  460. main(int argc, char *argv[])
  461. {
  462.   if (argc > 2)
  463.     {
  464.       char msg[80];
  465.  
  466.       sprintf(msg, "movdir: %d", rename(argv[1], argv[2]));
  467.       if (errno)
  468.         perror(msg);
  469.       else
  470.         puts(msg);
  471.     }
  472.   else
  473.     printf("Usage: %s from to\n", argv[0]);
  474.  
  475.   return 0;
  476. }
  477.  
  478. #endif
  479.