Index: usr.bin/xinstall/tests/install_test.sh =================================================================== --- usr.bin/xinstall/tests/install_test.sh +++ usr.bin/xinstall/tests/install_test.sh @@ -377,6 +377,28 @@ atf_check install -d dir1/dir2/dir3 } +atf_test_case hardlink_snap +hardlink_snap_head() { + atf_set "descr" "Check that -C snaps hardlinks when necessary" +} +hardlink_snap_body() { + echo test1 > s + atf_check install -C s a + ln a b + atf_check install -C s a + [ "$(stat -f %i a)" = "$(stat -f %i b)" ] || + atf_fail 'inode changed' + echo test2 > s + atf_check install -C s a + [ "$(stat -f %i a)" = "$(stat -f %i b)" ] && + atf_fail 'inode not changed' + ln -f a b + atf_check install -C -m 0 s a + [ "$(stat -f %i a)" = "$(stat -f %i b)" ] && + atf_fail 'inode not changed!' + true +} + atf_test_case symbolic_link_relative_absolute_common symbolic_link_relative_absolute_common_head() { atf_set "descr" "Verify -l rs with absolute paths having common components" @@ -440,4 +462,5 @@ atf_add_test_case symbolic_link_relative_absolute_source_and_dest2 atf_add_test_case symbolic_link_relative_absolute_common atf_add_test_case mkdir_simple + atf_add_test_case hardlink_snap } Index: usr.bin/xinstall/xinstall.c =================================================================== --- usr.bin/xinstall/xinstall.c +++ usr.bin/xinstall/xinstall.c @@ -767,7 +767,7 @@ { struct stat from_sb, temp_sb, to_sb; struct timespec tsb[2]; - int devnull, files_match, from_fd, serrno, target; + int devnull, files_match, from_fd, serrno, target, chgattr, tsbset; int tempcopy, temp_fd, to_fd; char backup[MAXPATHLEN], *p, pathbuf[MAXPATHLEN], tempfile[MAXPATHLEN]; char *digestresult; @@ -775,6 +775,7 @@ files_match = 0; from_fd = -1; to_fd = -1; + tsbset = 0; /* If try to install NULL file to a directory, fails. */ if (flags & DIRECTORY || strcmp(from_name, _PATH_DEVNULL)) { @@ -822,6 +823,17 @@ return; } + /* Determine if we're going to try to change the file's + * attributes later on. */ + chgattr = target && + ((!dounpriv && gid != (gid_t)-1 && gid != to_sb.st_gid) || + (!dounpriv && uid != (uid_t)-1 && uid != to_sb.st_uid) || + (mode != (to_sb.st_mode & + (dounpriv ? (S_IRWXU|S_IRWXG|S_IRWXO) : ALLPERMS))) || + (flags & SETFLAGS ? + to_sb.st_flags != fset : devnull ? 0 : + to_sb.st_flags != (from_sb.st_flags & ~UF_NODUMP))); + /* Only copy safe if the target exists. */ tempcopy = safecopy && target; @@ -832,12 +844,26 @@ if (docompare && !dostrip && target && S_ISREG(to_sb.st_mode)) { if ((to_fd = open(to_name, O_RDONLY, 0)) < 0) err(EX_OSERR, "%s", to_name); - if (devnull) - files_match = to_sb.st_size == 0; - else - files_match = !(compare(from_fd, from_name, + if (devnull ? to_sb.st_size == 0 : + compare(from_fd, from_name, (size_t)from_sb.st_size, to_fd, - to_name, (size_t)to_sb.st_size, &digestresult)); + to_name, (size_t)to_sb.st_size, + &digestresult) == 0) { + /* + * If target has more than one link and we + * want to change its attributes, we need to + * replace it in order to snap the extra + * links. Need to preserve target file times, + * though. + */ + if (to_sb.st_nlink != 1 && chgattr) { + tsbset = 1; + tsb[0] = to_sb.st_atim; + tsb[1] = to_sb.st_mtim; + } else { + files_match = 1; + } + } /* Close "to" file unless we match. */ if (!files_match) @@ -899,14 +925,16 @@ to_name, (size_t)to_sb.st_size, &digestresult) == 0) { /* - * If target has more than one link we need to - * replace it in order to snap the extra links. - * Need to preserve target file times, though. + * If target has more than one link and we + * want to change its attributes, we need to + * replace it in order to snap the extra + * links. Need to preserve target file times, + * though. */ - if (to_sb.st_nlink != 1) { + if (to_sb.st_nlink != 1 && chgattr) { + tsbset = 1; tsb[0] = to_sb.st_atim; tsb[1] = to_sb.st_mtim; - (void)utimensat(AT_FDCWD, tempfile, tsb, 0); } else { files_match = 1; (void)unlink(tempfile); @@ -976,9 +1004,11 @@ /* * Preserve the timestamp of the source file if necessary. */ - if (dopreserve && !files_match && !devnull) { - tsb[0] = from_sb.st_atim; - tsb[1] = from_sb.st_mtim; + if ((dopreserve && !files_match && !devnull) || tsbset) { + if (!tsbset) { + tsb[0] = from_sb.st_atim; + tsb[1] = from_sb.st_mtim; + } (void)utimensat(AT_FDCWD, to_name, tsb, 0); } @@ -1030,8 +1060,9 @@ * trying to turn off UF_NODUMP. If we're trying to set real flags, * then warn if the fs doesn't support it, otherwise fail. */ - if (!dounpriv & !devnull && (flags & SETFLAGS || - (from_sb.st_flags & ~UF_NODUMP) != to_sb.st_flags) && + if (!dounpriv && (flags & SETFLAGS || + (!devnull && (from_sb.st_flags & ~UF_NODUMP) != + to_sb.st_flags)) && fchflags(to_fd, flags & SETFLAGS ? fset : from_sb.st_flags & ~UF_NODUMP)) { if (flags & SETFLAGS) {