1 /* $OpenBSD: mv.c,v 1.43 2015/11/17 18:34:00 tedu Exp $ */ 2 /* $NetBSD: mv.c,v 1.9 1995/03/21 09:06:52 cgd Exp $ */ 3 4 /* 5 * Copyright (c) 1989, 1993, 1994 6 * The Regents of the University of California. All rights reserved. 7 * 8 * This code is derived from software contributed to Berkeley by 9 * Ken Smith of The State University of New York at Buffalo. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 3. Neither the name of the University nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 */ 35 36 #include <sys/time.h> 37 #include <sys/wait.h> 38 #include <sys/stat.h> 39 #include <sys/mount.h> 40 41 #include <err.h> 42 #include <errno.h> 43 #include <fcntl.h> 44 #include <stdio.h> 45 #include <stdlib.h> 46 #include <string.h> 47 #include <unistd.h> 48 #include <limits.h> 49 #include <pwd.h> 50 #include <grp.h> 51 52 #include "pathnames.h" 53 54 extern char *__progname; 55 56 int fflg, iflg; 57 int stdin_ok; 58 59 extern int cpmain(int argc, char **argv); 60 extern int rmmain(int argc, char **argv); 61 62 int mvcopy(char *, char *); 63 int do_move(char *, char *); 64 int fastcopy(char *, char *, struct stat *); 65 void usage(void); 66 67 int 68 main(int argc, char *argv[]) 69 { 70 int baselen, len, rval; 71 char *p, *endp; 72 struct stat sb; 73 int ch; 74 char path[PATH_MAX]; 75 76 while ((ch = getopt(argc, argv, "if")) != -1) 77 switch (ch) { 78 case 'i': 79 fflg = 0; 80 iflg = 1; 81 break; 82 case 'f': 83 iflg = 0; 84 fflg = 1; 85 break; 86 default: 87 usage(); 88 } 89 argc -= optind; 90 argv += optind; 91 92 if (argc < 2) 93 usage(); 94 95 stdin_ok = isatty(STDIN_FILENO); 96 97 /* 98 * If the stat on the target fails or the target isn't a directory, 99 * try the move. More than 2 arguments is an error in this case. 100 */ 101 if (stat(argv[argc - 1], &sb) || !S_ISDIR(sb.st_mode)) { 102 if (argc > 2) 103 usage(); 104 exit(do_move(argv[0], argv[1])); 105 } 106 107 /* It's a directory, move each file into it. */ 108 if (strlcpy(path, argv[argc - 1], sizeof path) >= sizeof path) 109 errx(1, "%s: destination pathname too long", *argv); 110 baselen = strlen(path); 111 endp = &path[baselen]; 112 if (*(endp - 1) != '/') { 113 *endp++ = '/'; 114 ++baselen; 115 } 116 for (rval = 0; --argc; ++argv) { 117 char *current_arg = *argv; 118 119 /* 120 * Get the name of the file to create from 121 * the argument. This is a bit tricky because 122 * in the case of b/ we actually want b and empty 123 * string 124 */ 125 if ((p = strrchr(current_arg, '/')) == NULL) 126 p = current_arg; 127 else { 128 /* Special case foo/ */ 129 if (!*(p+1)) { 130 while (p >= current_arg && *p == '/') 131 p--; 132 133 while (p >= current_arg && *p != '/') 134 p--; 135 } 136 137 p++; 138 } 139 140 if ((baselen + (len = strlen(p))) >= PATH_MAX) { 141 warnx("%s: destination pathname too long", *argv); 142 rval = 1; 143 } else { 144 memmove(endp, p, len + 1); 145 if (do_move(current_arg, path)) 146 rval = 1; 147 } 148 } 149 exit(rval); 150 } 151 152 int 153 do_move(char *from, char *to) 154 { 155 struct stat sb, fsb; 156 char modep[15]; 157 158 /* Source path must exist (symlink is OK). */ 159 if (lstat(from, &fsb)) { 160 warn("%s", from); 161 return (1); 162 } 163 164 /* 165 * (1) If the destination path exists, the -f option is not specified 166 * and either of the following conditions are true: 167 * 168 * (a) The permissions of the destination path do not permit 169 * writing and the standard input is a terminal. 170 * (b) The -i option is specified. 171 * 172 * the mv utility shall write a prompt to standard error and 173 * read a line from standard input. If the response is not 174 * affirmative, mv shall do nothing more with the current 175 * source file... 176 */ 177 if (!fflg && !access(to, F_OK)) { 178 int ask = 1; 179 int ch, first; 180 181 if (iflg && !access(from, F_OK)) { 182 (void)fprintf(stderr, "overwrite %s? ", to); 183 } else if (stdin_ok && access(to, W_OK) && !stat(to, &sb)) { 184 strmode(sb.st_mode, modep); 185 (void)fprintf(stderr, "override %s%s%s/%s for %s? ", 186 modep + 1, modep[9] == ' ' ? "" : " ", 187 user_from_uid(sb.st_uid, 0), 188 group_from_gid(sb.st_gid, 0), to); 189 } else 190 ask = 0; 191 if (ask) { 192 first = ch = getchar(); 193 while (ch != '\n' && ch != EOF) 194 ch = getchar(); 195 if (first != 'y' && first != 'Y') 196 return (0); 197 } 198 } 199 200 /* 201 * (2) If rename() succeeds, mv shall do nothing more with the 202 * current source file. If it fails for any other reason than 203 * EXDEV, mv shall write a diagnostic message to the standard 204 * error and do nothing more with the current source file. 205 * 206 * (3) If the destination path exists, and it is a file of type 207 * directory and source_file is not a file of type directory, 208 * or it is a file not of type directory, and source file is 209 * a file of type directory, mv shall write a diagnostic 210 * message to standard error, and do nothing more with the 211 * current source file... 212 */ 213 if (!rename(from, to)) 214 return (0); 215 216 if (errno != EXDEV) { 217 warn("rename %s to %s", from, to); 218 return (1); 219 } 220 221 /* Disallow moving a mount point. */ 222 if (S_ISDIR(fsb.st_mode)) { 223 struct statfs sfs; 224 char path[PATH_MAX]; 225 226 if (realpath(from, path) == NULL) { 227 warnx("cannot resolve %s", from); 228 return (1); 229 } 230 if (!statfs(path, &sfs) && !strcmp(path, sfs.f_mntonname)) { 231 warnx("cannot rename a mount point"); 232 return (1); 233 } 234 } 235 236 /* 237 * (4) If the destination path exists, mv shall attempt to remove it. 238 * If this fails for any reason, mv shall write a diagnostic 239 * message to the standard error and do nothing more with the 240 * current source file... 241 */ 242 if (!lstat(to, &sb)) { 243 if ((S_ISDIR(sb.st_mode)) ? rmdir(to) : unlink(to)) { 244 warn("can't remove %s", to); 245 return (1); 246 } 247 } 248 249 /* 250 * (5) The file hierarchy rooted in source_file shall be duplicated 251 * as a file hierarchy rooted in the destination path... 252 */ 253 return (S_ISREG(fsb.st_mode) ? 254 fastcopy(from, to, &fsb) : mvcopy(from, to)); 255 } 256 257 int 258 fastcopy(char *from, char *to, struct stat *sbp) 259 { 260 struct timespec ts[2]; 261 static u_int32_t blen; 262 static char *bp; 263 int nread, from_fd, to_fd; 264 int badchown = 0, serrno = 0; 265 266 if (!blen) { 267 blen = sbp->st_blksize; 268 if ((bp = malloc(blen)) == NULL) { 269 warn(NULL); 270 blen = 0; 271 return (1); 272 } 273 } 274 275 if ((from_fd = open(from, O_RDONLY, 0)) < 0) { 276 warn("%s", from); 277 return (1); 278 } 279 if ((to_fd = open(to, O_CREAT | O_TRUNC | O_WRONLY, 0600)) < 0) { 280 warn("%s", to); 281 (void)close(from_fd); 282 return (1); 283 } 284 285 if (fchown(to_fd, sbp->st_uid, sbp->st_gid)) { 286 serrno = errno; 287 badchown = 1; 288 } 289 (void) fchmod(to_fd, sbp->st_mode & ~(S_ISUID|S_ISGID)); 290 291 while ((nread = read(from_fd, bp, blen)) > 0) 292 if (write(to_fd, bp, nread) != nread) { 293 warn("%s", to); 294 goto err; 295 } 296 if (nread < 0) { 297 warn("%s", from); 298 err: if (unlink(to)) 299 warn("%s: remove", to); 300 (void)close(from_fd); 301 (void)close(to_fd); 302 return (1); 303 } 304 (void)close(from_fd); 305 306 if (badchown) { 307 if ((sbp->st_mode & (S_ISUID|S_ISGID))) { 308 warnc(serrno, 309 "%s: set owner/group; not setting setuid/setgid", 310 to); 311 sbp->st_mode &= ~(S_ISUID|S_ISGID); 312 } else if (!fflg) 313 warnc(serrno, "%s: set owner/group", to); 314 } 315 if (fchmod(to_fd, sbp->st_mode)) 316 warn("%s: set mode", to); 317 318 /* 319 * XXX 320 * NFS doesn't support chflags; ignore errors unless there's reason 321 * to believe we're losing bits. (Note, this still won't be right 322 * if the server supports flags and we were trying to *remove* flags 323 * on a file that we copied, i.e., that we didn't create.) 324 */ 325 errno = 0; 326 if (fchflags(to_fd, sbp->st_flags)) 327 if (errno != EOPNOTSUPP || sbp->st_flags != 0) 328 warn("%s: set flags", to); 329 330 ts[0] = sbp->st_atim; 331 ts[1] = sbp->st_mtim; 332 if (futimens(to_fd, ts)) 333 warn("%s: set times", to); 334 335 if (close(to_fd)) { 336 warn("%s", to); 337 return (1); 338 } 339 340 if (unlink(from)) { 341 warn("%s: remove", from); 342 return (1); 343 } 344 return (0); 345 } 346 347 int 348 mvcopy(char *from, char *to) 349 { 350 char *argv[3]; 351 352 argv[0] = from; 353 argv[1] = to; 354 argv[2] = NULL; 355 if (cpmain(2, argv)) { 356 warn("cp failed"); 357 _exit(1); 358 } 359 360 argv[0] = from; 361 argv[1] = NULL; 362 if (rmmain(1, argv)) { 363 warn("rm failed"); 364 _exit(1); 365 } 366 367 return (0); 368 } 369 370 void 371 usage(void) 372 { 373 (void)fprintf(stderr, "usage: %s [-fi] source target\n", __progname); 374 (void)fprintf(stderr, " %s [-fi] source ... directory\n", 375 __progname); 376 exit(1); 377 } 378