summaryrefslogtreecommitdiff
path: root/src/ln.c
diff options
context:
space:
mode:
authorJim Meyering <jim@meyering.net>2007-08-23 11:51:01 +0200
committerJim Meyering <jim@meyering.net>2007-08-23 14:00:35 +0200
commitd02e4e77753f580ab91afc5915333222edc82104 (patch)
tree72992991c941ad26fb747e3cbd981eb620edc652 /src/ln.c
parent22ed81c410c197003782ba379cb3148306b0cd8a (diff)
downloadcoreutils-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/ln.c')
-rw-r--r--src/ln.c53
1 files changed, 52 insertions, 1 deletions
diff --git a/src/ln.c b/src/ln.c
index 3ddcfdfff..aa0e47382 100644
--- a/src/ln.c
+++ b/src/ln.c
@@ -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)
{