]>
Commit | Line | Data |
---|---|---|
55c000e1 RM |
1 | /* |
2 | * hardlink - consolidate duplicate files via hardlinks | |
3 | * | |
4 | * Copyright (C) 2018 Red Hat, Inc. All rights reserved. | |
5 | * Written by Jakub Jelinek <jakub@redhat.com> | |
6 | * | |
0b05aab4 KZ |
7 | * Copyright (C) 2019 Karel Zak <kzak@redhat.com> |
8 | * | |
55c000e1 RM |
9 | * This program is free software; you can redistribute it and/or modify |
10 | * it under the terms of the GNU General Public License as published by | |
11 | * the Free Software Foundation; either version 2 of the License, or | |
12 | * (at your option) any later version. | |
13 | * | |
14 | * This program is distributed in the hope that it would be useful, | |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 | * GNU General Public License for more details. | |
18 | * | |
19 | * You should have received a copy of the GNU General Public License along | |
20 | * with this program; if not, write to the Free Software Foundation, Inc., | |
21 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
22 | */ | |
0ec20db8 DJ |
23 | #include <sys/types.h> |
24 | #include <stdlib.h> | |
7d50d361 | 25 | #include <getopt.h> |
0ec20db8 DJ |
26 | #include <stdio.h> |
27 | #include <unistd.h> | |
28 | #include <sys/stat.h> | |
29 | #include <sys/mman.h> | |
30 | #include <string.h> | |
31 | #include <dirent.h> | |
32 | #include <fcntl.h> | |
cbb0524c | 33 | #include <errno.h> |
04ae85a7 RM |
34 | #ifdef HAVE_PCRE |
35 | # define PCRE2_CODE_UNIT_WIDTH 8 | |
36 | # include <pcre2.h> | |
37 | #endif | |
0ec20db8 | 38 | |
7d50d361 | 39 | #include "c.h" |
d55db672 | 40 | #include "xalloc.h" |
7d50d361 RM |
41 | #include "nls.h" |
42 | #include "closestream.h" | |
43 | ||
55c000e1 | 44 | #define NHASH (1<<17) /* Must be a power of 2! */ |
55c000e1 | 45 | #define NBUF 64 |
0ec20db8 | 46 | |
3807e71a KZ |
47 | struct hardlink_file; |
48 | ||
49 | struct hardlink_hash { | |
50 | struct hardlink_hash *next; | |
51 | struct hardlink_file *chain; | |
55c000e1 RM |
52 | off_t size; |
53 | time_t mtime; | |
3807e71a | 54 | }; |
0ec20db8 | 55 | |
3807e71a KZ |
56 | struct hardlink_dir { |
57 | struct hardlink_dir *next; | |
e87263aa | 58 | char name[]; |
3807e71a | 59 | }; |
0ec20db8 | 60 | |
3807e71a KZ |
61 | struct hardlink_file { |
62 | struct hardlink_file *next; | |
63 | ino_t ino; | |
64 | dev_t dev; | |
65 | unsigned int cksum; | |
e87263aa | 66 | char name[]; |
3807e71a | 67 | }; |
0ec20db8 | 68 | |
3807e71a KZ |
69 | struct hardlink_dynstr { |
70 | char *buf; | |
71 | size_t alloc; | |
72 | }; | |
73 | ||
007b9874 SK |
74 | struct hardlink_ctl { |
75 | struct hardlink_dir *dirs; | |
76 | struct hardlink_hash *hps[NHASH]; | |
e2aa5d82 SK |
77 | char iobuf1[BUFSIZ]; |
78 | char iobuf2[BUFSIZ]; | |
007b9874 SK |
79 | /* summary counters */ |
80 | unsigned long long ndirs; | |
81 | unsigned long long nobjects; | |
82 | unsigned long long nregfiles; | |
83 | unsigned long long ncomp; | |
84 | unsigned long long nlinks; | |
85 | unsigned long long nsaved; | |
86 | /* current device */ | |
87 | dev_t dev; | |
88 | /* flags */ | |
89 | unsigned int verbose; | |
90 | unsigned int | |
91 | no_link:1, | |
92 | content_only:1, | |
93 | force:1; | |
94 | }; | |
95 | /* ctl is in global scope due use in atexit() */ | |
96 | struct hardlink_ctl global_ctl; | |
0ec20db8 | 97 | |
55c000e1 | 98 | __attribute__ ((always_inline)) |
53071734 | 99 | static inline unsigned int hash(off_t size, time_t mtime) |
0ec20db8 | 100 | { |
55c000e1 | 101 | return (size ^ mtime) & (NHASH - 1); |
0ec20db8 DJ |
102 | } |
103 | ||
55c000e1 | 104 | __attribute__ ((always_inline)) |
123eb9ef | 105 | static inline int stcmp(struct stat *st1, struct stat *st2, int content_scope) |
0ec20db8 | 106 | { |
123eb9ef | 107 | if (content_scope) |
55c000e1 | 108 | return st1->st_size != st2->st_size; |
0b05aab4 | 109 | |
bd7722af KZ |
110 | return st1->st_mode != st2->st_mode |
111 | || st1->st_uid != st2->st_uid | |
112 | || st1->st_gid != st2->st_gid | |
113 | || st1->st_size != st2->st_size | |
114 | || st1->st_mtime != st2->st_mtime; | |
0ec20db8 DJ |
115 | } |
116 | ||
d55db672 | 117 | static void print_summary(void) |
0ec20db8 | 118 | { |
007b9874 SK |
119 | struct hardlink_ctl const *const ctl = &global_ctl; |
120 | ||
121 | if (!ctl->verbose) | |
d55db672 KZ |
122 | return; |
123 | ||
007b9874 | 124 | if (ctl->verbose > 1 && ctl->nlinks) |
d55db672 | 125 | fputc('\n', stdout); |
bd7722af | 126 | |
007b9874 SK |
127 | printf(_("Directories: %9lld\n"), ctl->ndirs); |
128 | printf(_("Objects: %9lld\n"), ctl->nobjects); | |
129 | printf(_("Regular files: %9lld\n"), ctl->nregfiles); | |
130 | printf(_("Comparisons: %9lld\n"), ctl->ncomp); | |
131 | printf( "%s%9lld\n", (ctl->no_link ? | |
d55db672 | 132 | _("Would link: ") : |
007b9874 SK |
133 | _("Linked: ")), ctl->nlinks); |
134 | printf( "%s %9lld\n", (ctl->no_link ? | |
135 | _("Would save: ") : | |
136 | _("Saved: ")), ctl->nsaved); | |
0ec20db8 DJ |
137 | } |
138 | ||
7d50d361 | 139 | static void __attribute__((__noreturn__)) usage(void) |
0ec20db8 | 140 | { |
7d50d361 RM |
141 | fputs(USAGE_HEADER, stdout); |
142 | printf(_(" %s [options] directory...\n"), program_invocation_short_name); | |
143 | ||
144 | fputs(USAGE_SEPARATOR, stdout); | |
145 | puts(_("Consolidate duplicate files using hardlinks.")); | |
146 | ||
147 | fputs(USAGE_OPTIONS, stdout); | |
98542119 KZ |
148 | puts(_(" -c, --content compare only contents, ignore permission, etc.")); |
149 | puts(_(" -n, --dry-run don't actually link anything")); | |
150 | puts(_(" -v, --verbose print summary after hardlinking")); | |
151 | puts(_(" -vv print every hardlinked file and summary")); | |
152 | puts(_(" -f, --force force hardlinking across filesystems")); | |
153 | puts(_(" -x, --exclude <regex> exclude files matching pattern")); | |
0b05aab4 | 154 | |
7d50d361 RM |
155 | fputs(USAGE_SEPARATOR, stdout); |
156 | printf(USAGE_HELP_OPTIONS(16)); /* char offset to align option descriptions */ | |
157 | printf(USAGE_MAN_TAIL("hardlink(1)")); | |
158 | exit(EXIT_SUCCESS); | |
0ec20db8 DJ |
159 | } |
160 | ||
55c000e1 | 161 | __attribute__ ((always_inline)) |
53071734 | 162 | static inline size_t add2(size_t a, size_t b) |
94b040b0 | 163 | { |
55c000e1 | 164 | size_t sum = a + b; |
bd7722af | 165 | |
d55db672 KZ |
166 | if (sum < a) |
167 | errx(EXIT_FAILURE, _("integer overflow")); | |
55c000e1 | 168 | return sum; |
94b040b0 JN |
169 | } |
170 | ||
55c000e1 | 171 | __attribute__ ((always_inline)) |
53071734 | 172 | static inline size_t add3(size_t a, size_t b, size_t c) |
94b040b0 | 173 | { |
55c000e1 | 174 | return add2(add2(a, b), c); |
94b040b0 JN |
175 | } |
176 | ||
3807e71a | 177 | static void growstr(struct hardlink_dynstr *str, size_t newlen) |
94b040b0 | 178 | { |
55c000e1 RM |
179 | if (newlen < str->alloc) |
180 | return; | |
d55db672 | 181 | str->buf = xrealloc(str->buf, str->alloc = add2(newlen, 1)); |
94b040b0 | 182 | } |
55c000e1 | 183 | |
007b9874 | 184 | static void process_path(struct hardlink_ctl *ctl, const char *name) |
0ec20db8 | 185 | { |
55c000e1 RM |
186 | struct stat st, st2, st3; |
187 | const size_t namelen = strlen(name); | |
0b05aab4 | 188 | |
007b9874 | 189 | ctl->nobjects++; |
55c000e1 RM |
190 | if (lstat(name, &st)) |
191 | return; | |
bd7722af | 192 | |
007b9874 SK |
193 | if (st.st_dev != ctl->dev && !ctl->force) { |
194 | if (ctl->dev) | |
d55db672 KZ |
195 | errx(EXIT_FAILURE, |
196 | _("%s is on different filesystem than the rest " | |
197 | "(use -f option to override)."), name); | |
007b9874 | 198 | ctl->dev = st.st_dev; |
0cd6b1d3 | 199 | } |
55c000e1 | 200 | if (S_ISDIR(st.st_mode)) { |
3807e71a | 201 | struct hardlink_dir *dp = xmalloc(add3(sizeof(*dp), namelen, 1)); |
55c000e1 | 202 | memcpy(dp->name, name, namelen + 1); |
007b9874 SK |
203 | dp->next = ctl->dirs; |
204 | ctl->dirs = dp; | |
bd7722af | 205 | |
55c000e1 RM |
206 | } else if (S_ISREG(st.st_mode)) { |
207 | int fd, i; | |
3807e71a KZ |
208 | struct hardlink_file *fp, *fp2; |
209 | struct hardlink_hash *hp; | |
55c000e1 | 210 | const char *n1, *n2; |
007b9874 | 211 | unsigned int buf[NBUF]; |
55c000e1 RM |
212 | int cksumsize = sizeof(buf); |
213 | unsigned int cksum; | |
007b9874 | 214 | time_t mtime = ctl->content_only ? 0 : st.st_mtime; |
55c000e1 RM |
215 | unsigned int hsh = hash(st.st_size, mtime); |
216 | off_t fsize; | |
3807e71a | 217 | |
007b9874 SK |
218 | ctl->nregfiles++; |
219 | if (ctl->verbose > 1) | |
08a2cf72 | 220 | printf("%s\n", name); |
bd7722af | 221 | |
55c000e1 RM |
222 | fd = open(name, O_RDONLY); |
223 | if (fd < 0) | |
224 | return; | |
bd7722af | 225 | |
53071734 | 226 | if ((size_t)st.st_size < sizeof(buf)) { |
55c000e1 RM |
227 | cksumsize = st.st_size; |
228 | memset(((char *)buf) + cksumsize, 0, | |
229 | (sizeof(buf) - cksumsize) % sizeof(buf[0])); | |
230 | } | |
231 | if (read(fd, buf, cksumsize) != cksumsize) { | |
232 | close(fd); | |
55c000e1 RM |
233 | return; |
234 | } | |
235 | cksumsize = (cksumsize + sizeof(buf[0]) - 1) / sizeof(buf[0]); | |
236 | for (i = 0, cksum = 0; i < cksumsize; i++) { | |
237 | if (cksum + buf[i] < cksum) | |
238 | cksum += buf[i] + 1; | |
239 | else | |
240 | cksum += buf[i]; | |
241 | } | |
007b9874 | 242 | for (hp = ctl->hps[hsh]; hp; hp = hp->next) { |
55c000e1 RM |
243 | if (hp->size == st.st_size && hp->mtime == mtime) |
244 | break; | |
bd7722af | 245 | } |
55c000e1 | 246 | if (!hp) { |
3807e71a | 247 | hp = xmalloc(sizeof(*hp)); |
55c000e1 RM |
248 | hp->size = st.st_size; |
249 | hp->mtime = mtime; | |
250 | hp->chain = NULL; | |
007b9874 SK |
251 | hp->next = ctl->hps[hsh]; |
252 | ctl->hps[hsh] = hp; | |
55c000e1 | 253 | } |
bd7722af | 254 | for (fp = hp->chain; fp; fp = fp->next) { |
55c000e1 RM |
255 | if (fp->cksum == cksum) |
256 | break; | |
bd7722af KZ |
257 | } |
258 | for (fp2 = fp; fp2 && fp2->cksum == cksum; fp2 = fp2->next) { | |
55c000e1 RM |
259 | if (fp2->ino == st.st_ino && fp2->dev == st.st_dev) { |
260 | close(fd); | |
55c000e1 RM |
261 | return; |
262 | } | |
bd7722af KZ |
263 | } |
264 | for (fp2 = fp; fp2 && fp2->cksum == cksum; fp2 = fp2->next) { | |
265 | ||
55c000e1 | 266 | if (!lstat(fp2->name, &st2) && S_ISREG(st2.st_mode) && |
007b9874 | 267 | !stcmp(&st, &st2, ctl->content_only) && |
55c000e1 RM |
268 | st2.st_ino != st.st_ino && |
269 | st2.st_dev == st.st_dev) { | |
bd7722af | 270 | |
55c000e1 RM |
271 | int fd2 = open(fp2->name, O_RDONLY); |
272 | if (fd2 < 0) | |
273 | continue; | |
bd7722af | 274 | |
55c000e1 RM |
275 | if (fstat(fd2, &st2) || !S_ISREG(st2.st_mode) |
276 | || st2.st_size == 0) { | |
277 | close(fd2); | |
278 | continue; | |
279 | } | |
007b9874 | 280 | ctl->ncomp++; |
55c000e1 | 281 | lseek(fd, 0, SEEK_SET); |
bd7722af | 282 | |
55c000e1 | 283 | for (fsize = st.st_size; fsize > 0; |
e2aa5d82 | 284 | fsize -= (off_t)sizeof(ctl->iobuf1)) { |
c545e62c | 285 | ssize_t xsz; |
74a4d220 KZ |
286 | ssize_t rsize = fsize > (ssize_t) sizeof(ctl->iobuf1) ? |
287 | (ssize_t) sizeof(ctl->iobuf1) : fsize; | |
c545e62c | 288 | |
007b9874 | 289 | if ((xsz = read(fd, ctl->iobuf1, rsize)) != rsize) |
c545e62c | 290 | warn(_("cannot read %s"), name); |
007b9874 | 291 | else if ((xsz = read(fd2, ctl->iobuf2, rsize)) != rsize) |
c545e62c KZ |
292 | warn(_("cannot read %s"), fp2->name); |
293 | ||
294 | if (xsz != rsize) { | |
55c000e1 RM |
295 | close(fd); |
296 | close(fd2); | |
55c000e1 RM |
297 | return; |
298 | } | |
007b9874 | 299 | if (memcmp(ctl->iobuf1, ctl->iobuf2, rsize)) |
55c000e1 RM |
300 | break; |
301 | } | |
302 | close(fd2); | |
303 | if (fsize > 0) | |
304 | continue; | |
305 | if (lstat(name, &st3)) { | |
c545e62c | 306 | warn(_("cannot stat %s"), name); |
55c000e1 RM |
307 | close(fd); |
308 | return; | |
309 | } | |
310 | st3.st_atime = st.st_atime; | |
311 | if (stcmp(&st, &st3, 0)) { | |
c545e62c | 312 | warnx(_("file %s changed underneath us"), name); |
55c000e1 RM |
313 | close(fd); |
314 | return; | |
315 | } | |
316 | n1 = fp2->name; | |
317 | n2 = name; | |
bd7722af | 318 | |
007b9874 | 319 | if (!ctl->no_link) { |
55c000e1 RM |
320 | const char *suffix = |
321 | ".$$$___cleanit___$$$"; | |
322 | const size_t suffixlen = strlen(suffix); | |
323 | size_t n2len = strlen(n2); | |
3807e71a | 324 | struct hardlink_dynstr nam2 = { NULL, 0 }; |
bd7722af | 325 | |
55c000e1 RM |
326 | growstr(&nam2, add2(n2len, suffixlen)); |
327 | memcpy(nam2.buf, n2, n2len); | |
328 | memcpy(&nam2.buf[n2len], suffix, | |
329 | suffixlen + 1); | |
330 | /* First create a temporary link to n1 under a new name */ | |
331 | if (link(n1, nam2.buf)) { | |
c545e62c KZ |
332 | warn(_("failed to hardlink %s to %s (create temporary link as %s failed)"), |
333 | n1, n2, nam2.buf); | |
55c000e1 RM |
334 | free(nam2.buf); |
335 | continue; | |
336 | } | |
337 | /* Then rename into place over the existing n2 */ | |
338 | if (rename(nam2.buf, n2)) { | |
c545e62c KZ |
339 | warn(_("failed to hardlink %s to %s (rename temporary link to %s failed)"), |
340 | n1, n2, n2); | |
55c000e1 | 341 | /* Something went wrong, try to remove the now redundant temporary link */ |
c545e62c KZ |
342 | if (unlink(nam2.buf)) |
343 | warn(_("failed to remove temporary link %s"), nam2.buf); | |
55c000e1 RM |
344 | free(nam2.buf); |
345 | continue; | |
346 | } | |
347 | free(nam2.buf); | |
348 | } | |
007b9874 | 349 | ctl->nlinks++; |
55c000e1 RM |
350 | if (st3.st_nlink > 1) { |
351 | /* We actually did not save anything this time, since the link second argument | |
352 | had some other links as well. */ | |
007b9874 | 353 | if (ctl->verbose > 1) |
08a2cf72 | 354 | printf(_(" %s %s to %s\n"), |
007b9874 | 355 | (ctl->no_link ? _("Would link") : _("Linked")), |
c545e62c | 356 | n1, n2); |
55c000e1 | 357 | } else { |
007b9874 SK |
358 | ctl->nsaved += ((st.st_size + 4095) / 4096) * 4096; |
359 | if (ctl->verbose > 1) | |
08a2cf72 | 360 | printf(_(" %s %s to %s, %s %jd\n"), |
007b9874 | 361 | (ctl->no_link ? _("Would link") : _("Linked")), |
c545e62c | 362 | n1, n2, |
007b9874 | 363 | (ctl->no_link ? _("would save") : _("saved")), |
53071734 | 364 | (intmax_t)st.st_size); |
55c000e1 RM |
365 | } |
366 | close(fd); | |
367 | return; | |
368 | } | |
bd7722af | 369 | } |
3807e71a | 370 | fp2 = xmalloc(add3(sizeof(*fp2), namelen, 1)); |
55c000e1 RM |
371 | close(fd); |
372 | fp2->ino = st.st_ino; | |
373 | fp2->dev = st.st_dev; | |
374 | fp2->cksum = cksum; | |
375 | memcpy(fp2->name, name, namelen + 1); | |
bd7722af | 376 | |
55c000e1 RM |
377 | if (fp) { |
378 | fp2->next = fp->next; | |
379 | fp->next = fp2; | |
380 | } else { | |
381 | fp2->next = hp->chain; | |
382 | hp->chain = fp2; | |
383 | } | |
55c000e1 | 384 | return; |
0ec20db8 | 385 | } |
0ec20db8 DJ |
386 | } |
387 | ||
388 | int main(int argc, char **argv) | |
389 | { | |
55c000e1 RM |
390 | int ch; |
391 | int i; | |
04ae85a7 | 392 | #ifdef HAVE_PCRE |
55c000e1 RM |
393 | int errornumber; |
394 | PCRE2_SIZE erroroffset; | |
ac255c2b | 395 | pcre2_code *re = NULL; |
74a4d220 | 396 | PCRE2_SPTR exclude_pattern = NULL; |
ac255c2b | 397 | pcre2_match_data *match_data = NULL; |
04ae85a7 | 398 | #endif |
3807e71a | 399 | struct hardlink_dynstr nam1 = { NULL, 0 }; |
007b9874 | 400 | struct hardlink_ctl *ctl = &global_ctl; |
7d50d361 RM |
401 | |
402 | static const struct option longopts[] = { | |
98542119 KZ |
403 | { "content", no_argument, NULL, 'c' }, |
404 | { "dry-run", no_argument, NULL, 'n' }, | |
405 | { "exclude", required_argument, NULL, 'x' }, | |
406 | { "force", no_argument, NULL, 'f' }, | |
7d50d361 | 407 | { "help", no_argument, NULL, 'h' }, |
98542119 KZ |
408 | { "verbose", no_argument, NULL, 'v' }, |
409 | { "version", no_argument, NULL, 'V' }, | |
7d50d361 RM |
410 | { NULL, 0, NULL, 0 }, |
411 | }; | |
412 | ||
413 | setlocale(LC_ALL, ""); | |
414 | bindtextdomain(PACKAGE, LOCALEDIR); | |
415 | textdomain(PACKAGE); | |
2c308875 | 416 | close_stdout_atexit(); |
7d50d361 RM |
417 | |
418 | while ((ch = getopt_long(argc, argv, "cnvfx:Vh", longopts, NULL)) != -1) { | |
55c000e1 RM |
419 | switch (ch) { |
420 | case 'n': | |
007b9874 | 421 | ctl->no_link = 1; |
55c000e1 RM |
422 | break; |
423 | case 'v': | |
007b9874 | 424 | ctl->verbose++; |
55c000e1 RM |
425 | break; |
426 | case 'c': | |
007b9874 | 427 | ctl->content_only = 1; |
55c000e1 RM |
428 | break; |
429 | case 'f': | |
007b9874 | 430 | ctl->force = 1; |
55c000e1 RM |
431 | break; |
432 | case 'x': | |
04ae85a7 | 433 | #ifdef HAVE_PCRE |
55c000e1 | 434 | exclude_pattern = (PCRE2_SPTR) optarg; |
04ae85a7 | 435 | #else |
7d50d361 | 436 | errx(EXIT_FAILURE, |
98542119 | 437 | _("option --exclude not supported (built without pcre2)")); |
04ae85a7 | 438 | #endif |
55c000e1 | 439 | break; |
7d50d361 | 440 | case 'V': |
2c308875 | 441 | print_version(EXIT_SUCCESS); |
55c000e1 | 442 | case 'h': |
7d50d361 | 443 | usage(); |
55c000e1 | 444 | default: |
7d50d361 | 445 | errtryhelp(EXIT_FAILURE); |
55c000e1 RM |
446 | } |
447 | } | |
7d50d361 RM |
448 | |
449 | if (optind == argc) { | |
450 | warnx(_("no directory specified")); | |
451 | errtryhelp(EXIT_FAILURE); | |
452 | } | |
453 | ||
04ae85a7 | 454 | #ifdef HAVE_PCRE |
55c000e1 RM |
455 | if (exclude_pattern) { |
456 | re = pcre2_compile(exclude_pattern, /* the pattern */ | |
457 | PCRE2_ZERO_TERMINATED, /* indicates pattern is zero-terminate */ | |
458 | 0, /* default options */ | |
459 | &errornumber, &erroroffset, NULL); /* use default compile context */ | |
460 | if (!re) { | |
461 | PCRE2_UCHAR buffer[256]; | |
462 | pcre2_get_error_message(errornumber, buffer, | |
463 | sizeof(buffer)); | |
7d50d361 | 464 | errx(EXIT_FAILURE, _("pattern error at offset %d: %s"), |
55c000e1 | 465 | (int)erroroffset, buffer); |
55c000e1 RM |
466 | } |
467 | match_data = pcre2_match_data_create_from_pattern(re, NULL); | |
468 | } | |
04ae85a7 | 469 | #endif |
d55db672 KZ |
470 | atexit(print_summary); |
471 | ||
55c000e1 | 472 | for (i = optind; i < argc; i++) |
007b9874 | 473 | process_path(ctl, argv[i]); |
bd7722af | 474 | |
007b9874 | 475 | while (ctl->dirs) { |
55c000e1 RM |
476 | DIR *dh; |
477 | struct dirent *di; | |
007b9874 | 478 | struct hardlink_dir *dp = ctl->dirs; |
55c000e1 | 479 | size_t nam1baselen = strlen(dp->name); |
bd7722af | 480 | |
007b9874 | 481 | ctl->dirs = dp->next; |
55c000e1 RM |
482 | growstr(&nam1, add2(nam1baselen, 1)); |
483 | memcpy(nam1.buf, dp->name, nam1baselen); | |
484 | free(dp); | |
485 | nam1.buf[nam1baselen++] = '/'; | |
486 | nam1.buf[nam1baselen] = 0; | |
487 | dh = opendir(nam1.buf); | |
bd7722af | 488 | |
55c000e1 RM |
489 | if (dh == NULL) |
490 | continue; | |
007b9874 | 491 | ctl->ndirs++; |
bd7722af | 492 | |
55c000e1 RM |
493 | while ((di = readdir(dh)) != NULL) { |
494 | if (!di->d_name[0]) | |
495 | continue; | |
496 | if (di->d_name[0] == '.') { | |
497 | if (!di->d_name[1] || !strcmp(di->d_name, "..")) | |
498 | continue; | |
499 | } | |
04ae85a7 | 500 | #ifdef HAVE_PCRE |
55c000e1 RM |
501 | if (re && pcre2_match(re, /* compiled regex */ |
502 | (PCRE2_SPTR) di->d_name, strlen(di->d_name), 0, /* start at offset 0 */ | |
503 | 0, /* default options */ | |
504 | match_data, /* block for storing the result */ | |
505 | NULL) /* use default match context */ | |
506 | >=0) { | |
007b9874 | 507 | if (ctl->verbose) { |
55c000e1 | 508 | nam1.buf[nam1baselen] = 0; |
c545e62c | 509 | printf(_("Skipping %s%s\n"), nam1.buf, di->d_name); |
55c000e1 RM |
510 | } |
511 | continue; | |
512 | } | |
04ae85a7 | 513 | #endif |
55c000e1 RM |
514 | { |
515 | size_t subdirlen; | |
516 | growstr(&nam1, | |
517 | add2(nam1baselen, subdirlen = | |
518 | strlen(di->d_name))); | |
519 | memcpy(&nam1.buf[nam1baselen], di->d_name, | |
520 | add2(subdirlen, 1)); | |
521 | } | |
007b9874 | 522 | process_path(ctl, nam1.buf); |
55c000e1 RM |
523 | } |
524 | closedir(dh); | |
525 | } | |
d55db672 | 526 | |
55c000e1 | 527 | return 0; |
0ec20db8 | 528 | } |