From 0ec20db824044c36412508310705f32e3503aa4c Mon Sep 17 00:00:00 2001 From: Dave Jones Date: Fri, 7 Jan 2005 05:58:05 +0000 Subject: [PATCH 01/25] auto-import hardlink-1.0-1.1 on branch devel from hardlink-1.0-1.1.src.rpm --- misc-utils/hardlink.c | 344 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 344 insertions(+) create mode 100644 misc-utils/hardlink.c diff --git a/misc-utils/hardlink.c b/misc-utils/hardlink.c new file mode 100644 index 000000000..ad2c2ffcf --- /dev/null +++ b/misc-utils/hardlink.c @@ -0,0 +1,344 @@ +/* Copyright (C) 2001 Red Hat, Inc. + + Written by Jakub Jelinek . + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public + License along with this program; see the file COPYING. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +/* Changes by Rémy Card to use constants and add option -n. */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NHASH 131072 /* Must be a power of 2! */ +#define NAMELEN 4096 +#define NBUF 64 + +struct _f; +typedef struct _h { + struct _h *next; + struct _f *chain; + off_t size; + time_t mtime; +} h; + +typedef struct _d { + struct _d *next; + char name[0]; +} d; + +d *dirs; + +h *hps[NHASH]; + +int no_link = 0; +int verbose = 0; +int content_only = 0; + +typedef struct _f { + struct _f *next; + ino_t ino; + dev_t dev; + unsigned int cksum; + char name[0]; +} f; + +inline unsigned int hash(off_t size, time_t mtime) +{ + return (size ^ mtime) & (NHASH - 1); +} + +inline int stcmp(struct stat *st1, struct stat *st2, int content_only) +{ + if (content_only) + return st1->st_size != st2->st_size; + return st1->st_mode != st2->st_mode || st1->st_uid != st2->st_uid || + st1->st_gid != st2->st_gid || st1->st_size != st2->st_size || + st1->st_mtime != st2->st_mtime; +} + +long long ndirs, nobjects, nregfiles, nmmap, ncomp, nlinks, nsaved; + +void doexit(int i) +{ + if (verbose) { + fprintf(stderr, "\n\n"); + fprintf(stderr, "Directories %lld\n", ndirs); + fprintf(stderr, "Objects %lld\n", nobjects); + fprintf(stderr, "IFREG %lld\n", nregfiles); + fprintf(stderr, "Mmaps %lld\n", nmmap); + fprintf(stderr, "Comparisons %lld\n", ncomp); + fprintf(stderr, "%s %lld\n", (no_link ? "Would link" : "Linked"), nlinks); + fprintf(stderr, "%s %lld\n", (no_link ? "Would save" : "saved"), nsaved); + } + exit(i); +} + +void usage(prog) +{ + fprintf (stderr, "Usage: %s [-cnv] directories...\n", prog); + exit(255); +} + +unsigned int buf[NBUF]; +char nambuf1[NAMELEN], nambuf2[NAMELEN]; + +void rf (char *name) +{ + struct stat st, st2, st3; + nobjects++; + if (lstat (name, &st)) + return; + if (S_ISDIR (st.st_mode)) { + d * dp = malloc(sizeof(d) + 1 + strlen (name)); + if (!dp) { + fprintf(stderr, "\nOut of memory 3\n"); + doexit(3); + } + strcpy (dp->name, name); + dp->next = dirs; + dirs = dp; + } else if (S_ISREG (st.st_mode)) { + int fd, i; + f * fp, * fp2; + h * hp; + char *p, *q; + char *n1, *n2; + int cksumsize = sizeof(buf); + unsigned int cksum; + time_t mtime = content_only ? 0 : st.st_mtime; + unsigned int hsh = hash (st.st_size, mtime); + nregfiles++; + if (verbose > 1) + fprintf(stderr, " %s", name); + fd = open (name, O_RDONLY); + if (fd < 0) return; + if (st.st_size < sizeof(buf)) { + cksumsize = st.st_size; + memset (((char *)buf) + cksumsize, 0, (sizeof(buf) - cksumsize) % sizeof(buf[0])); + } + if (read (fd, buf, cksumsize) != cksumsize) { + close(fd); + if (verbose > 1) + fprintf(stderr, "\r%*s\r", (int)strlen(name)+2, ""); + return; + } + cksumsize = (cksumsize + sizeof(buf[0]) - 1) / sizeof(buf[0]); + for (i = 0, cksum = 0; i < cksumsize; i++) { + if (cksum + buf[i] < cksum) + cksum += buf[i] + 1; + else + cksum += buf[i]; + } + for (hp = hps[hsh]; hp; hp = hp->next) + if (hp->size == st.st_size && hp->mtime == mtime) + break; + if (!hp) { + hp = malloc(sizeof(h)); + if (!hp) { + fprintf(stderr, "\nOut of memory 1\n"); + doexit(1); + } + hp->size = st.st_size; + hp->mtime = mtime; + hp->chain = NULL; + hp->next = hps[hsh]; + hps[hsh] = hp; + } + for (fp = hp->chain; fp; fp = fp->next) + if (fp->cksum == cksum) + break; + for (fp2 = fp; fp2 && fp2->cksum == cksum; fp2 = fp2->next) + if (fp2->ino == st.st_ino && fp2->dev == st.st_dev) { + close(fd); + if (verbose > 1) + fprintf(stderr, "\r%*s\r", (int)strlen(name)+2, ""); + return; + } + if (fp) { + p = mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + nmmap++; + if (p == (void *)-1) { + close(fd); + fprintf(stderr, "\nFailed to mmap %s\n", name); + return; + } + } + for (fp2 = fp; fp2 && fp2->cksum == cksum; fp2 = fp2->next) + if (!lstat (fp2->name, &st2) && S_ISREG (st2.st_mode) && + !stcmp (&st, &st2, content_only) && + st2.st_ino != st.st_ino && + st2.st_dev == st.st_dev) { + int fd2 = open (fp2->name, O_RDONLY); + if (fd2 < 0) continue; + if (fstat (fd2, &st2) || !S_ISREG (st2.st_mode)) { + close (fd2); + continue; + } + ncomp++; + q = mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd2, 0); + if (q == (void *)-1) { + close(fd2); + fprintf(stderr, "\nFailed to mmap %s\n", fp2->name); + continue; + } + if (memcmp (p, q, st.st_size)) { + munmap (q, st.st_size); + close(fd2); + continue; + } + munmap (q, st.st_size); + close(fd2); + if (lstat (name, &st3)) { + fprintf(stderr, "\nCould not stat %s again\n", name); + munmap (p, st.st_size); + close(fd); + return; + } + st3.st_atime = st.st_atime; + if (stcmp (&st, &st3, 0)) { + fprintf(stderr, "\nFile %s changed underneath us\n", name); + munmap (p, st.st_size); + close(fd); + return; + } + n1 = fp2->name; + n2 = name; + if (!no_link) { + strcpy (stpcpy (nambuf2, n2), ".$$$___cleanit___$$$"); + if (rename (n2, nambuf2)) { + fprintf(stderr, "\nFailed to rename %s to %s\n", n2, nambuf2); + continue; + } + if (link (n1, n2)) { + fprintf(stderr, "\nFailed to hardlink %s to %s\n", n1, n2); + if (rename (nambuf2, n2)) { + fprintf(stderr, "\nBad bad - failed to rename back %s to %s\n", nambuf2, n2); + } + munmap (p, st.st_size); + close(fd); + return; + } + unlink (nambuf2); + } + nlinks++; + if (st3.st_nlink > 1) { + /* We actually did not save anything this time, since the link second argument + had some other links as well. */ + if (verbose > 1) + fprintf(stderr, "\r%*s\r%s %s to %s\n", (int)strlen(name)+2, "", (no_link ? "Would link" : "Linked"), n1, n2); + } else { + nsaved+=((st.st_size+4095)/4096)*4096; + if (verbose > 1) + fprintf(stderr, "\r%*s\r%s %s to %s, %s %ld\n", (int)strlen(name)+2, "", (no_link ? "Would link" : "Linked"), n1, n2, (no_link ? "would save" : "saved"), st.st_size); + } + munmap (p, st.st_size); + close(fd); + return; + } + if (fp) + munmap (p, st.st_size); + fp2 = malloc(sizeof(f) + 1 + strlen (name)); + if (!fp2) { + fprintf(stderr, "\nOut of memory 2\n"); + doexit(2); + } + close(fd); + fp2->ino = st.st_ino; + fp2->dev = st.st_dev; + fp2->cksum = cksum; + strcpy(fp2->name, name); + if (fp) { + fp2->next = fp->next; + fp->next = fp2; + } else { + fp2->next = hp->chain; + hp->chain = fp2; + } + if (verbose > 1) + fprintf(stderr, "\r%*s\r", (int)strlen(name)+2, ""); + return; + } +} + +int main(int argc, char **argv) +{ + int ch; + int i; + char *p; + d * dp; + DIR *dh; + struct dirent *di; + while ((ch = getopt (argc, argv, "cnv")) != -1) { + switch (ch) { + case 'n': + no_link++; + break; + case 'v': + verbose++; + break; + case 'c': + content_only++; + break; + default: + usage(argv[0]); + } + } + if (optind >= argc) + usage(argv[0]); + for (i = optind; i < argc; i++) + rf(argv[i]); + while (dirs) { + dp = dirs; + dirs = dp->next; + strcpy (nambuf1, dp->name); + free (dp); + strcat (nambuf1, "/"); + p = strchr (nambuf1, 0); + dh = opendir (nambuf1); + if (dh == NULL) + continue; + ndirs++; + while ((di = readdir (dh)) != NULL) { + if (!di->d_name[0]) + continue; + if (di->d_name[0] == '.') { + char *q; + if (!di->d_name[1] || !strcmp (di->d_name, "..") || !strncmp (di->d_name, ".in.", 4)) + continue; + q = strrchr (di->d_name, '.'); + if (q && strlen (q) == 7 && q != di->d_name) { + *p = 0; + if (verbose) + fprintf(stderr, "Skipping %s%s\n", nambuf1, di->d_name); + continue; + } + } + strcpy (p, di->d_name); + rf(nambuf1); + } + closedir(dh); + } + doexit(0); + return 0; +} From 5edefc924240843c842f77020da0b7c418c1a50a Mon Sep 17 00:00:00 2001 From: Dave Jones Date: Wed, 9 Feb 2005 23:12:47 +0000 Subject: [PATCH 02/25] kill warning --- misc-utils/hardlink.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc-utils/hardlink.c b/misc-utils/hardlink.c index ad2c2ffcf..52cb41f42 100644 --- a/misc-utils/hardlink.c +++ b/misc-utils/hardlink.c @@ -94,7 +94,7 @@ void doexit(int i) exit(i); } -void usage(prog) +void usage(char *prog) { fprintf (stderr, "Usage: %s [-cnv] directories...\n", prog); exit(255); From a16d7294a868f4dacb1482d49820f0226e50e949 Mon Sep 17 00:00:00 2001 From: Jeremy Katz Date: Wed, 27 Apr 2005 22:05:13 +0000 Subject: [PATCH 03/25] - don't try to hardlink 0 byte files (#154404) --- misc-utils/hardlink.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misc-utils/hardlink.c b/misc-utils/hardlink.c index 52cb41f42..7a6cf6f52 100644 --- a/misc-utils/hardlink.c +++ b/misc-utils/hardlink.c @@ -175,7 +175,7 @@ void rf (char *name) fprintf(stderr, "\r%*s\r", (int)strlen(name)+2, ""); return; } - if (fp) { + if (fp && st.st_size > 0) { p = mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); nmmap++; if (p == (void *)-1) { @@ -191,7 +191,7 @@ void rf (char *name) st2.st_dev == st.st_dev) { int fd2 = open (fp2->name, O_RDONLY); if (fd2 < 0) continue; - if (fstat (fd2, &st2) || !S_ISREG (st2.st_mode)) { + if (fstat (fd2, &st2) || !S_ISREG (st2.st_mode) || st2.st_size == 0) { close (fd2); continue; } From a810992a7215994d020897215946fbddad3d8e68 Mon Sep 17 00:00:00 2001 From: Dave Jones Date: Fri, 26 Aug 2005 04:42:38 +0000 Subject: [PATCH 04/25] Document hardlink command line options. (Ville Skytta) (#161738) --- misc-utils/hardlink.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/misc-utils/hardlink.c b/misc-utils/hardlink.c index 7a6cf6f52..1e102101f 100644 --- a/misc-utils/hardlink.c +++ b/misc-utils/hardlink.c @@ -97,6 +97,9 @@ void doexit(int i) void usage(char *prog) { fprintf (stderr, "Usage: %s [-cnv] directories...\n", prog); + fprintf (stderr, " -c When finding candidates for linking, compare only file contents.\n"); + fprintf (stderr, " -n Don't actually link anything, just report what would be done.\n"); + fprintf (stderr, " -v Operate in verbose mode.\n"); exit(255); } From cabf1c1cb7409db30917e1d7e8546ec850e43c1d Mon Sep 17 00:00:00 2001 From: Jindrich Novy Date: Mon, 7 Nov 2005 14:07:33 +0000 Subject: [PATCH 05/25] add -h option --- misc-utils/hardlink.1 | 39 +++++++++++++++++++++++++++++++++++++++ misc-utils/hardlink.c | 8 +++++--- 2 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 misc-utils/hardlink.1 diff --git a/misc-utils/hardlink.1 b/misc-utils/hardlink.1 new file mode 100644 index 000000000..7ffc2b460 --- /dev/null +++ b/misc-utils/hardlink.1 @@ -0,0 +1,39 @@ +.TH "hardlink" "1" +.SH "NAME" +hardlink \- Consolidate duplicate files via hardlinks +.SH "SYNOPSIS" +.PP +\fBhardlink\fP [\fB-c\fP] [\fB-n\fP] [\fB-v\fP] [\fB-h\fP] directory1 [ directory2 ... ] +.SH "DESCRIPTION" +.PP +This manual page documents \fBhardlink\fP, a +program which consolidates duplicate files in one or more directories +using hardlinks. +.PP +\fBhardlink\fP traverses one +or more directories searching for duplicate files. When it finds duplicate +files, it uses one of them as the master. It then removes all other +duplicates and places a hardlink for each one pointing to the master file. +This allows for conservation of disk space where multiple directories +on a single filesystem contain many duplicate files. +.PP +Since hard links can only span a single filesystem, \fBhardlink\fP +is only useful when all directories specified are on the same filesystem. +.SH "OPTIONS" +.PP +.IP "\fB-c\fP" 10 +Compare only the contents of the files being considered for consolidation. +Disregards permission, ownership and other differences. +.IP "\fB-n\fP" 10 +Do not perform the consolidation; only print what would be changed. +.IP "\fB-v\fP" 10 +Enable verbose logging. +.IP "\fB-h\fP" 10 +Show help. +.SH "AUTHOR" +.PP +\fBhardlink\fP was written by Jakub Jelinek . +.PP +Man page written by Brian Long. +.PP +Man page updated by Jindrich Novy diff --git a/misc-utils/hardlink.c b/misc-utils/hardlink.c index 1e102101f..fd511c894 100644 --- a/misc-utils/hardlink.c +++ b/misc-utils/hardlink.c @@ -96,10 +96,11 @@ void doexit(int i) void usage(char *prog) { - fprintf (stderr, "Usage: %s [-cnv] directories...\n", prog); + fprintf (stderr, "Usage: %s [-cnvh] directories...\n", prog); fprintf (stderr, " -c When finding candidates for linking, compare only file contents.\n"); fprintf (stderr, " -n Don't actually link anything, just report what would be done.\n"); fprintf (stderr, " -v Operate in verbose mode.\n"); + fprintf (stderr, " -h Show help.\n"); exit(255); } @@ -125,7 +126,7 @@ void rf (char *name) int fd, i; f * fp, * fp2; h * hp; - char *p, *q; + char *p = NULL, *q; char *n1, *n2; int cksumsize = sizeof(buf); unsigned int cksum; @@ -292,7 +293,7 @@ int main(int argc, char **argv) d * dp; DIR *dh; struct dirent *di; - while ((ch = getopt (argc, argv, "cnv")) != -1) { + while ((ch = getopt (argc, argv, "cnvh")) != -1) { switch (ch) { case 'n': no_link++; @@ -303,6 +304,7 @@ int main(int argc, char **argv) case 'c': content_only++; break; + case 'h': default: usage(argv[0]); } From b4ece7768f292758e99de782ba248d3b0a9751d7 Mon Sep 17 00:00:00 2001 From: Jindrich Novy Date: Mon, 14 Nov 2005 08:31:25 +0000 Subject: [PATCH 06/25] mostly spec cleanup --- misc-utils/hardlink.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/misc-utils/hardlink.c b/misc-utils/hardlink.c index fd511c894..4625f1ea3 100644 --- a/misc-utils/hardlink.c +++ b/misc-utils/hardlink.c @@ -17,7 +17,8 @@ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -/* Changes by Rémy Card to use constants and add option -n. */ +/* Changes by Rémy Card to use constants and add option -n. */ +/* Changes by Jindrich Novy to add option -h. */ #define _GNU_SOURCE #include From a272f143e7daca35c3617ba02a9371dbe4b5a0ac Mon Sep 17 00:00:00 2001 From: Jindrich Novy Date: Sun, 29 Oct 2006 07:19:35 +0000 Subject: [PATCH 07/25] - update docs to describe highest verbosity -vv option (#210816) - use dist Resolves: 210816 --- misc-utils/hardlink.1 | 6 ++++-- misc-utils/hardlink.c | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/misc-utils/hardlink.1 b/misc-utils/hardlink.1 index 7ffc2b460..09fd7ac90 100644 --- a/misc-utils/hardlink.1 +++ b/misc-utils/hardlink.1 @@ -3,7 +3,7 @@ hardlink \- Consolidate duplicate files via hardlinks .SH "SYNOPSIS" .PP -\fBhardlink\fP [\fB-c\fP] [\fB-n\fP] [\fB-v\fP] [\fB-h\fP] directory1 [ directory2 ... ] +\fBhardlink\fP [\fB-c\fP] [\fB-n\fP] [\fB-v\fP] [\fB-vv\fP] [\fB-h\fP] directory1 [ directory2 ... ] .SH "DESCRIPTION" .PP This manual page documents \fBhardlink\fP, a @@ -27,7 +27,9 @@ Disregards permission, ownership and other differences. .IP "\fB-n\fP" 10 Do not perform the consolidation; only print what would be changed. .IP "\fB-v\fP" 10 -Enable verbose logging. +Print summary after hardlinking. +.IP "\fB-vv\fP" 10 +Print every hardlinked file and bytes saved. Also print summary after hardlinking. .IP "\fB-h\fP" 10 Show help. .SH "AUTHOR" diff --git a/misc-utils/hardlink.c b/misc-utils/hardlink.c index 4625f1ea3..a07d90c16 100644 --- a/misc-utils/hardlink.c +++ b/misc-utils/hardlink.c @@ -100,7 +100,8 @@ void usage(char *prog) fprintf (stderr, "Usage: %s [-cnvh] directories...\n", prog); fprintf (stderr, " -c When finding candidates for linking, compare only file contents.\n"); fprintf (stderr, " -n Don't actually link anything, just report what would be done.\n"); - fprintf (stderr, " -v Operate in verbose mode.\n"); + fprintf (stderr, " -v Print summary after hardlinking.\n"); + fprintf (stderr, " -vv Print every hardlinked file and bytes saved + summary.\n"); fprintf (stderr, " -h Show help.\n"); exit(255); } From 0cd6b1d3539a25a8bd5513e3c7d9b092a5ca47d3 Mon Sep 17 00:00:00 2001 From: Jindrich Novy Date: Thu, 3 Mar 2011 12:15:33 +0100 Subject: [PATCH 08/25] fix URL and remove mmap() (#676962, #672917) --- misc-utils/hardlink.c | 52 ++++++++++++++++--------------------------- 1 file changed, 19 insertions(+), 33 deletions(-) diff --git a/misc-utils/hardlink.c b/misc-utils/hardlink.c index a07d90c16..225c71f45 100644 --- a/misc-utils/hardlink.c +++ b/misc-utils/hardlink.c @@ -18,7 +18,7 @@ Boston, MA 02111-1307, USA. */ /* Changes by Rémy Card to use constants and add option -n. */ -/* Changes by Jindrich Novy to add option -h. */ +/* Changes by Jindrich Novy to add option -h, replace mmap(2) */ #define _GNU_SOURCE #include @@ -31,7 +31,8 @@ #include #include -#define NHASH 131072 /* Must be a power of 2! */ +#define NHASH (1<<17) /* Must be a power of 2! */ +#define NIOBUF (1<<12) #define NAMELEN 4096 #define NBUF 64 @@ -78,7 +79,7 @@ inline int stcmp(struct stat *st1, struct stat *st2, int content_only) st1->st_mtime != st2->st_mtime; } -long long ndirs, nobjects, nregfiles, nmmap, ncomp, nlinks, nsaved; +long long ndirs, nobjects, nregfiles, ncomp, nlinks, nsaved; void doexit(int i) { @@ -87,7 +88,6 @@ void doexit(int i) fprintf(stderr, "Directories %lld\n", ndirs); fprintf(stderr, "Objects %lld\n", nobjects); fprintf(stderr, "IFREG %lld\n", nregfiles); - fprintf(stderr, "Mmaps %lld\n", nmmap); fprintf(stderr, "Comparisons %lld\n", ncomp); fprintf(stderr, "%s %lld\n", (no_link ? "Would link" : "Linked"), nlinks); fprintf(stderr, "%s %lld\n", (no_link ? "Would save" : "saved"), nsaved); @@ -107,6 +107,7 @@ void usage(char *prog) } unsigned int buf[NBUF]; +char iobuf1[NIOBUF], iobuf2[NIOBUF]; char nambuf1[NAMELEN], nambuf2[NAMELEN]; void rf (char *name) @@ -128,12 +129,12 @@ void rf (char *name) int fd, i; f * fp, * fp2; h * hp; - char *p = NULL, *q; char *n1, *n2; int cksumsize = sizeof(buf); unsigned int cksum; time_t mtime = content_only ? 0 : st.st_mtime; unsigned int hsh = hash (st.st_size, mtime); + off_t fsize; nregfiles++; if (verbose > 1) fprintf(stderr, " %s", name); @@ -181,15 +182,6 @@ void rf (char *name) fprintf(stderr, "\r%*s\r", (int)strlen(name)+2, ""); return; } - if (fp && st.st_size > 0) { - p = mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); - nmmap++; - if (p == (void *)-1) { - close(fd); - fprintf(stderr, "\nFailed to mmap %s\n", name); - return; - } - } for (fp2 = fp; fp2 && fp2->cksum == cksum; fp2 = fp2->next) if (!lstat (fp2->name, &st2) && S_ISREG (st2.st_mode) && !stcmp (&st, &st2, content_only) && @@ -202,29 +194,27 @@ void rf (char *name) continue; } ncomp++; - q = mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd2, 0); - if (q == (void *)-1) { - close(fd2); - fprintf(stderr, "\nFailed to mmap %s\n", fp2->name); - continue; - } - if (memcmp (p, q, st.st_size)) { - munmap (q, st.st_size); - close(fd2); - continue; - } - munmap (q, st.st_size); - close(fd2); + lseek(fd, 0, SEEK_SET); + for (fsize = st.st_size; fsize > 0; fsize -= NIOBUF) { + off_t rsize = fsize >= NIOBUF ? NIOBUF : fsize; + if (read (fd, iobuf1, rsize) != rsize || read (fd2, iobuf2, rsize) != rsize) { + close(fd); + close(fd2); + fprintf(stderr, "\nReading error\n"); + return; + } + if (memcmp (iobuf1, iobuf2, rsize)) break; + } + close(fd2); + if (fsize > 0) continue; if (lstat (name, &st3)) { fprintf(stderr, "\nCould not stat %s again\n", name); - munmap (p, st.st_size); close(fd); return; } st3.st_atime = st.st_atime; if (stcmp (&st, &st3, 0)) { fprintf(stderr, "\nFile %s changed underneath us\n", name); - munmap (p, st.st_size); close(fd); return; } @@ -241,7 +231,6 @@ void rf (char *name) if (rename (nambuf2, n2)) { fprintf(stderr, "\nBad bad - failed to rename back %s to %s\n", nambuf2, n2); } - munmap (p, st.st_size); close(fd); return; } @@ -258,12 +247,9 @@ void rf (char *name) if (verbose > 1) fprintf(stderr, "\r%*s\r%s %s to %s, %s %ld\n", (int)strlen(name)+2, "", (no_link ? "Would link" : "Linked"), n1, n2, (no_link ? "would save" : "saved"), st.st_size); } - munmap (p, st.st_size); close(fd); return; } - if (fp) - munmap (p, st.st_size); fp2 = malloc(sizeof(f) + 1 + strlen (name)); if (!fp2) { fprintf(stderr, "\nOut of memory 2\n"); From 94b040b06ba018af0799e4117fe5c8dc99f70ef4 Mon Sep 17 00:00:00 2001 From: Jindrich Novy Date: Fri, 21 Oct 2011 02:49:08 +0200 Subject: [PATCH 09/25] fix possible buffer overflows, integer overflows, update man page --- misc-utils/hardlink.1 | 13 ++++- misc-utils/hardlink.c | 125 ++++++++++++++++++++++++++++-------------- 2 files changed, 95 insertions(+), 43 deletions(-) diff --git a/misc-utils/hardlink.1 b/misc-utils/hardlink.1 index 09fd7ac90..0590e8464 100644 --- a/misc-utils/hardlink.1 +++ b/misc-utils/hardlink.1 @@ -6,11 +6,11 @@ hardlink \- Consolidate duplicate files via hardlinks \fBhardlink\fP [\fB-c\fP] [\fB-n\fP] [\fB-v\fP] [\fB-vv\fP] [\fB-h\fP] directory1 [ directory2 ... ] .SH "DESCRIPTION" .PP -This manual page documents \fBhardlink\fP, a +This manual page documents \fBhardlink\fP, a program which consolidates duplicate files in one or more directories using hardlinks. .PP -\fBhardlink\fP traverses one +\fBhardlink\fP traverses one or more directories searching for duplicate files. When it finds duplicate files, it uses one of them as the master. It then removes all other duplicates and places a hardlink for each one pointing to the master file. @@ -34,8 +34,15 @@ Print every hardlinked file and bytes saved. Also print summary after hardlinkin Show help. .SH "AUTHOR" .PP -\fBhardlink\fP was written by Jakub Jelinek . +\fBhardlink\fP was written by Jakub Jelinek . .PP Man page written by Brian Long. .PP Man page updated by Jindrich Novy +.SH "BUGS" +.PP +\fBhardlink\fP assumes that its target directory trees do not change from under +it. If a directory tree does change, this may result in \fBhardlink\fP +accessing files and/or directories outside of the intended directory tree. +Thus, you must avoid running \fBhardlink\fP on potentially changing directory +trees, and especially on directory trees under control of another user. diff --git a/misc-utils/hardlink.c b/misc-utils/hardlink.c index 225c71f45..51a71cf97 100644 --- a/misc-utils/hardlink.c +++ b/misc-utils/hardlink.c @@ -1,24 +1,24 @@ /* Copyright (C) 2001 Red Hat, Inc. Written by Jakub Jelinek . - + This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ - + /* Changes by Rémy Card to use constants and add option -n. */ -/* Changes by Jindrich Novy to add option -h, replace mmap(2) */ +/* Changes by Jindrich Novy to add option -h, replace mmap(2), fix overflows */ #define _GNU_SOURCE #include @@ -83,7 +83,7 @@ long long ndirs, nobjects, nregfiles, ncomp, nlinks, nsaved; void doexit(int i) { - if (verbose) { + if (verbose) { fprintf(stderr, "\n\n"); fprintf(stderr, "Directories %lld\n", ndirs); fprintf(stderr, "Objects %lld\n", nobjects); @@ -108,28 +108,59 @@ void usage(char *prog) unsigned int buf[NBUF]; char iobuf1[NIOBUF], iobuf2[NIOBUF]; -char nambuf1[NAMELEN], nambuf2[NAMELEN]; -void rf (char *name) +inline size_t add2(size_t a, size_t b) +{ + size_t sum = a + b; + if (sum < a) { + fprintf(stderr, "\nInteger overflow\n"); + doexit(5); + } + return sum; +} + +inline size_t add3(size_t a, size_t b, size_t c) +{ + return add2(add2(a, b), c); +} + +typedef struct { + char *buf; + size_t alloc; +} dynstr; + +void growstr(dynstr *str, size_t newlen) +{ + if (newlen < str->alloc) + return; + str->buf = realloc(str->buf, str->alloc = add2(newlen, 1)); + if (!str->buf) { + fprintf(stderr, "\nOut of memory 4\n"); + doexit(4); + } +} + +void rf (const char *name) { struct stat st, st2, st3; + const size_t namelen = strlen(name); nobjects++; if (lstat (name, &st)) return; if (S_ISDIR (st.st_mode)) { - d * dp = malloc(sizeof(d) + 1 + strlen (name)); + d * dp = malloc(add3(sizeof(d), namelen, 1)); if (!dp) { fprintf(stderr, "\nOut of memory 3\n"); doexit(3); } - strcpy (dp->name, name); + memcpy(dp->name, name, namelen + 1); dp->next = dirs; dirs = dp; } else if (S_ISREG (st.st_mode)) { int fd, i; f * fp, * fp2; h * hp; - char *n1, *n2; + const char *n1, *n2; int cksumsize = sizeof(buf); unsigned int cksum; time_t mtime = content_only ? 0 : st.st_mtime; @@ -146,8 +177,8 @@ void rf (char *name) } if (read (fd, buf, cksumsize) != cksumsize) { close(fd); - if (verbose > 1) - fprintf(stderr, "\r%*s\r", (int)strlen(name)+2, ""); + if (verbose > 1 && namelen <= NAMELEN) + fprintf(stderr, "\r%*s\r", (int)(namelen + 2), ""); return; } cksumsize = (cksumsize + sizeof(buf[0]) - 1) / sizeof(buf[0]); @@ -178,8 +209,8 @@ void rf (char *name) for (fp2 = fp; fp2 && fp2->cksum == cksum; fp2 = fp2->next) if (fp2->ino == st.st_ino && fp2->dev == st.st_dev) { close(fd); - if (verbose > 1) - fprintf(stderr, "\r%*s\r", (int)strlen(name)+2, ""); + if (verbose > 1 && namelen <= NAMELEN) + fprintf(stderr, "\r%*s\r", (int)(namelen + 2), ""); return; } for (fp2 = fp; fp2 && fp2->cksum == cksum; fp2 = fp2->next) @@ -221,36 +252,45 @@ void rf (char *name) n1 = fp2->name; n2 = name; if (!no_link) { - strcpy (stpcpy (nambuf2, n2), ".$$$___cleanit___$$$"); - if (rename (n2, nambuf2)) { - fprintf(stderr, "\nFailed to rename %s to %s\n", n2, nambuf2); + const char *suffix = ".$$$___cleanit___$$$"; + const size_t suffixlen = strlen(suffix); + size_t n2len = strlen(n2); + dynstr nam2 = {NULL, 0}; + growstr(&nam2, add2(n2len, suffixlen)); + memcpy(nam2.buf, n2, n2len); + memcpy(&nam2.buf[n2len], suffix, suffixlen + 1); + if (rename (n2, nam2.buf)) { + fprintf(stderr, "\nFailed to rename %s to %s\n", n2, nam2.buf); + free(nam2.buf); continue; } if (link (n1, n2)) { fprintf(stderr, "\nFailed to hardlink %s to %s\n", n1, n2); - if (rename (nambuf2, n2)) { - fprintf(stderr, "\nBad bad - failed to rename back %s to %s\n", nambuf2, n2); + if (rename (nam2.buf, n2)) { + fprintf(stderr, "\nBad bad - failed to rename back %s to %s\n", nam2.buf, n2); } close(fd); + free(nam2.buf); return; } - unlink (nambuf2); + unlink (nam2.buf); + free(nam2.buf); } nlinks++; if (st3.st_nlink > 1) { /* We actually did not save anything this time, since the link second argument had some other links as well. */ if (verbose > 1) - fprintf(stderr, "\r%*s\r%s %s to %s\n", (int)strlen(name)+2, "", (no_link ? "Would link" : "Linked"), n1, n2); + fprintf(stderr, "\r%*s\r%s %s to %s\n", (int)(((namelen > NAMELEN) ? 0 : namelen) + 2), "", (no_link ? "Would link" : "Linked"), n1, n2); } else { nsaved+=((st.st_size+4095)/4096)*4096; if (verbose > 1) - fprintf(stderr, "\r%*s\r%s %s to %s, %s %ld\n", (int)strlen(name)+2, "", (no_link ? "Would link" : "Linked"), n1, n2, (no_link ? "would save" : "saved"), st.st_size); + fprintf(stderr, "\r%*s\r%s %s to %s, %s %ld\n", (int)(((namelen > NAMELEN) ? 0 : namelen) + 2), "", (no_link ? "Would link" : "Linked"), n1, n2, (no_link ? "would save" : "saved"), st.st_size); } close(fd); return; } - fp2 = malloc(sizeof(f) + 1 + strlen (name)); + fp2 = malloc(add3(sizeof(f), namelen, 1)); if (!fp2) { fprintf(stderr, "\nOut of memory 2\n"); doexit(2); @@ -259,7 +299,7 @@ void rf (char *name) fp2->ino = st.st_ino; fp2->dev = st.st_dev; fp2->cksum = cksum; - strcpy(fp2->name, name); + memcpy(fp2->name, name, namelen + 1); if (fp) { fp2->next = fp->next; fp->next = fp2; @@ -267,8 +307,8 @@ void rf (char *name) fp2->next = hp->chain; hp->chain = fp2; } - if (verbose > 1) - fprintf(stderr, "\r%*s\r", (int)strlen(name)+2, ""); + if (verbose > 1 && namelen <= NAMELEN) + fprintf(stderr, "\r%*s\r", (int)(namelen + 2), ""); return; } } @@ -277,10 +317,7 @@ int main(int argc, char **argv) { int ch; int i; - char *p; - d * dp; - DIR *dh; - struct dirent *di; + dynstr nam1 = {NULL, 0}; while ((ch = getopt (argc, argv, "cnvh")) != -1) { switch (ch) { case 'n': @@ -302,13 +339,17 @@ int main(int argc, char **argv) for (i = optind; i < argc; i++) rf(argv[i]); while (dirs) { - dp = dirs; + DIR *dh; + struct dirent *di; + d * dp = dirs; + size_t nam1baselen = strlen(dp->name); dirs = dp->next; - strcpy (nambuf1, dp->name); + growstr(&nam1, add2(nam1baselen, 1)); + memcpy(nam1.buf, dp->name, nam1baselen); free (dp); - strcat (nambuf1, "/"); - p = strchr (nambuf1, 0); - dh = opendir (nambuf1); + nam1.buf[nam1baselen++] = '/'; + nam1.buf[nam1baselen] = 0; + dh = opendir (nam1.buf); if (dh == NULL) continue; ndirs++; @@ -321,14 +362,18 @@ int main(int argc, char **argv) continue; q = strrchr (di->d_name, '.'); if (q && strlen (q) == 7 && q != di->d_name) { - *p = 0; + nam1.buf[nam1baselen] = 0; if (verbose) - fprintf(stderr, "Skipping %s%s\n", nambuf1, di->d_name); + fprintf(stderr, "Skipping %s%s\n", nam1.buf, di->d_name); continue; } } - strcpy (p, di->d_name); - rf(nambuf1); + { + size_t subdirlen; + growstr(&nam1, add2(nam1baselen, subdirlen = strlen(di->d_name))); + memcpy(&nam1.buf[nam1baselen], di->d_name, add2(subdirlen, 1)); + } + rf(nam1.buf); } closedir(dh); } From c23b4a230531da663fc35a478754c641866cb6df Mon Sep 17 00:00:00 2001 From: Jindrich Novy Date: Sun, 15 Apr 2012 11:22:10 +0200 Subject: [PATCH 10/25] do not allow to hardlink files across filesystems by default (#786719) (use -f option to override) --- misc-utils/hardlink.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/misc-utils/hardlink.c b/misc-utils/hardlink.c index 51a71cf97..a7c72492d 100644 --- a/misc-utils/hardlink.c +++ b/misc-utils/hardlink.c @@ -18,7 +18,7 @@ Boston, MA 02111-1307, USA. */ /* Changes by Rémy Card to use constants and add option -n. */ -/* Changes by Jindrich Novy to add option -h, replace mmap(2), fix overflows */ +/* Changes by Jindrich Novy to add option -h, -f, replace mmap(2), fix overflows */ #define _GNU_SOURCE #include @@ -56,6 +56,7 @@ h *hps[NHASH]; int no_link = 0; int verbose = 0; int content_only = 0; +int force = 0; typedef struct _f { struct _f *next; @@ -97,11 +98,12 @@ void doexit(int i) void usage(char *prog) { - fprintf (stderr, "Usage: %s [-cnvh] directories...\n", prog); + fprintf (stderr, "Usage: %s [-cnvhf] directories...\n", prog); fprintf (stderr, " -c When finding candidates for linking, compare only file contents.\n"); fprintf (stderr, " -n Don't actually link anything, just report what would be done.\n"); fprintf (stderr, " -v Print summary after hardlinking.\n"); fprintf (stderr, " -vv Print every hardlinked file and bytes saved + summary.\n"); + fprintf (stderr, " -f Force hardlinking across filesystems.\n"); fprintf (stderr, " -h Show help.\n"); exit(255); } @@ -139,7 +141,7 @@ void growstr(dynstr *str, size_t newlen) doexit(4); } } - +dev_t dev = 0; void rf (const char *name) { struct stat st, st2, st3; @@ -147,6 +149,13 @@ void rf (const char *name) nobjects++; if (lstat (name, &st)) return; + if (st.st_dev != dev && !force) { + if (dev) { + fprintf(stderr, "%s is on different filesystem than the rest.\nUse -f option to override.\n", name); + doexit(6); + } + dev = st.st_dev; + } if (S_ISDIR (st.st_mode)) { d * dp = malloc(add3(sizeof(d), namelen, 1)); if (!dp) { @@ -318,7 +327,7 @@ int main(int argc, char **argv) int ch; int i; dynstr nam1 = {NULL, 0}; - while ((ch = getopt (argc, argv, "cnvh")) != -1) { + while ((ch = getopt (argc, argv, "cnvhf")) != -1) { switch (ch) { case 'n': no_link++; @@ -329,6 +338,9 @@ int main(int argc, char **argv) case 'c': content_only++; break; + case 'f': + force=1; + break; case 'h': default: usage(argv[0]); From 348af99a6296d9d15de10700594e1403d23a9a37 Mon Sep 17 00:00:00 2001 From: Jan Zeleny Date: Wed, 10 Apr 2013 14:47:17 +0200 Subject: [PATCH 11/25] Mention -f option in the man page --- misc-utils/hardlink.1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/misc-utils/hardlink.1 b/misc-utils/hardlink.1 index 0590e8464..04228f4bb 100644 --- a/misc-utils/hardlink.1 +++ b/misc-utils/hardlink.1 @@ -24,6 +24,8 @@ is only useful when all directories specified are on the same filesystem. .IP "\fB-c\fP" 10 Compare only the contents of the files being considered for consolidation. Disregards permission, ownership and other differences. +.IP "\fB-f\fP" 10 +Force hardlinking across file systems. .IP "\fB-n\fP" 10 Do not perform the consolidation; only print what would be changed. .IP "\fB-v\fP" 10 From c11af66f0fce8ded25f92c8dde294afb2a64b735 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Javier=20Tsao=20Sant=C3=ADn?= Date: Sun, 10 Jul 2016 03:57:43 +0200 Subject: [PATCH 12/25] spec file reflects the atomic hardlinking patch; removed cleaning buildroot (redundant); current FSF address at .c source file --- misc-utils/hardlink.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/misc-utils/hardlink.c b/misc-utils/hardlink.c index a7c72492d..ef042deba 100644 --- a/misc-utils/hardlink.c +++ b/misc-utils/hardlink.c @@ -12,10 +12,9 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public - License along with this program; see the file COPYING. If not, - write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, - Boston, MA 02111-1307, USA. */ + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /* Changes by Rémy Card to use constants and add option -n. */ /* Changes by Jindrich Novy to add option -h, -f, replace mmap(2), fix overflows */ From cbb0524c7c8b4aa62d1dc2fd07c7392ede7cfc5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Javier=20Tsao=20Sant=C3=ADn?= Date: Sun, 10 Jul 2016 04:03:41 +0200 Subject: [PATCH 13/25] Revert "spec file reflects the atomic hardlinking patch; removed cleaning buildroot (redundant); current FSF address at .c source file" This reverts commit bb9e76ae339794c2243ae294207942b7ea278364. --- misc-utils/hardlink.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/misc-utils/hardlink.c b/misc-utils/hardlink.c index ef042deba..3521cb3ea 100644 --- a/misc-utils/hardlink.c +++ b/misc-utils/hardlink.c @@ -12,9 +12,10 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software Foundation, - Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + You should have received a copy of the GNU General Public + License along with this program; see the file COPYING. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ /* Changes by Rémy Card to use constants and add option -n. */ /* Changes by Jindrich Novy to add option -h, -f, replace mmap(2), fix overflows */ @@ -29,6 +30,7 @@ #include #include #include +#include #define NHASH (1<<17) /* Must be a power of 2! */ #define NIOBUF (1<<12) @@ -267,21 +269,22 @@ void rf (const char *name) growstr(&nam2, add2(n2len, suffixlen)); memcpy(nam2.buf, n2, n2len); memcpy(&nam2.buf[n2len], suffix, suffixlen + 1); - if (rename (n2, nam2.buf)) { - fprintf(stderr, "\nFailed to rename %s to %s\n", n2, nam2.buf); + /* First create a temporary link to n1 under a new name */ + if (link(n1, nam2.buf)) { + fprintf(stderr, "\nFailed to hardlink %s to %s (create temporary link as %s failed - %s)\n", n1, n2, nam2.buf, strerror(errno)); free(nam2.buf); continue; } - if (link (n1, n2)) { - fprintf(stderr, "\nFailed to hardlink %s to %s\n", n1, n2); - if (rename (nam2.buf, n2)) { - fprintf(stderr, "\nBad bad - failed to rename back %s to %s\n", nam2.buf, n2); + /* Then rename into place over the existing n2 */ + if (rename (nam2.buf, n2)) { + fprintf(stderr, "\nFailed to hardlink %s to %s (rename temporary link to %s failed - %s)\n", n1, n2, n2, strerror(errno)); + /* Something went wrong, try to remove the now redundant temporary link */ + if (unlink(nam2.buf)) { + fprintf(stderr, "\nFailed to remove temporary link %s - %s\n", nam2.buf, strerror(errno)); } - close(fd); free(nam2.buf); - return; + continue; } - unlink (nam2.buf); free(nam2.buf); } nlinks++; From 4d072ba7155358ac50531bda512442f514b37e37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Javier=20Tsao=20Sant=C3=ADn?= Date: Sun, 10 Jul 2016 04:12:06 +0200 Subject: [PATCH 14/25] spec file reflects the atomic hardlinking patch; removed cleaning buildroot (redundant); update FSF address at .c source file --- misc-utils/hardlink.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/misc-utils/hardlink.c b/misc-utils/hardlink.c index 3521cb3ea..e661e5f0c 100644 --- a/misc-utils/hardlink.c +++ b/misc-utils/hardlink.c @@ -12,13 +12,13 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public - License along with this program; see the file COPYING. If not, - write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, - Boston, MA 02111-1307, USA. */ + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /* Changes by Rémy Card to use constants and add option -n. */ /* Changes by Jindrich Novy to add option -h, -f, replace mmap(2), fix overflows */ +/* Changes by Travers Carter to make atomic hardlinking */ #define _GNU_SOURCE #include From 92c79dc79e1154f15d6049961287e367fc43aab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Javier=20Tsao=20Sant=C3=ADn?= Date: Thu, 16 Feb 2017 20:08:40 +0100 Subject: [PATCH 15/25] Fixed 32 bit build with gcc7 (RH Bugzilla ID 1422989) --- misc-utils/hardlink.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/misc-utils/hardlink.c b/misc-utils/hardlink.c index e661e5f0c..16d8163e2 100644 --- a/misc-utils/hardlink.c +++ b/misc-utils/hardlink.c @@ -67,12 +67,12 @@ typedef struct _f { char name[0]; } f; -inline unsigned int hash(off_t size, time_t mtime) +__attribute__((always_inline)) inline unsigned int hash(off_t size, time_t mtime) { return (size ^ mtime) & (NHASH - 1); } -inline int stcmp(struct stat *st1, struct stat *st2, int content_only) +__attribute__((always_inline)) inline int stcmp(struct stat *st1, struct stat *st2, int content_only) { if (content_only) return st1->st_size != st2->st_size; @@ -112,7 +112,7 @@ void usage(char *prog) unsigned int buf[NBUF]; char iobuf1[NIOBUF], iobuf2[NIOBUF]; -inline size_t add2(size_t a, size_t b) +__attribute__((always_inline)) inline size_t add2(size_t a, size_t b) { size_t sum = a + b; if (sum < a) { @@ -122,7 +122,7 @@ inline size_t add2(size_t a, size_t b) return sum; } -inline size_t add3(size_t a, size_t b, size_t c) +__attribute__((always_inline)) inline size_t add3(size_t a, size_t b, size_t c) { return add2(add2(a, b), c); } From 551e8963f4ca7850a2d91bcb5ffb6032a690e93f Mon Sep 17 00:00:00 2001 From: Todd Lewis Date: Tue, 28 Mar 2017 15:16:56 -0400 Subject: [PATCH 16/25] exclude files via pcre --- misc-utils/hardlink.1 | 14 +++++++++++- misc-utils/hardlink.c | 53 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/misc-utils/hardlink.1 b/misc-utils/hardlink.1 index 04228f4bb..b8bfe9d22 100644 --- a/misc-utils/hardlink.1 +++ b/misc-utils/hardlink.1 @@ -3,7 +3,7 @@ hardlink \- Consolidate duplicate files via hardlinks .SH "SYNOPSIS" .PP -\fBhardlink\fP [\fB-c\fP] [\fB-n\fP] [\fB-v\fP] [\fB-vv\fP] [\fB-h\fP] directory1 [ directory2 ... ] +\fBhardlink\fP [\fB-c\fP] [\fB-n\fP] [\fB-v\fP] [\fB-vv\fP] [\fB-x pattern\fP] [\fB-h\fP] directory1 [ directory2 ... ] .SH "DESCRIPTION" .PP This manual page documents \fBhardlink\fP, a @@ -32,8 +32,14 @@ Do not perform the consolidation; only print what would be changed. Print summary after hardlinking. .IP "\fB-vv\fP" 10 Print every hardlinked file and bytes saved. Also print summary after hardlinking. +.IP "\fB-x pattern\fP" 10 +Exclude files and directories matching pattern from hardlinking. .IP "\fB-h\fP" 10 Show help. +.PP +The optional pattern for excluding files and directories must be a PCRE2 +compatible regular expression. Only the basename of the file or directory +is checked, not its path. Excluded directories' contents will not be examined. .SH "AUTHOR" .PP \fBhardlink\fP was written by Jakub Jelinek . @@ -48,3 +54,9 @@ it. If a directory tree does change, this may result in \fBhardlink\fP accessing files and/or directories outside of the intended directory tree. Thus, you must avoid running \fBhardlink\fP on potentially changing directory trees, and especially on directory trees under control of another user. +.PP +Historically \fBhardlink\fP silently excluded any names beginning with +".in.", as well as any names beginning with "." followed by exactly 6 +other characters. That prior behavior can be achieved by specifying +.br +-x '^(\.in\.|\.[^.]{6}$)' diff --git a/misc-utils/hardlink.c b/misc-utils/hardlink.c index 16d8163e2..69f6a464c 100644 --- a/misc-utils/hardlink.c +++ b/misc-utils/hardlink.c @@ -21,6 +21,7 @@ /* Changes by Travers Carter to make atomic hardlinking */ #define _GNU_SOURCE +#define PCRE2_CODE_UNIT_WIDTH 8 #include #include #include @@ -31,12 +32,17 @@ #include #include #include +#include #define NHASH (1<<17) /* Must be a power of 2! */ #define NIOBUF (1<<12) #define NAMELEN 4096 #define NBUF 64 +pcre2_code *re; +PCRE2_SPTR exclude_pattern; +pcre2_match_data *match_data; + struct _f; typedef struct _h { struct _h *next; @@ -99,12 +105,13 @@ void doexit(int i) void usage(char *prog) { - fprintf (stderr, "Usage: %s [-cnvhf] directories...\n", prog); + fprintf (stderr, "Usage: %s [-cnvhf] [-x pat] directories...\n", prog); fprintf (stderr, " -c When finding candidates for linking, compare only file contents.\n"); fprintf (stderr, " -n Don't actually link anything, just report what would be done.\n"); fprintf (stderr, " -v Print summary after hardlinking.\n"); fprintf (stderr, " -vv Print every hardlinked file and bytes saved + summary.\n"); fprintf (stderr, " -f Force hardlinking across filesystems.\n"); + fprintf (stderr, " -x pat Exclude files matching pattern.\n"); fprintf (stderr, " -h Show help.\n"); exit(255); } @@ -328,8 +335,10 @@ int main(int argc, char **argv) { int ch; int i; + int errornumber; + PCRE2_SIZE erroroffset; dynstr nam1 = {NULL, 0}; - while ((ch = getopt (argc, argv, "cnvhf")) != -1) { + while ((ch = getopt (argc, argv, "cnvhfx:")) != -1) { switch (ch) { case 'n': no_link++; @@ -343,6 +352,9 @@ int main(int argc, char **argv) case 'f': force=1; break; + case 'x': + exclude_pattern = (PCRE2_SPTR)optarg; + break; case 'h': default: usage(argv[0]); @@ -350,6 +362,22 @@ int main(int argc, char **argv) } if (optind >= argc) usage(argv[0]); + if (exclude_pattern) { + re = pcre2_compile( + exclude_pattern, /* the pattern */ + PCRE2_ZERO_TERMINATED, /* indicates pattern is zero-terminate */ + 0, /* default options */ + &errornumber, + &erroroffset, + NULL); /* use default compile context */ + if (!re) { + PCRE2_UCHAR buffer[256]; + pcre2_get_error_message(errornumber, buffer, sizeof(buffer)); + fprintf(stderr, "pattern error at offset %d: %s\n", (int)erroroffset, buffer); + usage(argv[0]); + } + match_data = pcre2_match_data_create_from_pattern(re, NULL); + } for (i = optind; i < argc; i++) rf(argv[i]); while (dirs) { @@ -371,16 +399,23 @@ int main(int argc, char **argv) if (!di->d_name[0]) continue; if (di->d_name[0] == '.') { - char *q; - if (!di->d_name[1] || !strcmp (di->d_name, "..") || !strncmp (di->d_name, ".in.", 4)) + if (!di->d_name[1] || !strcmp(di->d_name, "..")) continue; - q = strrchr (di->d_name, '.'); - if (q && strlen (q) == 7 && q != di->d_name) { + } + if (re && pcre2_match( + re, /* compiled regex */ + (PCRE2_SPTR)di->d_name, + strlen(di->d_name), + 0, /* start at offset 0 */ + 0, /* default options */ + match_data, /* block for storing the result */ + NULL) /* use default match context */ + >= 0) { + if (verbose) { nam1.buf[nam1baselen] = 0; - if (verbose) - fprintf(stderr, "Skipping %s%s\n", nam1.buf, di->d_name); - continue; + fprintf(stderr,"Skipping %s%s\n", nam1.buf, di->d_name); } + continue; } { size_t subdirlen; From c64d7e60b19fefbbffbab72ecb89b7b4c652d04c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Javier=20Tsao=20Sant=C3=ADn?= Date: Mon, 24 Apr 2017 00:06:03 +0200 Subject: [PATCH 17/25] Fixed version number, added changelog about Todd Lewis' patch --- misc-utils/hardlink.c | 1 + 1 file changed, 1 insertion(+) diff --git a/misc-utils/hardlink.c b/misc-utils/hardlink.c index 69f6a464c..8e74ca021 100644 --- a/misc-utils/hardlink.c +++ b/misc-utils/hardlink.c @@ -19,6 +19,7 @@ /* Changes by Rémy Card to use constants and add option -n. */ /* Changes by Jindrich Novy to add option -h, -f, replace mmap(2), fix overflows */ /* Changes by Travers Carter to make atomic hardlinking */ +/* Changes by Todd Lewis that adds option -x to exclude files with pcre lib */ #define _GNU_SOURCE #define PCRE2_CODE_UNIT_WIDTH 8 From 88824694f68b3da23954bf3bae5db8e915164111 Mon Sep 17 00:00:00 2001 From: utoddl Date: Mon, 14 Aug 2017 14:46:25 +0000 Subject: [PATCH 18/25] Update hardlink.1 The example regex in the BUGS section needs its backslashes backslashed. --- misc-utils/hardlink.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc-utils/hardlink.1 b/misc-utils/hardlink.1 index b8bfe9d22..5aa022a1f 100644 --- a/misc-utils/hardlink.1 +++ b/misc-utils/hardlink.1 @@ -59,4 +59,4 @@ Historically \fBhardlink\fP silently excluded any names beginning with ".in.", as well as any names beginning with "." followed by exactly 6 other characters. That prior behavior can be achieved by specifying .br --x '^(\.in\.|\.[^.]{6}$)' +-x '^(\\.in\\.|\\.[^.]{6}$)' From 3b9498938911cabd10130adbcda797d3fe8a2763 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Javier=20Tsao=20Sant=C3=ADn?= Date: Tue, 17 Oct 2017 23:21:36 +0200 Subject: [PATCH 19/25] temporal fix before re-patch (updates from Fedora repo) --- misc-utils/hardlink.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc-utils/hardlink.1 b/misc-utils/hardlink.1 index 5aa022a1f..b8bfe9d22 100644 --- a/misc-utils/hardlink.1 +++ b/misc-utils/hardlink.1 @@ -59,4 +59,4 @@ Historically \fBhardlink\fP silently excluded any names beginning with ".in.", as well as any names beginning with "." followed by exactly 6 other characters. That prior behavior can be achieved by specifying .br --x '^(\\.in\\.|\\.[^.]{6}$)' +-x '^(\.in\.|\.[^.]{6}$)' From 5aad3c12f30819ceed1025e9d20e14bd47d5932b Mon Sep 17 00:00:00 2001 From: Kevin Fenzi Date: Sun, 17 Sep 2017 14:07:42 -0700 Subject: [PATCH 20/25] fixes for the fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Francisco Javier Tsao Santín --- misc-utils/hardlink.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc-utils/hardlink.1 b/misc-utils/hardlink.1 index b8bfe9d22..5aa022a1f 100644 --- a/misc-utils/hardlink.1 +++ b/misc-utils/hardlink.1 @@ -59,4 +59,4 @@ Historically \fBhardlink\fP silently excluded any names beginning with ".in.", as well as any names beginning with "." followed by exactly 6 other characters. That prior behavior can be achieved by specifying .br --x '^(\.in\.|\.[^.]{6}$)' +-x '^(\\.in\\.|\\.[^.]{6}$)' From 04ae85a7e57691983c25f0917cfb8ae4dcb462d5 Mon Sep 17 00:00:00 2001 From: Ruediger Meier Date: Tue, 12 Jun 2018 14:04:06 +0200 Subject: [PATCH 21/25] hardlink: enable build with and without pcre2 Signed-off-by: Ruediger Meier --- .gitignore | 1 + configure.ac | 12 ++++++++++++ misc-utils/Makemodule.am | 12 ++++++++++++ misc-utils/hardlink.c | 19 +++++++++++++++++-- 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 9f2fb17db..b582a8627 100644 --- a/.gitignore +++ b/.gitignore @@ -96,6 +96,7 @@ ylwrap /fsfreeze /fstrim /getopt +/hardlink /hexdump /hwclock /ionice diff --git a/configure.ac b/configure.ac index b3430dac4..260283cf2 100644 --- a/configure.ac +++ b/configure.ac @@ -1372,6 +1372,18 @@ UL_REQUIRES_HAVE([setpriv], [linux_securebits_h], [securebits.h header file]) UL_REQUIRES_HAVE([setpriv], [cap_ng], [libcap-ng library]) AM_CONDITIONAL([BUILD_SETPRIV], [test "x$build_setpriv" = xyes]) +PKG_CHECK_MODULES([PCRE], [libpcre2-8], [have_pcre=yes], [have_pcre=no]) +AS_IF([test "x$have_pcre" = xyes ], [ + AC_DEFINE([HAVE_PCRE], [1], [Define if libpcre2 is available]) +]) +AM_CONDITIONAL([HAVE_PCRE], [test "x$have_pcre" = xyes]) + +AC_ARG_ENABLE([hardlink], + AS_HELP_STRING([--disable-hardlink], [do not build hardlink]), + [], [UL_DEFAULT_ENABLE([hardlink], [check])] +) +UL_BUILD_INIT([hardlink]) +AM_CONDITIONAL([BUILD_HARDLINK], [test "x$build_hardlink" = xyes]) AC_ARG_ENABLE([eject], AS_HELP_STRING([--disable-eject], [do not build eject]), diff --git a/misc-utils/Makemodule.am b/misc-utils/Makemodule.am index 36195b7a3..30b7c2f0f 100644 --- a/misc-utils/Makemodule.am +++ b/misc-utils/Makemodule.am @@ -211,3 +211,15 @@ fincore_SOURCES = misc-utils/fincore.c fincore_LDADD = $(LDADD) libsmartcols.la libcommon.la fincore_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir) endif + +if BUILD_HARDLINK +usrbin_exec_PROGRAMS += hardlink +hardlink_SOURCES = misc-utils/hardlink.c +hardlink_LDADD = $(LDADD) libcommon.la +hardlink_CFLAGS = $(AM_CFLAGS) +if HAVE_PCRE +hardlink_LDADD += $(PCRE_LIBS) +hardlink_CFLAGS += $(PCRE_CFLAGS) +endif +dist_man_MANS += misc-utils/hardlink.1 +endif diff --git a/misc-utils/hardlink.c b/misc-utils/hardlink.c index 8e74ca021..ba519993a 100644 --- a/misc-utils/hardlink.c +++ b/misc-utils/hardlink.c @@ -22,7 +22,6 @@ /* Changes by Todd Lewis that adds option -x to exclude files with pcre lib */ #define _GNU_SOURCE -#define PCRE2_CODE_UNIT_WIDTH 8 #include #include #include @@ -33,16 +32,21 @@ #include #include #include -#include +#ifdef HAVE_PCRE +# define PCRE2_CODE_UNIT_WIDTH 8 +# include +#endif #define NHASH (1<<17) /* Must be a power of 2! */ #define NIOBUF (1<<12) #define NAMELEN 4096 #define NBUF 64 +#ifdef HAVE_PCRE pcre2_code *re; PCRE2_SPTR exclude_pattern; pcre2_match_data *match_data; +#endif struct _f; typedef struct _h { @@ -336,8 +340,10 @@ int main(int argc, char **argv) { int ch; int i; +#ifdef HAVE_PCRE int errornumber; PCRE2_SIZE erroroffset; +#endif dynstr nam1 = {NULL, 0}; while ((ch = getopt (argc, argv, "cnvhfx:")) != -1) { switch (ch) { @@ -354,7 +360,12 @@ int main(int argc, char **argv) force=1; break; case 'x': +#ifdef HAVE_PCRE exclude_pattern = (PCRE2_SPTR)optarg; +#else + fprintf(stderr, "option x not supported (built without pcre2)\n"); + exit(1); +#endif break; case 'h': default: @@ -363,6 +374,7 @@ int main(int argc, char **argv) } if (optind >= argc) usage(argv[0]); +#ifdef HAVE_PCRE if (exclude_pattern) { re = pcre2_compile( exclude_pattern, /* the pattern */ @@ -379,6 +391,7 @@ int main(int argc, char **argv) } match_data = pcre2_match_data_create_from_pattern(re, NULL); } +#endif for (i = optind; i < argc; i++) rf(argv[i]); while (dirs) { @@ -403,6 +416,7 @@ int main(int argc, char **argv) if (!di->d_name[1] || !strcmp(di->d_name, "..")) continue; } +#ifdef HAVE_PCRE if (re && pcre2_match( re, /* compiled regex */ (PCRE2_SPTR)di->d_name, @@ -418,6 +432,7 @@ int main(int argc, char **argv) } continue; } +#endif { size_t subdirlen; growstr(&nam1, add2(nam1baselen, subdirlen = strlen(di->d_name))); From 55c000e1ebb95ebb222206da692ceb77d0ba8409 Mon Sep 17 00:00:00 2001 From: Ruediger Meier Date: Tue, 12 Jun 2018 19:32:33 +0200 Subject: [PATCH 22/25] hardlink: style indentations and license header Signed-off-by: Ruediger Meier --- misc-utils/hardlink.c | 769 +++++++++++++++++++++++------------------- 1 file changed, 413 insertions(+), 356 deletions(-) diff --git a/misc-utils/hardlink.c b/misc-utils/hardlink.c index ba519993a..335c6c452 100644 --- a/misc-utils/hardlink.c +++ b/misc-utils/hardlink.c @@ -1,25 +1,23 @@ -/* Copyright (C) 2001 Red Hat, Inc. - - Written by Jakub Jelinek . - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software Foundation, - Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ - -/* Changes by Rémy Card to use constants and add option -n. */ -/* Changes by Jindrich Novy to add option -h, -f, replace mmap(2), fix overflows */ -/* Changes by Travers Carter to make atomic hardlinking */ -/* Changes by Todd Lewis that adds option -x to exclude files with pcre lib */ +/* + * hardlink - consolidate duplicate files via hardlinks + * + * Copyright (C) 2018 Red Hat, Inc. All rights reserved. + * Written by Jakub Jelinek + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it would be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ #define _GNU_SOURCE #include @@ -37,10 +35,10 @@ # include #endif -#define NHASH (1<<17) /* Must be a power of 2! */ -#define NIOBUF (1<<12) -#define NAMELEN 4096 -#define NBUF 64 +#define NHASH (1<<17) /* Must be a power of 2! */ +#define NIOBUF (1<<12) +#define NAMELEN 4096 +#define NBUF 64 #ifdef HAVE_PCRE pcre2_code *re; @@ -50,15 +48,15 @@ pcre2_match_data *match_data; struct _f; typedef struct _h { - struct _h *next; - struct _f *chain; - off_t size; - time_t mtime; + struct _h *next; + struct _f *chain; + off_t size; + time_t mtime; } h; typedef struct _d { - struct _d *next; - char name[0]; + struct _d *next; + char name[0]; } d; d *dirs; @@ -71,377 +69,436 @@ int content_only = 0; int force = 0; typedef struct _f { - struct _f *next; - ino_t ino; - dev_t dev; - unsigned int cksum; - char name[0]; + struct _f *next; + ino_t ino; + dev_t dev; + unsigned int cksum; + char name[0]; } f; -__attribute__((always_inline)) inline unsigned int hash(off_t size, time_t mtime) +__attribute__ ((always_inline)) +inline unsigned int hash(off_t size, time_t mtime) { - return (size ^ mtime) & (NHASH - 1); + return (size ^ mtime) & (NHASH - 1); } -__attribute__((always_inline)) inline int stcmp(struct stat *st1, struct stat *st2, int content_only) +__attribute__ ((always_inline)) +inline int stcmp(struct stat *st1, struct stat *st2, int content_only) { - if (content_only) - return st1->st_size != st2->st_size; - return st1->st_mode != st2->st_mode || st1->st_uid != st2->st_uid || - st1->st_gid != st2->st_gid || st1->st_size != st2->st_size || - st1->st_mtime != st2->st_mtime; + if (content_only) + return st1->st_size != st2->st_size; + return st1->st_mode != st2->st_mode || st1->st_uid != st2->st_uid || + st1->st_gid != st2->st_gid || st1->st_size != st2->st_size || + st1->st_mtime != st2->st_mtime; } long long ndirs, nobjects, nregfiles, ncomp, nlinks, nsaved; void doexit(int i) { - if (verbose) { - fprintf(stderr, "\n\n"); - fprintf(stderr, "Directories %lld\n", ndirs); - fprintf(stderr, "Objects %lld\n", nobjects); - fprintf(stderr, "IFREG %lld\n", nregfiles); - fprintf(stderr, "Comparisons %lld\n", ncomp); - fprintf(stderr, "%s %lld\n", (no_link ? "Would link" : "Linked"), nlinks); - fprintf(stderr, "%s %lld\n", (no_link ? "Would save" : "saved"), nsaved); - } - exit(i); + if (verbose) { + fprintf(stderr, "\n\n"); + fprintf(stderr, "Directories %lld\n", ndirs); + fprintf(stderr, "Objects %lld\n", nobjects); + fprintf(stderr, "IFREG %lld\n", nregfiles); + fprintf(stderr, "Comparisons %lld\n", ncomp); + fprintf(stderr, "%s %lld\n", + (no_link ? "Would link" : "Linked"), nlinks); + fprintf(stderr, "%s %lld\n", (no_link ? "Would save" : "saved"), + nsaved); + } + exit(i); } void usage(char *prog) { - fprintf (stderr, "Usage: %s [-cnvhf] [-x pat] directories...\n", prog); - fprintf (stderr, " -c When finding candidates for linking, compare only file contents.\n"); - fprintf (stderr, " -n Don't actually link anything, just report what would be done.\n"); - fprintf (stderr, " -v Print summary after hardlinking.\n"); - fprintf (stderr, " -vv Print every hardlinked file and bytes saved + summary.\n"); - fprintf (stderr, " -f Force hardlinking across filesystems.\n"); - fprintf (stderr, " -x pat Exclude files matching pattern.\n"); - fprintf (stderr, " -h Show help.\n"); - exit(255); + fprintf(stderr, "Usage: %s [-cnvhf] [-x pat] directories...\n", prog); + fprintf(stderr, + " -c When finding candidates for linking, compare only file contents.\n"); + fprintf(stderr, + " -n Don't actually link anything, just report what would be done.\n"); + fprintf(stderr, " -v Print summary after hardlinking.\n"); + fprintf(stderr, + " -vv Print every hardlinked file and bytes saved + summary.\n"); + fprintf(stderr, " -f Force hardlinking across filesystems.\n"); + fprintf(stderr, " -x pat Exclude files matching pattern.\n"); + fprintf(stderr, " -h Show help.\n"); + exit(255); } unsigned int buf[NBUF]; char iobuf1[NIOBUF], iobuf2[NIOBUF]; -__attribute__((always_inline)) inline size_t add2(size_t a, size_t b) +__attribute__ ((always_inline)) +inline size_t add2(size_t a, size_t b) { - size_t sum = a + b; - if (sum < a) { - fprintf(stderr, "\nInteger overflow\n"); - doexit(5); - } - return sum; + size_t sum = a + b; + if (sum < a) { + fprintf(stderr, "\nInteger overflow\n"); + doexit(5); + } + return sum; } -__attribute__((always_inline)) inline size_t add3(size_t a, size_t b, size_t c) +__attribute__ ((always_inline)) +inline size_t add3(size_t a, size_t b, size_t c) { - return add2(add2(a, b), c); + return add2(add2(a, b), c); } typedef struct { - char *buf; - size_t alloc; + char *buf; + size_t alloc; } dynstr; -void growstr(dynstr *str, size_t newlen) +void growstr(dynstr * str, size_t newlen) { - if (newlen < str->alloc) - return; - str->buf = realloc(str->buf, str->alloc = add2(newlen, 1)); - if (!str->buf) { - fprintf(stderr, "\nOut of memory 4\n"); - doexit(4); - } + if (newlen < str->alloc) + return; + str->buf = realloc(str->buf, str->alloc = add2(newlen, 1)); + if (!str->buf) { + fprintf(stderr, "\nOut of memory 4\n"); + doexit(4); + } } + dev_t dev = 0; -void rf (const char *name) +void rf(const char *name) { - struct stat st, st2, st3; - const size_t namelen = strlen(name); - nobjects++; - if (lstat (name, &st)) - return; - if (st.st_dev != dev && !force) { - if (dev) { - fprintf(stderr, "%s is on different filesystem than the rest.\nUse -f option to override.\n", name); - doexit(6); - } - dev = st.st_dev; - } - if (S_ISDIR (st.st_mode)) { - d * dp = malloc(add3(sizeof(d), namelen, 1)); - if (!dp) { - fprintf(stderr, "\nOut of memory 3\n"); - doexit(3); - } - memcpy(dp->name, name, namelen + 1); - dp->next = dirs; - dirs = dp; - } else if (S_ISREG (st.st_mode)) { - int fd, i; - f * fp, * fp2; - h * hp; - const char *n1, *n2; - int cksumsize = sizeof(buf); - unsigned int cksum; - time_t mtime = content_only ? 0 : st.st_mtime; - unsigned int hsh = hash (st.st_size, mtime); - off_t fsize; - nregfiles++; - if (verbose > 1) - fprintf(stderr, " %s", name); - fd = open (name, O_RDONLY); - if (fd < 0) return; - if (st.st_size < sizeof(buf)) { - cksumsize = st.st_size; - memset (((char *)buf) + cksumsize, 0, (sizeof(buf) - cksumsize) % sizeof(buf[0])); - } - if (read (fd, buf, cksumsize) != cksumsize) { - close(fd); - if (verbose > 1 && namelen <= NAMELEN) - fprintf(stderr, "\r%*s\r", (int)(namelen + 2), ""); - return; - } - cksumsize = (cksumsize + sizeof(buf[0]) - 1) / sizeof(buf[0]); - for (i = 0, cksum = 0; i < cksumsize; i++) { - if (cksum + buf[i] < cksum) - cksum += buf[i] + 1; - else - cksum += buf[i]; - } - for (hp = hps[hsh]; hp; hp = hp->next) - if (hp->size == st.st_size && hp->mtime == mtime) - break; - if (!hp) { - hp = malloc(sizeof(h)); - if (!hp) { - fprintf(stderr, "\nOut of memory 1\n"); - doexit(1); - } - hp->size = st.st_size; - hp->mtime = mtime; - hp->chain = NULL; - hp->next = hps[hsh]; - hps[hsh] = hp; - } - for (fp = hp->chain; fp; fp = fp->next) - if (fp->cksum == cksum) - break; - for (fp2 = fp; fp2 && fp2->cksum == cksum; fp2 = fp2->next) - if (fp2->ino == st.st_ino && fp2->dev == st.st_dev) { - close(fd); - if (verbose > 1 && namelen <= NAMELEN) - fprintf(stderr, "\r%*s\r", (int)(namelen + 2), ""); - return; - } - for (fp2 = fp; fp2 && fp2->cksum == cksum; fp2 = fp2->next) - if (!lstat (fp2->name, &st2) && S_ISREG (st2.st_mode) && - !stcmp (&st, &st2, content_only) && - st2.st_ino != st.st_ino && - st2.st_dev == st.st_dev) { - int fd2 = open (fp2->name, O_RDONLY); - if (fd2 < 0) continue; - if (fstat (fd2, &st2) || !S_ISREG (st2.st_mode) || st2.st_size == 0) { - close (fd2); - continue; - } - ncomp++; - lseek(fd, 0, SEEK_SET); - for (fsize = st.st_size; fsize > 0; fsize -= NIOBUF) { - off_t rsize = fsize >= NIOBUF ? NIOBUF : fsize; - if (read (fd, iobuf1, rsize) != rsize || read (fd2, iobuf2, rsize) != rsize) { - close(fd); - close(fd2); - fprintf(stderr, "\nReading error\n"); - return; - } - if (memcmp (iobuf1, iobuf2, rsize)) break; + struct stat st, st2, st3; + const size_t namelen = strlen(name); + nobjects++; + if (lstat(name, &st)) + return; + if (st.st_dev != dev && !force) { + if (dev) { + fprintf(stderr, + "%s is on different filesystem than the rest.\nUse -f option to override.\n", + name); + doexit(6); + } + dev = st.st_dev; } - close(fd2); - if (fsize > 0) continue; - if (lstat (name, &st3)) { - fprintf(stderr, "\nCould not stat %s again\n", name); - close(fd); - return; - } - st3.st_atime = st.st_atime; - if (stcmp (&st, &st3, 0)) { - fprintf(stderr, "\nFile %s changed underneath us\n", name); - close(fd); - return; - } - n1 = fp2->name; - n2 = name; - if (!no_link) { - const char *suffix = ".$$$___cleanit___$$$"; - const size_t suffixlen = strlen(suffix); - size_t n2len = strlen(n2); - dynstr nam2 = {NULL, 0}; - growstr(&nam2, add2(n2len, suffixlen)); - memcpy(nam2.buf, n2, n2len); - memcpy(&nam2.buf[n2len], suffix, suffixlen + 1); - /* First create a temporary link to n1 under a new name */ - if (link(n1, nam2.buf)) { - fprintf(stderr, "\nFailed to hardlink %s to %s (create temporary link as %s failed - %s)\n", n1, n2, nam2.buf, strerror(errno)); - free(nam2.buf); - continue; - } - /* Then rename into place over the existing n2 */ - if (rename (nam2.buf, n2)) { - fprintf(stderr, "\nFailed to hardlink %s to %s (rename temporary link to %s failed - %s)\n", n1, n2, n2, strerror(errno)); - /* Something went wrong, try to remove the now redundant temporary link */ - if (unlink(nam2.buf)) { - fprintf(stderr, "\nFailed to remove temporary link %s - %s\n", nam2.buf, strerror(errno)); - } - free(nam2.buf); - continue; - } - free(nam2.buf); - } - nlinks++; - if (st3.st_nlink > 1) { - /* We actually did not save anything this time, since the link second argument - had some other links as well. */ - if (verbose > 1) - fprintf(stderr, "\r%*s\r%s %s to %s\n", (int)(((namelen > NAMELEN) ? 0 : namelen) + 2), "", (no_link ? "Would link" : "Linked"), n1, n2); - } else { - nsaved+=((st.st_size+4095)/4096)*4096; - if (verbose > 1) - fprintf(stderr, "\r%*s\r%s %s to %s, %s %ld\n", (int)(((namelen > NAMELEN) ? 0 : namelen) + 2), "", (no_link ? "Would link" : "Linked"), n1, n2, (no_link ? "would save" : "saved"), st.st_size); + if (S_ISDIR(st.st_mode)) { + d *dp = malloc(add3(sizeof(d), namelen, 1)); + if (!dp) { + fprintf(stderr, "\nOut of memory 3\n"); + doexit(3); + } + memcpy(dp->name, name, namelen + 1); + dp->next = dirs; + dirs = dp; + } else if (S_ISREG(st.st_mode)) { + int fd, i; + f *fp, *fp2; + h *hp; + const char *n1, *n2; + int cksumsize = sizeof(buf); + unsigned int cksum; + time_t mtime = content_only ? 0 : st.st_mtime; + unsigned int hsh = hash(st.st_size, mtime); + off_t fsize; + nregfiles++; + if (verbose > 1) + fprintf(stderr, " %s", name); + fd = open(name, O_RDONLY); + if (fd < 0) + return; + if (st.st_size < sizeof(buf)) { + cksumsize = st.st_size; + memset(((char *)buf) + cksumsize, 0, + (sizeof(buf) - cksumsize) % sizeof(buf[0])); + } + if (read(fd, buf, cksumsize) != cksumsize) { + close(fd); + if (verbose > 1 && namelen <= NAMELEN) + fprintf(stderr, "\r%*s\r", (int)(namelen + 2), + ""); + return; + } + cksumsize = (cksumsize + sizeof(buf[0]) - 1) / sizeof(buf[0]); + for (i = 0, cksum = 0; i < cksumsize; i++) { + if (cksum + buf[i] < cksum) + cksum += buf[i] + 1; + else + cksum += buf[i]; + } + for (hp = hps[hsh]; hp; hp = hp->next) + if (hp->size == st.st_size && hp->mtime == mtime) + break; + if (!hp) { + hp = malloc(sizeof(h)); + if (!hp) { + fprintf(stderr, "\nOut of memory 1\n"); + doexit(1); + } + hp->size = st.st_size; + hp->mtime = mtime; + hp->chain = NULL; + hp->next = hps[hsh]; + hps[hsh] = hp; + } + for (fp = hp->chain; fp; fp = fp->next) + if (fp->cksum == cksum) + break; + for (fp2 = fp; fp2 && fp2->cksum == cksum; fp2 = fp2->next) + if (fp2->ino == st.st_ino && fp2->dev == st.st_dev) { + close(fd); + if (verbose > 1 && namelen <= NAMELEN) + fprintf(stderr, "\r%*s\r", + (int)(namelen + 2), ""); + return; + } + for (fp2 = fp; fp2 && fp2->cksum == cksum; fp2 = fp2->next) + if (!lstat(fp2->name, &st2) && S_ISREG(st2.st_mode) && + !stcmp(&st, &st2, content_only) && + st2.st_ino != st.st_ino && + st2.st_dev == st.st_dev) { + int fd2 = open(fp2->name, O_RDONLY); + if (fd2 < 0) + continue; + if (fstat(fd2, &st2) || !S_ISREG(st2.st_mode) + || st2.st_size == 0) { + close(fd2); + continue; + } + ncomp++; + lseek(fd, 0, SEEK_SET); + for (fsize = st.st_size; fsize > 0; + fsize -= NIOBUF) { + off_t rsize = + fsize >= NIOBUF ? NIOBUF : fsize; + if (read(fd, iobuf1, rsize) != rsize + || read(fd2, iobuf2, + rsize) != rsize) { + close(fd); + close(fd2); + fprintf(stderr, + "\nReading error\n"); + return; + } + if (memcmp(iobuf1, iobuf2, rsize)) + break; + } + close(fd2); + if (fsize > 0) + continue; + if (lstat(name, &st3)) { + fprintf(stderr, + "\nCould not stat %s again\n", + name); + close(fd); + return; + } + st3.st_atime = st.st_atime; + if (stcmp(&st, &st3, 0)) { + fprintf(stderr, + "\nFile %s changed underneath us\n", + name); + close(fd); + return; + } + n1 = fp2->name; + n2 = name; + if (!no_link) { + const char *suffix = + ".$$$___cleanit___$$$"; + const size_t suffixlen = strlen(suffix); + size_t n2len = strlen(n2); + dynstr nam2 = { NULL, 0 }; + growstr(&nam2, add2(n2len, suffixlen)); + memcpy(nam2.buf, n2, n2len); + memcpy(&nam2.buf[n2len], suffix, + suffixlen + 1); + /* First create a temporary link to n1 under a new name */ + if (link(n1, nam2.buf)) { + fprintf(stderr, + "\nFailed to hardlink %s to %s (create temporary link as %s failed - %s)\n", + n1, n2, nam2.buf, + strerror(errno)); + free(nam2.buf); + continue; + } + /* Then rename into place over the existing n2 */ + if (rename(nam2.buf, n2)) { + fprintf(stderr, + "\nFailed to hardlink %s to %s (rename temporary link to %s failed - %s)\n", + n1, n2, n2, + strerror(errno)); + /* Something went wrong, try to remove the now redundant temporary link */ + if (unlink(nam2.buf)) { + fprintf(stderr, + "\nFailed to remove temporary link %s - %s\n", + nam2.buf, + strerror + (errno)); + } + free(nam2.buf); + continue; + } + free(nam2.buf); + } + nlinks++; + if (st3.st_nlink > 1) { + /* We actually did not save anything this time, since the link second argument + had some other links as well. */ + if (verbose > 1) + fprintf(stderr, + "\r%*s\r%s %s to %s\n", + (int)(((namelen > + NAMELEN) ? 0 : + namelen) + 2), + "", + (no_link ? "Would link" + : "Linked"), n1, n2); + } else { + nsaved += + ((st.st_size + 4095) / 4096) * 4096; + if (verbose > 1) + fprintf(stderr, + "\r%*s\r%s %s to %s, %s %ld\n", + (int)(((namelen > + NAMELEN) ? 0 : + namelen) + 2), + "", + (no_link ? "Would link" + : "Linked"), n1, n2, + (no_link ? "would save" + : "saved"), + st.st_size); + } + close(fd); + return; + } + fp2 = malloc(add3(sizeof(f), namelen, 1)); + if (!fp2) { + fprintf(stderr, "\nOut of memory 2\n"); + doexit(2); + } + close(fd); + fp2->ino = st.st_ino; + fp2->dev = st.st_dev; + fp2->cksum = cksum; + memcpy(fp2->name, name, namelen + 1); + if (fp) { + fp2->next = fp->next; + fp->next = fp2; + } else { + fp2->next = hp->chain; + hp->chain = fp2; + } + if (verbose > 1 && namelen <= NAMELEN) + fprintf(stderr, "\r%*s\r", (int)(namelen + 2), ""); + return; } - close(fd); - return; - } - fp2 = malloc(add3(sizeof(f), namelen, 1)); - if (!fp2) { - fprintf(stderr, "\nOut of memory 2\n"); - doexit(2); - } - close(fd); - fp2->ino = st.st_ino; - fp2->dev = st.st_dev; - fp2->cksum = cksum; - memcpy(fp2->name, name, namelen + 1); - if (fp) { - fp2->next = fp->next; - fp->next = fp2; - } else { - fp2->next = hp->chain; - hp->chain = fp2; - } - if (verbose > 1 && namelen <= NAMELEN) - fprintf(stderr, "\r%*s\r", (int)(namelen + 2), ""); - return; - } } int main(int argc, char **argv) { - int ch; - int i; + int ch; + int i; #ifdef HAVE_PCRE - int errornumber; - PCRE2_SIZE erroroffset; + int errornumber; + PCRE2_SIZE erroroffset; #endif - dynstr nam1 = {NULL, 0}; - while ((ch = getopt (argc, argv, "cnvhfx:")) != -1) { - switch (ch) { - case 'n': - no_link++; - break; - case 'v': - verbose++; - break; - case 'c': - content_only++; - break; - case 'f': - force=1; - break; - case 'x': + dynstr nam1 = { NULL, 0 }; + while ((ch = getopt(argc, argv, "cnvhfx:")) != -1) { + switch (ch) { + case 'n': + no_link++; + break; + case 'v': + verbose++; + break; + case 'c': + content_only++; + break; + case 'f': + force = 1; + break; + case 'x': #ifdef HAVE_PCRE - exclude_pattern = (PCRE2_SPTR)optarg; + exclude_pattern = (PCRE2_SPTR) optarg; #else - fprintf(stderr, "option x not supported (built without pcre2)\n"); - exit(1); + fprintf(stderr, "option x not supported (built without pcre2)\n"); + exit(1); #endif - break; - case 'h': - default: - usage(argv[0]); - } - } - if (optind >= argc) - usage(argv[0]); + break; + case 'h': + default: + usage(argv[0]); + } + } + if (optind >= argc) + usage(argv[0]); #ifdef HAVE_PCRE - if (exclude_pattern) { - re = pcre2_compile( - exclude_pattern, /* the pattern */ - PCRE2_ZERO_TERMINATED, /* indicates pattern is zero-terminate */ - 0, /* default options */ - &errornumber, - &erroroffset, - NULL); /* use default compile context */ - if (!re) { - PCRE2_UCHAR buffer[256]; - pcre2_get_error_message(errornumber, buffer, sizeof(buffer)); - fprintf(stderr, "pattern error at offset %d: %s\n", (int)erroroffset, buffer); - usage(argv[0]); - } - match_data = pcre2_match_data_create_from_pattern(re, NULL); - } + if (exclude_pattern) { + re = pcre2_compile(exclude_pattern, /* the pattern */ + PCRE2_ZERO_TERMINATED, /* indicates pattern is zero-terminate */ + 0, /* default options */ + &errornumber, &erroroffset, NULL); /* use default compile context */ + if (!re) { + PCRE2_UCHAR buffer[256]; + pcre2_get_error_message(errornumber, buffer, + sizeof(buffer)); + fprintf(stderr, "pattern error at offset %d: %s\n", + (int)erroroffset, buffer); + usage(argv[0]); + } + match_data = pcre2_match_data_create_from_pattern(re, NULL); + } #endif - for (i = optind; i < argc; i++) - rf(argv[i]); - while (dirs) { - DIR *dh; - struct dirent *di; - d * dp = dirs; - size_t nam1baselen = strlen(dp->name); - dirs = dp->next; - growstr(&nam1, add2(nam1baselen, 1)); - memcpy(nam1.buf, dp->name, nam1baselen); - free (dp); - nam1.buf[nam1baselen++] = '/'; - nam1.buf[nam1baselen] = 0; - dh = opendir (nam1.buf); - if (dh == NULL) - continue; - ndirs++; - while ((di = readdir (dh)) != NULL) { - if (!di->d_name[0]) - continue; - if (di->d_name[0] == '.') { - if (!di->d_name[1] || !strcmp(di->d_name, "..")) - continue; - } + for (i = optind; i < argc; i++) + rf(argv[i]); + while (dirs) { + DIR *dh; + struct dirent *di; + d *dp = dirs; + size_t nam1baselen = strlen(dp->name); + dirs = dp->next; + growstr(&nam1, add2(nam1baselen, 1)); + memcpy(nam1.buf, dp->name, nam1baselen); + free(dp); + nam1.buf[nam1baselen++] = '/'; + nam1.buf[nam1baselen] = 0; + dh = opendir(nam1.buf); + if (dh == NULL) + continue; + ndirs++; + while ((di = readdir(dh)) != NULL) { + if (!di->d_name[0]) + continue; + if (di->d_name[0] == '.') { + if (!di->d_name[1] || !strcmp(di->d_name, "..")) + continue; + } #ifdef HAVE_PCRE - if (re && pcre2_match( - re, /* compiled regex */ - (PCRE2_SPTR)di->d_name, - strlen(di->d_name), - 0, /* start at offset 0 */ - 0, /* default options */ - match_data, /* block for storing the result */ - NULL) /* use default match context */ - >= 0) { - if (verbose) { - nam1.buf[nam1baselen] = 0; - fprintf(stderr,"Skipping %s%s\n", nam1.buf, di->d_name); - } - continue; - } + if (re && pcre2_match(re, /* compiled regex */ + (PCRE2_SPTR) di->d_name, strlen(di->d_name), 0, /* start at offset 0 */ + 0, /* default options */ + match_data, /* block for storing the result */ + NULL) /* use default match context */ + >=0) { + if (verbose) { + nam1.buf[nam1baselen] = 0; + fprintf(stderr, "Skipping %s%s\n", + nam1.buf, di->d_name); + } + continue; + } #endif - { - size_t subdirlen; - growstr(&nam1, add2(nam1baselen, subdirlen = strlen(di->d_name))); - memcpy(&nam1.buf[nam1baselen], di->d_name, add2(subdirlen, 1)); - } - rf(nam1.buf); - } - closedir(dh); - } - doexit(0); - return 0; + { + size_t subdirlen; + growstr(&nam1, + add2(nam1baselen, subdirlen = + strlen(di->d_name))); + memcpy(&nam1.buf[nam1baselen], di->d_name, + add2(subdirlen, 1)); + } + rf(nam1.buf); + } + closedir(dh); + } + doexit(0); + return 0; } From 53071734aa19a7d1ee4cf71590e3521aa89aaa54 Mon Sep 17 00:00:00 2001 From: Ruediger Meier Date: Tue, 12 Jun 2018 20:19:21 +0200 Subject: [PATCH 23/25] hardlink: fix compiler warnings Signed-off-by: Ruediger Meier --- misc-utils/hardlink.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/misc-utils/hardlink.c b/misc-utils/hardlink.c index 335c6c452..0bd33db38 100644 --- a/misc-utils/hardlink.c +++ b/misc-utils/hardlink.c @@ -19,7 +19,6 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#define _GNU_SOURCE #include #include #include @@ -77,13 +76,13 @@ typedef struct _f { } f; __attribute__ ((always_inline)) -inline unsigned int hash(off_t size, time_t mtime) +static inline unsigned int hash(off_t size, time_t mtime) { return (size ^ mtime) & (NHASH - 1); } __attribute__ ((always_inline)) -inline int stcmp(struct stat *st1, struct stat *st2, int content_only) +static inline int stcmp(struct stat *st1, struct stat *st2, int content_only) { if (content_only) return st1->st_size != st2->st_size; @@ -94,7 +93,7 @@ inline int stcmp(struct stat *st1, struct stat *st2, int content_only) long long ndirs, nobjects, nregfiles, ncomp, nlinks, nsaved; -void doexit(int i) +static void doexit(int i) { if (verbose) { fprintf(stderr, "\n\n"); @@ -110,7 +109,7 @@ void doexit(int i) exit(i); } -void usage(char *prog) +static void usage(char *prog) { fprintf(stderr, "Usage: %s [-cnvhf] [-x pat] directories...\n", prog); fprintf(stderr, @@ -130,7 +129,7 @@ unsigned int buf[NBUF]; char iobuf1[NIOBUF], iobuf2[NIOBUF]; __attribute__ ((always_inline)) -inline size_t add2(size_t a, size_t b) +static inline size_t add2(size_t a, size_t b) { size_t sum = a + b; if (sum < a) { @@ -141,7 +140,7 @@ inline size_t add2(size_t a, size_t b) } __attribute__ ((always_inline)) -inline size_t add3(size_t a, size_t b, size_t c) +static inline size_t add3(size_t a, size_t b, size_t c) { return add2(add2(a, b), c); } @@ -151,7 +150,7 @@ typedef struct { size_t alloc; } dynstr; -void growstr(dynstr * str, size_t newlen) +static void growstr(dynstr * str, size_t newlen) { if (newlen < str->alloc) return; @@ -163,7 +162,7 @@ void growstr(dynstr * str, size_t newlen) } dev_t dev = 0; -void rf(const char *name) +static void rf(const char *name) { struct stat st, st2, st3; const size_t namelen = strlen(name); @@ -204,7 +203,7 @@ void rf(const char *name) fd = open(name, O_RDONLY); if (fd < 0) return; - if (st.st_size < sizeof(buf)) { + if ((size_t)st.st_size < sizeof(buf)) { cksumsize = st.st_size; memset(((char *)buf) + cksumsize, 0, (sizeof(buf) - cksumsize) % sizeof(buf[0])); @@ -356,7 +355,7 @@ void rf(const char *name) ((st.st_size + 4095) / 4096) * 4096; if (verbose > 1) fprintf(stderr, - "\r%*s\r%s %s to %s, %s %ld\n", + "\r%*s\r%s %s to %s, %s %jd\n", (int)(((namelen > NAMELEN) ? 0 : namelen) + 2), @@ -365,7 +364,7 @@ void rf(const char *name) : "Linked"), n1, n2, (no_link ? "would save" : "saved"), - st.st_size); + (intmax_t)st.st_size); } close(fd); return; From 7d50d3612163833f0863f4c007159bc8edf65663 Mon Sep 17 00:00:00 2001 From: Ruediger Meier Date: Thu, 14 Jun 2018 01:13:56 +0200 Subject: [PATCH 24/25] hardlink: util-linux usage Signed-off-by: Ruediger Meier --- misc-utils/hardlink.c | 71 +++++++++++++++++++++++++++++++------------ 1 file changed, 51 insertions(+), 20 deletions(-) diff --git a/misc-utils/hardlink.c b/misc-utils/hardlink.c index 0bd33db38..56edaa686 100644 --- a/misc-utils/hardlink.c +++ b/misc-utils/hardlink.c @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -34,6 +35,10 @@ # include #endif +#include "c.h" +#include "nls.h" +#include "closestream.h" + #define NHASH (1<<17) /* Must be a power of 2! */ #define NIOBUF (1<<12) #define NAMELEN 4096 @@ -109,22 +114,28 @@ static void doexit(int i) exit(i); } -static void usage(char *prog) +static void __attribute__((__noreturn__)) usage(void) { - fprintf(stderr, "Usage: %s [-cnvhf] [-x pat] directories...\n", prog); - fprintf(stderr, - " -c When finding candidates for linking, compare only file contents.\n"); - fprintf(stderr, - " -n Don't actually link anything, just report what would be done.\n"); - fprintf(stderr, " -v Print summary after hardlinking.\n"); - fprintf(stderr, - " -vv Print every hardlinked file and bytes saved + summary.\n"); - fprintf(stderr, " -f Force hardlinking across filesystems.\n"); - fprintf(stderr, " -x pat Exclude files matching pattern.\n"); - fprintf(stderr, " -h Show help.\n"); - exit(255); + fputs(USAGE_HEADER, stdout); + printf(_(" %s [options] directory...\n"), program_invocation_short_name); + + fputs(USAGE_SEPARATOR, stdout); + puts(_("Consolidate duplicate files using hardlinks.")); + + fputs(USAGE_OPTIONS, stdout); + puts(_(" -c when finding candidates for linking, compare only file contents")); + puts(_(" -n don't actually link anything, just report what would be done")); + puts(_(" -v print summary after hardlinking")); + puts(_(" -vv print every hardlinked file and bytes saved + summary")); + puts(_(" -f force hardlinking across filesystems")); + puts(_(" -x exclude files matching pattern")); + fputs(USAGE_SEPARATOR, stdout); + printf(USAGE_HELP_OPTIONS(16)); /* char offset to align option descriptions */ + printf(USAGE_MAN_TAIL("hardlink(1)")); + exit(EXIT_SUCCESS); } + unsigned int buf[NBUF]; char iobuf1[NIOBUF], iobuf2[NIOBUF]; @@ -401,7 +412,19 @@ int main(int argc, char **argv) PCRE2_SIZE erroroffset; #endif dynstr nam1 = { NULL, 0 }; - while ((ch = getopt(argc, argv, "cnvhfx:")) != -1) { + + static const struct option longopts[] = { + { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, + { NULL, 0, NULL, 0 }, + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + while ((ch = getopt_long(argc, argv, "cnvfx:Vh", longopts, NULL)) != -1) { switch (ch) { case 'n': no_link++; @@ -419,17 +442,26 @@ int main(int argc, char **argv) #ifdef HAVE_PCRE exclude_pattern = (PCRE2_SPTR) optarg; #else - fprintf(stderr, "option x not supported (built without pcre2)\n"); + errx(EXIT_FAILURE, + _("option -x not supported (built without pcre2)")); exit(1); #endif break; + case 'V': + printf(UTIL_LINUX_VERSION); + return EXIT_SUCCESS; case 'h': + usage(); default: - usage(argv[0]); + errtryhelp(EXIT_FAILURE); } } - if (optind >= argc) - usage(argv[0]); + + if (optind == argc) { + warnx(_("no directory specified")); + errtryhelp(EXIT_FAILURE); + } + #ifdef HAVE_PCRE if (exclude_pattern) { re = pcre2_compile(exclude_pattern, /* the pattern */ @@ -440,9 +472,8 @@ int main(int argc, char **argv) PCRE2_UCHAR buffer[256]; pcre2_get_error_message(errornumber, buffer, sizeof(buffer)); - fprintf(stderr, "pattern error at offset %d: %s\n", + errx(EXIT_FAILURE, _("pattern error at offset %d: %s"), (int)erroroffset, buffer); - usage(argv[0]); } match_data = pcre2_match_data_create_from_pattern(re, NULL); } From bd0be5e02afd76a065315cc51f3c96c2b53fcdf0 Mon Sep 17 00:00:00 2001 From: Ruediger Meier Date: Mon, 12 Nov 2018 20:27:52 +0100 Subject: [PATCH 25/25] hardlink: add first simple tests The test still fails without pcre2 and may also make problems on exotic file systems. Signed-off-by: Ruediger Meier --- tests/commands.sh | 1 + tests/expected/hardlink/options-content | 26 ++++++ tests/expected/hardlink/options-dryrun | 34 ++++++++ tests/expected/hardlink/options-nargs | 34 ++++++++ tests/expected/hardlink/options-noregex | 1 + tests/expected/hardlink/options-orig | 26 ++++++ tests/expected/hardlink/options-regex-escapes | 26 ++++++ tests/ts/hardlink/options | 78 ++++++++++++++++++ tests/ts/hardlink/testdir1.tar.xz | Bin 0 -> 26468 bytes 9 files changed, 226 insertions(+) create mode 100644 tests/expected/hardlink/options-content create mode 100644 tests/expected/hardlink/options-dryrun create mode 100644 tests/expected/hardlink/options-nargs create mode 100644 tests/expected/hardlink/options-noregex create mode 100644 tests/expected/hardlink/options-orig create mode 100644 tests/expected/hardlink/options-regex-escapes create mode 100755 tests/ts/hardlink/options create mode 100644 tests/ts/hardlink/testdir1.tar.xz diff --git a/tests/commands.sh b/tests/commands.sh index 1be2d25b4..6f3139351 100644 --- a/tests/commands.sh +++ b/tests/commands.sh @@ -61,6 +61,7 @@ TS_CMD_FINDMNT=${TS_CMD_FINDMNT-"$top_builddir/findmnt"} TS_CMD_FSCKCRAMFS=${TS_CMD_FSCKCRAMFS:-"$top_builddir/fsck.cramfs"} TS_CMD_FSCKMINIX=${TS_CMD_FSCKMINIX:-"$top_builddir/fsck.minix"} TS_CMD_GETOPT=${TS_CMD_GETOPT-"$top_builddir/getopt"} +TS_CMD_HARDLINK=${TS_CMD_HARDLINK-"$top_builddir/hardlink"} TS_CMD_HEXDUMP=${TS_CMD_HEXDUMP-"$top_builddir/hexdump"} TS_CMD_HWCLOCK=${TS_CMD_HWCLOCK-"$top_builddir/hwclock"} TS_CMD_IONICE=${TS_CMD_IONICE-"$top_builddir/ionice"} diff --git a/tests/expected/hardlink/options-content b/tests/expected/hardlink/options-content new file mode 100644 index 000000000..a1bfede44 --- /dev/null +++ b/tests/expected/hardlink/options-content @@ -0,0 +1,26 @@ +dir-1/sdir-1/file-a-1 10 8192 1540236xxx perm +dir-1/sdir-1/file-a-2 10 8192 1540236xxx perm +dir-1/sdir-1/file-a-3 10 8192 1540236xxx perm +dir-1/sdir-1/file-b-1 10 8192 1540236xxx perm +dir-1/sdir-1/file-b-2 10 8192 1540236xxx perm +dir-1/sdir-1/file-b-3 10 8192 1540236xxx perm +dir-1/sdir-1/file-c-1 6 8192 1540236xxx perm +dir-1/sdir-1/file-c-2 6 8192 1540236xxx perm +dir-1/sdir-1/file-c-3 6 8192 1540236xxx perm +dir-1/sdir-2/file-a-1-abcdefghijklmnopqrstxyz-"§$%&()=?*+ 10 8192 1540236xxx perm +dir-2/sdir-2/file-a-5 10 8192 1540236xxx perm +dir-2/sdir-2/file-b-5 10 8192 1540236xxx perm +dir-2/sdir-3/file-b-4 10 8192 1540236xxx perm +file-a-1 10 8192 1540236xxx perm +file-a-2 10 8192 1540236xxx perm +file-a-3 10 8192 1540236xxx perm +file-a-4 10 8192 1540236xxx perm +file-a-5 10 8192 1540236xxx perm +file-b-1 10 8192 1540236xxx perm +file-b-2 10 8192 1540236xxx perm +file-b-3 10 8192 1540236xxx perm +file-b-4 10 8192 1540236xxx perm +file-b-5 10 8192 1540236xxx perm +file-c-1 6 8192 1540236xxx perm +file-c-2 6 8192 1540236xxx perm +file-c-3 6 8192 1540236xxx perm diff --git a/tests/expected/hardlink/options-dryrun b/tests/expected/hardlink/options-dryrun new file mode 100644 index 000000000..4e9d65d75 --- /dev/null +++ b/tests/expected/hardlink/options-dryrun @@ -0,0 +1,34 @@ + + +Directories 7 +Objects 33 +IFREG 26 +Comparisons 18 +Would link 18 +Would save 147456 +dir-1/sdir-1/file-a-1 1 8192 1540236330 644 +dir-1/sdir-1/file-a-2 1 8192 1540236330 644 +dir-1/sdir-1/file-a-3 1 8192 1540236423 644 +dir-1/sdir-1/file-b-1 1 8192 1540236383 644 +dir-1/sdir-1/file-b-2 1 8192 1540236383 644 +dir-1/sdir-1/file-b-3 1 8192 1540236430 644 +dir-1/sdir-1/file-c-1 1 8192 1540236330 644 +dir-1/sdir-1/file-c-2 1 8192 1540236330 644 +dir-1/sdir-1/file-c-3 1 8192 1540236548 644 +dir-1/sdir-2/file-a-1-abcdefghijklmnopqrstxyz-"§$%&()=?*+ 1 8192 1540236330 644 +dir-2/sdir-2/file-a-5 1 8192 1540236330 600 +dir-2/sdir-2/file-b-5 1 8192 1540236383 640 +dir-2/sdir-3/file-b-4 1 8192 1540236383 640 +file-a-1 1 8192 1540236330 644 +file-a-2 1 8192 1540236330 644 +file-a-3 1 8192 1540236423 644 +file-a-4 1 8192 1540236330 600 +file-a-5 1 8192 1540236330 600 +file-b-1 1 8192 1540236383 644 +file-b-2 1 8192 1540236383 644 +file-b-3 1 8192 1540236430 644 +file-b-4 1 8192 1540236383 640 +file-b-5 1 8192 1540236383 640 +file-c-1 1 8192 1540236330 644 +file-c-2 1 8192 1540236330 644 +file-c-3 1 8192 1540236548 644 diff --git a/tests/expected/hardlink/options-nargs b/tests/expected/hardlink/options-nargs new file mode 100644 index 000000000..7705bba30 --- /dev/null +++ b/tests/expected/hardlink/options-nargs @@ -0,0 +1,34 @@ + + +Directories 1 +Objects 16 +IFREG 15 +Comparisons 9 +Linked 9 +saved 73728 +dir-1/sdir-1/file-a-1 4 8192 1540236330 644 +dir-1/sdir-1/file-a-2 4 8192 1540236330 644 +dir-1/sdir-1/file-a-3 1 8192 1540236423 644 +dir-1/sdir-1/file-b-1 4 8192 1540236383 644 +dir-1/sdir-1/file-b-2 4 8192 1540236383 644 +dir-1/sdir-1/file-b-3 1 8192 1540236430 644 +dir-1/sdir-1/file-c-1 4 8192 1540236330 644 +dir-1/sdir-1/file-c-2 4 8192 1540236330 644 +dir-1/sdir-1/file-c-3 1 8192 1540236548 644 +dir-1/sdir-2/file-a-1-abcdefghijklmnopqrstxyz-"§$%&()=?*+ 1 8192 1540236330 644 +dir-2/sdir-2/file-a-5 1 8192 1540236330 600 +dir-2/sdir-2/file-b-5 1 8192 1540236383 640 +dir-2/sdir-3/file-b-4 1 8192 1540236383 640 +file-a-1 4 8192 1540236330 644 +file-a-2 4 8192 1540236330 644 +file-a-3 1 8192 1540236423 644 +file-a-4 1 8192 1540236330 600 +file-a-5 1 8192 1540236330 600 +file-b-1 4 8192 1540236383 644 +file-b-2 4 8192 1540236383 644 +file-b-3 1 8192 1540236430 644 +file-b-4 1 8192 1540236383 640 +file-b-5 1 8192 1540236383 640 +file-c-1 4 8192 1540236330 644 +file-c-2 4 8192 1540236330 644 +file-c-3 1 8192 1540236548 644 diff --git a/tests/expected/hardlink/options-noregex b/tests/expected/hardlink/options-noregex new file mode 100644 index 000000000..93363adcb --- /dev/null +++ b/tests/expected/hardlink/options-noregex @@ -0,0 +1 @@ +hardlink: option -x not supported (built without pcre2) diff --git a/tests/expected/hardlink/options-orig b/tests/expected/hardlink/options-orig new file mode 100644 index 000000000..6b578233a --- /dev/null +++ b/tests/expected/hardlink/options-orig @@ -0,0 +1,26 @@ +dir-1/sdir-1/file-a-1 1 8192 1540236330 644 +dir-1/sdir-1/file-a-2 1 8192 1540236330 644 +dir-1/sdir-1/file-a-3 1 8192 1540236423 644 +dir-1/sdir-1/file-b-1 1 8192 1540236383 644 +dir-1/sdir-1/file-b-2 1 8192 1540236383 644 +dir-1/sdir-1/file-b-3 1 8192 1540236430 644 +dir-1/sdir-1/file-c-1 1 8192 1540236330 644 +dir-1/sdir-1/file-c-2 1 8192 1540236330 644 +dir-1/sdir-1/file-c-3 1 8192 1540236548 644 +dir-1/sdir-2/file-a-1-abcdefghijklmnopqrstxyz-"§$%&()=?*+ 1 8192 1540236330 644 +dir-2/sdir-2/file-a-5 1 8192 1540236330 600 +dir-2/sdir-2/file-b-5 1 8192 1540236383 640 +dir-2/sdir-3/file-b-4 1 8192 1540236383 640 +file-a-1 1 8192 1540236330 644 +file-a-2 1 8192 1540236330 644 +file-a-3 1 8192 1540236423 644 +file-a-4 1 8192 1540236330 600 +file-a-5 1 8192 1540236330 600 +file-b-1 1 8192 1540236383 644 +file-b-2 1 8192 1540236383 644 +file-b-3 1 8192 1540236430 644 +file-b-4 1 8192 1540236383 640 +file-b-5 1 8192 1540236383 640 +file-c-1 1 8192 1540236330 644 +file-c-2 1 8192 1540236330 644 +file-c-3 1 8192 1540236548 644 diff --git a/tests/expected/hardlink/options-regex-escapes b/tests/expected/hardlink/options-regex-escapes new file mode 100644 index 000000000..afab5e35d --- /dev/null +++ b/tests/expected/hardlink/options-regex-escapes @@ -0,0 +1,26 @@ +dir-1/sdir-1/file-a-1 4 8192 1540236330 644 +dir-1/sdir-1/file-a-2 4 8192 1540236330 644 +dir-1/sdir-1/file-a-3 2 8192 1540236423 644 +dir-1/sdir-1/file-b-1 4 8192 1540236383 644 +dir-1/sdir-1/file-b-2 4 8192 1540236383 644 +dir-1/sdir-1/file-b-3 2 8192 1540236430 644 +dir-1/sdir-1/file-c-1 4 8192 1540236330 644 +dir-1/sdir-1/file-c-2 4 8192 1540236330 644 +dir-1/sdir-1/file-c-3 2 8192 1540236548 644 +dir-1/sdir-2/file-a-1-abcdefghijklmnopqrstxyz-"§$%&()=?*+ 1 8192 1540236330 644 +dir-2/sdir-2/file-a-5 3 8192 1540236330 600 +dir-2/sdir-2/file-b-5 4 8192 1540236383 640 +dir-2/sdir-3/file-b-4 4 8192 1540236383 640 +file-a-1 4 8192 1540236330 644 +file-a-2 4 8192 1540236330 644 +file-a-3 2 8192 1540236423 644 +file-a-4 3 8192 1540236330 600 +file-a-5 3 8192 1540236330 600 +file-b-1 4 8192 1540236383 644 +file-b-2 4 8192 1540236383 644 +file-b-3 2 8192 1540236430 644 +file-b-4 4 8192 1540236383 640 +file-b-5 4 8192 1540236383 640 +file-c-1 4 8192 1540236330 644 +file-c-2 4 8192 1540236330 644 +file-c-3 2 8192 1540236548 644 diff --git a/tests/ts/hardlink/options b/tests/ts/hardlink/options new file mode 100755 index 000000000..848ea1654 --- /dev/null +++ b/tests/ts/hardlink/options @@ -0,0 +1,78 @@ +#!/bin/bash +# +# Copyright (C) 2018 Ruediger Meier +# +# This file is part of util-linux. +# +# This file is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This file is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# + +TS_TOPDIR="${0%/*}/../.." +TS_DESC="options" + +. $TS_TOPDIR/functions.sh + +ts_init "$*" + +ts_check_test_command "$TS_CMD_HARDLINK" + +SRCDIR="$TS_OUTDIR/testdir1" + +create_srcdir() +{ + rm -rf "$SRCDIR" + tar -C "$TS_OUTDIR" -xJf "$TS_SELF/testdir1.tar.xz" +} + +show_srcdir() +{ + find "$SRCDIR" -type f -printf "%P\t%n\t%s\t%Ts\t%m\n" | sort +} + +create_srcdir + +ts_init_subtest "orig" # just list original dir +show_srcdir >>$TS_OUTPUT 2>&1 +ts_finalize_subtest + +ts_init_subtest "dryrun" +$TS_CMD_HARDLINK -n -v "$SRCDIR" >>$TS_OUTPUT 2>&1 +show_srcdir >>$TS_OUTPUT 2>&1 +ts_finalize_subtest + +ts_init_subtest "nargs" +$TS_CMD_HARDLINK -v "$SRCDIR"/dir-1/sdir-1 "$SRCDIR"/file-?-{1,2} >>$TS_OUTPUT 2>&1 +show_srcdir >>$TS_OUTPUT 2>&1 +ts_finalize_subtest + +# cases without and with -x support +tmp=$($TS_CMD_HARDLINK -x pattern "$SRCDIR"/dir-1/sdir-2 2>&1) +if test $? -ne 0; then + ts_init_subtest "noregex" + printf "%s\n" "$tmp" >>$TS_OUTPUT 2>&1 + ts_finalize_subtest +else + ts_init_subtest "regex-escapes" + $TS_CMD_HARDLINK -x '.*z-"§\$%&\(\)=\?\*\+$' "$SRCDIR" >>$TS_OUTPUT 2>&1 + show_srcdir >>$TS_OUTPUT 2>&1 + ts_finalize_subtest +fi + +ts_init_subtest "content" +$TS_CMD_HARDLINK -c "$SRCDIR" >>$TS_OUTPUT 2>&1 +# When using -c we need to cheat with sed because it's not deterministic which +# file (i.e. which timestamp and perms) wins. TODO at least the choice of the +# permissions should be sensitive by default and/or controllable by the user. +show_srcdir | sed 's/\(1540236\).*/\1xxx\tperm/' >>$TS_OUTPUT 2>&1 +ts_finalize_subtest + +rm -rf "$SRCDIR" +ts_finalize diff --git a/tests/ts/hardlink/testdir1.tar.xz b/tests/ts/hardlink/testdir1.tar.xz new file mode 100644 index 0000000000000000000000000000000000000000..b9bc245d17dea9ac81d3b1ada0aa5df073dd4fea GIT binary patch literal 26468 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 PvBYQl0ssI200dcDJEK>$ literal 0 HcmV?d00001