204 lines
4.8 KiB
C
204 lines
4.8 KiB
C
/* mv - move files Author: Adri Koppes */
|
|
|
|
/* 4/25/87 - J. Paradis Bug fixes for directory handling
|
|
* 3/15/88 - P. Housel More directory bug fixes
|
|
* 1/21/89 - B. Evans Allow owner to move non-writable file.
|
|
* Don't allow move to non-writable file.
|
|
* Delete target file in case of moving to dir.
|
|
* Requires making file readable before copy.
|
|
* Fix up mode after copy (cp uses the target
|
|
* mode, if any, and strips high bits).
|
|
* Use adequate size for dotdot buffer.
|
|
* Jan 90 - B. Evans The directory above /foo was calculated as
|
|
* "" instead of "/", so "mv /bin /bin0" left
|
|
* the old bin/. and bin/.. links in the
|
|
* current directory or nowhere.
|
|
*
|
|
* BUGS.
|
|
* There are race conditions all over.
|
|
* Error messages are not specific.
|
|
* There are magic sizes 64 and 128 instead of
|
|
* sizes related to PATH_MAX.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <signal.h>
|
|
#include <string.h>
|
|
|
|
struct stat st, st1;
|
|
|
|
main(argc, argv)
|
|
int argc;
|
|
char **argv;
|
|
{
|
|
char *destdir;
|
|
|
|
if (argc < 3) {
|
|
std_err("Usage: mv file1 file2 or mv dir1 dir2 or mv file1 file2 ... dir\n");
|
|
exit(1);
|
|
}
|
|
if (argc == 3) {
|
|
if (stat(argv[1], &st)) {
|
|
std_err("mv: ");
|
|
std_err(argv[1]);
|
|
std_err(" doesn't exist\n");
|
|
exit(1);
|
|
}
|
|
move(argv[1], argv[2]);
|
|
} else {
|
|
destdir = argv[--argc];
|
|
if (stat(destdir, &st)) {
|
|
std_err("mv: target directory ");
|
|
std_err(destdir);
|
|
std_err(" doesn't exist\n");
|
|
exit(1);
|
|
}
|
|
if ((st.st_mode & S_IFMT) != S_IFDIR) {
|
|
std_err("mv: target ");
|
|
std_err(destdir);
|
|
std_err(" not a directory\n");
|
|
exit(1);
|
|
}
|
|
while (--argc) move(*++argv, destdir);
|
|
}
|
|
exit(0);
|
|
}
|
|
|
|
move(old, new)
|
|
char *old, *new;
|
|
{
|
|
int retval;
|
|
char name[64];
|
|
char parent[64];
|
|
char *oldbase;
|
|
int i;
|
|
|
|
if ((oldbase = strrchr(old, '/')) == 0)
|
|
oldbase = old;
|
|
else
|
|
++oldbase;
|
|
|
|
/* It's too dangerous to fool with "." or ".." ! */
|
|
if ((strcmp(oldbase, ".") == 0) || (strcmp(oldbase, "..") == 0)) {
|
|
cant(old);
|
|
}
|
|
|
|
/* Don't move a non-existent file. */
|
|
if (stat(old, &st) != 0) cant(old);
|
|
|
|
/* If source is not writable, don't move it unless user owns it. */
|
|
if (access(old, 2) != 0 && st.st_uid != getuid()) cant(old);
|
|
|
|
/* If target is a directory, form its full name. The error of moving
|
|
* a directory to itself is caught later. */
|
|
if (stat(new, &st1) == 0 && (st1.st_mode & S_IFMT) == S_IFDIR) {
|
|
if ((strlen(oldbase) + strlen(new) + 2) > 64) cant(old);
|
|
strcpy(name, new);
|
|
strcat(name, "/");
|
|
strcat(name, oldbase);
|
|
new = name;
|
|
}
|
|
|
|
/* If target exists, do various overwriting checks. */
|
|
if (stat(new, &st1) == 0) {
|
|
|
|
/* Don't move a file to itself. */
|
|
if (st.st_dev == st1.st_dev && st.st_ino == st1.st_ino) cant(old);
|
|
|
|
/* Don't move on top of a directory. */
|
|
if ((st1.st_mode & S_IFMT) == S_IFDIR) cant(old);
|
|
|
|
/* Don't move on top of a non-writable file. */
|
|
if (access(new, 2) != 0) cant(old);
|
|
}
|
|
strcpy(parent, new);
|
|
|
|
for (i = (strlen(parent) - 1); i > 0; i--) {
|
|
if (parent[i] == '/') break;
|
|
}
|
|
|
|
if (i == 0) {
|
|
if (parent[0] == '/')
|
|
parent[1] = '\0'; /* parent of "/bin" is "/", not "." */
|
|
else
|
|
strcpy(parent, ".");
|
|
} else {
|
|
/* Null-terminate name at last slash */
|
|
parent[i] = '\0';
|
|
}
|
|
|
|
/* Prevent moving a directory into its own subdirectory */
|
|
if ((st.st_mode & S_IFMT) == S_IFDIR) {
|
|
char lower[128];
|
|
short int prevdev = -1;
|
|
unsigned short previno;
|
|
|
|
strcpy(lower, parent);
|
|
while (1) {
|
|
if (stat(lower, &st1) || (st1.st_dev == st.st_dev
|
|
&& st1.st_ino == st.st_ino))
|
|
cant(old);
|
|
|
|
/* Stop at root */
|
|
if (st1.st_dev == prevdev && st1.st_ino == previno) break;
|
|
prevdev = st1.st_dev;
|
|
previno = st1.st_ino;
|
|
strcat(lower, "/..");
|
|
}
|
|
}
|
|
|
|
/* If target exists, it is a file. Delete it so that following link()
|
|
* works except across file systems, to avoid copying. */
|
|
if (stat(new, &st1) == 0) unlink(new);
|
|
|
|
if (link(old, new))
|
|
if ((st.st_mode & S_IFMT) != S_IFDIR) {
|
|
switch (fork()) {
|
|
case 0:
|
|
if (!(st.st_mode & S_IRUSR))
|
|
chmod(old, st.st_mode | S_IRUSR);
|
|
setgid(getgid());
|
|
setuid(getuid());
|
|
execl("/bin/cp", "cp", old, new, (char *) 0);
|
|
execl("/usr/bin/cp", "cp", old, new, (char *) 0);
|
|
cant(old);
|
|
case -1:
|
|
std_err("mv: can't fork\n");
|
|
exit(1);
|
|
default:
|
|
wait(&retval);
|
|
if (!(st.st_mode & S_IRUSR)) chmod(old, st.st_mode);
|
|
if (retval) cant(old);
|
|
chmod(new, st.st_mode);
|
|
}
|
|
} else
|
|
cant(old);
|
|
|
|
/* If this was a directory that we moved, then we have * to update
|
|
* its ".." entry (in case it was moved some- * where else in the
|
|
* tree...) */
|
|
if ((st.st_mode & S_IFMT) == S_IFDIR) {
|
|
char dotdot[64 + 3];
|
|
|
|
/* Unlink the ".." entry */
|
|
strcpy(dotdot, new);
|
|
strcat(dotdot, "/..");
|
|
unlink(dotdot);
|
|
|
|
/* Now link it to its parent */
|
|
link(parent, dotdot);
|
|
}
|
|
utime(new, &st.st_atime);
|
|
unlink(old);
|
|
}
|
|
|
|
cant(name)
|
|
char *name;
|
|
{
|
|
std_err("mv: can't move ");
|
|
std_err(name);
|
|
std_err("\n");
|
|
exit(1);
|
|
}
|