diff options
author | Jim Meyering <jim@meyering.net> | 2007-08-23 11:51:01 +0200 |
---|---|---|
committer | Jim Meyering <jim@meyering.net> | 2007-08-23 14:00:35 +0200 |
commit | d02e4e77753f580ab91afc5915333222edc82104 (patch) | |
tree | 72992991c941ad26fb747e3cbd981eb620edc652 /src | |
parent | 22ed81c410c197003782ba379cb3148306b0cd8a (diff) | |
download | coreutils-d02e4e77753f580ab91afc5915333222edc82104.tar.xz |
Don't let ln be a party to destroying user data.
* src/ln.c: Include "file-set.h", "hash.h" and "hash-triple.h".
(dest_set, DEST_INFO_INITIAL_CAPACITY): New globals.
(do_link): Refuse to remove a just-created link.
Record a name,dev,ino triple for each link we create.
(main): Initialize dest_set, if needed.
* tests/mv/childproof: Test for the above fix.
* NEWS: Document this.
Reported by Eric Blake.
Signed-off-by: Jim Meyering <jim@meyering.net>
Diffstat (limited to 'src')
-rw-r--r-- | src/ln.c | 53 |
1 files changed, 52 insertions, 1 deletions
@@ -22,11 +22,14 @@ #include <getopt.h> #include "system.h" -#include "same.h" #include "backupfile.h" #include "error.h" #include "filenamecat.h" +#include "file-set.h" +#include "hash.h" +#include "hash-triple.h" #include "quote.h" +#include "same.h" #include "yesno.h" /* The official name of this program (e.g., no `g' prefix). */ @@ -82,6 +85,15 @@ static bool hard_dir_link; symlink-to-dir before creating the new link. */ static bool dereference_dest_dir_symlinks = true; +/* This is a set of destination name/inode/dev triples for hard links + created by ln. Use this data structure to avoid data loss via a + sequence of commands like this: + rm -rf a b c; mkdir a b c; touch a/f b/f; ln -f a/f b/f c && rm -r a b */ +static Hash_table *dest_set; + +/* Initial size of the dest_set hash table. */ +enum { DEST_INFO_INITIAL_CAPACITY = 61 }; + static struct option const long_options[] = { {"backup", optional_argument, NULL, 'b'}, @@ -178,6 +190,18 @@ do_link (const char *source, const char *dest) } } + /* If the current target was created as a hard link to another + source file, then refuse to unlink it. */ + if (dest_lstat_ok + && dest_set != NULL + && seen_file (dest_set, dest, &dest_stats)) + { + error (0, 0, + _("will not overwrite just-created %s with %s"), + quote_n (0, dest), quote_n (1, source)); + return false; + } + /* If --force (-f) has been specified without --backup, then before making a link ln must remove the destination file if it exists. (with --backup, it just renames any existing destination file) @@ -278,6 +302,10 @@ do_link (const char *source, const char *dest) if (ok) { + /* Right after creating a hard link, do this: (note dest name and + source_stats, which are also the just-linked-destinations stats) */ + record_file (dest_set, dest, &source_stats); + if (verbose) { if (dest_backup) @@ -514,6 +542,29 @@ main (int argc, char **argv) if (target_directory) { int i; + + /* Create the data structure we'll use to record which hard links we + create. Used to ensure that ln detects an obscure corner case that + might result in user data loss. Create it only if needed. */ + if (2 <= n_files + && remove_existing_files + /* Don't bother trying to protect symlinks, since ln clobbering + a just-created symlink won't ever lead to real data loss. */ + && ! symbolic_link + /* No destination hard link can be clobbered when making + numbered backups. */ + && backup_type != numbered_backups) + + { + dest_set = hash_initialize (DEST_INFO_INITIAL_CAPACITY, + NULL, + triple_hash, + triple_compare, + triple_free); + if (dest_set == NULL) + xalloc_die (); + } + ok = true; for (i = 0; i < n_files; ++i) { |