]> git.ipfire.org Git - thirdparty/xfsprogs-dev.git/blame - fsr/xfs_fsr.c
libfrog: move crc32c.h to libfrog/
[thirdparty/xfsprogs-dev.git] / fsr / xfs_fsr.c
CommitLineData
959ef981 1// SPDX-License-Identifier: GPL-2.0
c988ea91
CH
2/*
3 * Copyright (c) 2000-2002 Silicon Graphics, Inc.
4 * All Rights Reserved.
c988ea91
CH
5 */
6
6b803e5a
CH
7#include "libxfs.h"
8#include "xfs.h"
9#include "xfs_types.h"
10#include "jdm.h"
11#include "xfs_bmap_btree.h"
12#include "xfs_attr_sf.h"
938f7b70 13#include "path.h"
fee68490 14#include "libfrog/fsgeom.h"
f31b5e12 15#include "libfrog/bulkstat.h"
c988ea91
CH
16
17#include <fcntl.h>
18#include <errno.h>
c988ea91
CH
19#include <syslog.h>
20#include <signal.h>
21#include <sys/ioctl.h>
22#include <sys/wait.h>
c988ea91
CH
23#include <sys/statvfs.h>
24#include <sys/xattr.h>
0e2fb84a 25#include <paths.h>
c988ea91 26
89e4b5bd
CH
27#define _PATH_FSRLAST "/var/tmp/.fsrlast_xfs"
28#define _PATH_PROC_MOUNTS "/proc/mounts"
29
c988ea91
CH
30
31char *progname;
32
00ff2b10
ES
33static int vflag;
34static int gflag;
c988ea91
CH
35static int Mflag;
36/* static int nflag; */
00ff2b10 37static int dflag = 0;
c988ea91 38/* static int sflag; */
00ff2b10 39static int argv_blksz_dio;
c988ea91
CH
40extern int max_ext_size;
41static int npasses = 10;
42static int startpass = 0;
43
00ff2b10
ES
44static struct getbmap *outmap = NULL;
45static int outmap_size = 0;
46static int RealUid;
47static int tmp_agi;
14f8b681 48static int64_t minimumfree = 2048;
c988ea91
CH
49
50#define MNTTYPE_XFS "xfs"
51
52#define SMBUFSZ 1024
53#define ROOT 0
54#define NULLFD -1
55#define GRABSZ 64
56#define TARGETRANGE 10
c988ea91 57#define BUFFER_MAX (1<<24)
c988ea91
CH
58
59static time_t howlong = 7200; /* default seconds of reorganizing */
60static char *leftofffile = _PATH_FSRLAST; /* where we left off last */
c988ea91
CH
61static time_t endtime;
62static time_t starttime;
63static xfs_ino_t leftoffino = 0;
64static int pagesize;
65
66void usage(int ret);
67static int fsrfile(char *fname, xfs_ino_t ino);
68static int fsrfile_common( char *fname, char *tname, char *mnt,
b46789e2 69 int fd, struct xfs_bstat *statp);
c988ea91 70static int packfile(char *fname, char *tname, int fd,
b46789e2 71 struct xfs_bstat *statp, struct fsxattr *fsxp);
c988ea91
CH
72static void fsrdir(char *dirname);
73static int fsrfs(char *mntdir, xfs_ino_t ino, int targetrange);
74static void initallfs(char *mtab);
89e4b5bd 75static void fsrallfs(char *mtab, int howlong, char *leftofffile);
c988ea91
CH
76static void fsrall_cleanup(int timeout);
77static int getnextents(int);
78int xfsrtextsize(int fd);
09d38d96 79int xfs_getrt(int fd, struct statvfs *sfbp);
c988ea91
CH
80char * gettmpname(char *fname);
81char * getparent(char *fname);
82int fsrprintf(const char *fmt, ...);
b46789e2 83int read_fd_bmap(int, struct xfs_bstat *, int *);
c988ea91
CH
84int cmp(const void *, const void *);
85static void tmp_init(char *mnt);
86static char * tmp_next(char *mnt);
87static void tmp_close(char *mnt);
c988ea91 88
9612817d 89static struct xfs_fsop_geom fsgeom; /* geometry of active mounted system */
c988ea91
CH
90
91#define NMOUNT 64
92static int numfs;
93
94typedef struct fsdesc {
95 char *dev;
96 char *mnt;
97 int npass;
98} fsdesc_t;
99
00ff2b10
ES
100static fsdesc_t *fs, *fsbase, *fsend;
101static int fsbufsize = 10; /* A starting value */
102static int nfrags = 0; /* Debug option: Coerse into specific number
c988ea91 103 * of extents */
00ff2b10 104static int openopts = O_CREAT|O_EXCL|O_RDWR|O_DIRECT;
c988ea91 105
00ff2b10 106static int
c988ea91
CH
107xfs_swapext(int fd, xfs_swapext_t *sx)
108{
109 return ioctl(fd, XFS_IOC_SWAPEXT, sx);
110}
111
00ff2b10 112static int
c988ea91
CH
113xfs_fscounts(int fd, xfs_fsop_counts_t *counts)
114{
115 return ioctl(fd, XFS_IOC_FSCOUNTS, counts);
116}
117
00ff2b10 118static void
c988ea91
CH
119aborter(int unused)
120{
121 fsrall_cleanup(1);
122 exit(1);
123}
124
125int
126main(int argc, char **argv)
127{
f594a0d1 128 struct stat sb;
c988ea91 129 char *argname;
c988ea91 130 int c;
938f7b70 131 struct fs_path *fsp;
89e4b5bd 132 char *mtab = NULL;
c988ea91
CH
133
134 setlinebuf(stdout);
135 progname = basename(argv[0]);
136
137 setlocale(LC_ALL, "");
138 bindtextdomain(PACKAGE, LOCALEDIR);
139 textdomain(PACKAGE);
140
141 gflag = ! isatty(0);
142
89e4b5bd 143 while ((c = getopt(argc, argv, "C:p:e:MgsdnvTt:f:m:b:N:FV")) != -1) {
c988ea91
CH
144 switch (c) {
145 case 'M':
146 Mflag = 1;
147 break;
148 case 'g':
149 gflag = 1;
150 break;
151 case 'n':
152 /* nflag = 1; */
153 break;
154 case 'v':
155 ++vflag;
156 break;
157 case 'd':
158 dflag = 1;
159 break;
160 case 's': /* frag stats only */
161 /* sflag = 1; */
162 fprintf(stderr,
163 _("%s: Stats not yet supported for XFS\n"),
164 progname);
165 usage(1);
166 break;
167 case 't':
168 howlong = atoi(optarg);
169 break;
170 case 'f':
171 leftofffile = optarg;
172 break;
173 case 'm':
174 mtab = optarg;
175 break;
176 case 'b':
177 argv_blksz_dio = atoi(optarg);
178 break;
179 case 'p':
180 npasses = atoi(optarg);
181 break;
182 case 'C':
183 /* Testing opt: coerses frag count in result */
184 if (getenv("FSRXFSTEST") != NULL) {
185 nfrags = atoi(optarg);
186 openopts |= O_SYNC;
187 }
188 break;
189 case 'V':
190 printf(_("%s version %s\n"), progname, VERSION);
191 exit(0);
192 default:
193 usage(1);
194 }
89e4b5bd
CH
195 }
196
197 /*
198 * If the user did not specify an explicit mount table, try to use
199 * /proc/mounts if it is available, else /etc/mtab. We prefer
200 * /proc/mounts because it is kernel controlled, while /etc/mtab
201 * may contain garbage that userspace tools like pam_mounts wrote
202 * into it.
203 */
204 if (!mtab) {
205 if (access(_PATH_PROC_MOUNTS, R_OK) == 0)
206 mtab = _PATH_PROC_MOUNTS;
207 else
208 mtab = _PATH_MOUNTED;
209 }
210
c988ea91
CH
211 if (vflag)
212 setbuf(stdout, NULL);
213
3dd2705a 214 starttime = time(NULL);
c988ea91
CH
215
216 /* Save the caller's real uid */
217 RealUid = getuid();
218
219 pagesize = getpagesize();
938f7b70 220 fs_table_initialise(0, NULL, 0, NULL);
c988ea91
CH
221 if (optind < argc) {
222 for (; optind < argc; optind++) {
223 argname = argv[optind];
3e50d888 224
f594a0d1 225 if (lstat(argname, &sb) < 0) {
c988ea91
CH
226 fprintf(stderr,
227 _("%s: could not stat: %s: %s\n"),
228 progname, argname, strerror(errno));
229 continue;
230 }
3e50d888
CH
231
232 if (S_ISLNK(sb.st_mode)) {
f594a0d1 233 struct stat sb2;
3e50d888 234
f594a0d1 235 if (stat(argname, &sb2) == 0 &&
3e50d888
CH
236 (S_ISBLK(sb2.st_mode) ||
237 S_ISCHR(sb2.st_mode)))
c988ea91 238 sb = sb2;
c988ea91 239 }
3e50d888 240
938f7b70
DW
241 fsp = fs_table_lookup_mount(argname);
242 if (!fsp)
243 fsp = fs_table_lookup_blkdev(argname);
244 if (fsp != NULL) {
245 fsrfs(fsp->fs_dir, 0, 100);
c988ea91
CH
246 } else if (S_ISCHR(sb.st_mode)) {
247 fprintf(stderr, _(
248 "%s: char special not supported: %s\n"),
249 progname, argname);
250 exit(1);
251 } else if (S_ISDIR(sb.st_mode) || S_ISREG(sb.st_mode)) {
252 if (!platform_test_xfs_path(argname)) {
253 fprintf(stderr, _(
254 "%s: cannot defragment: %s: Not XFS\n"),
255 progname, argname);
256 continue;
257 }
258 if (S_ISDIR(sb.st_mode))
259 fsrdir(argname);
260 else
261 fsrfile(argname, sb.st_ino);
262 } else {
263 printf(
264 _("%s: not fsys dev, dir, or reg file, ignoring\n"),
265 argname);
266 }
267 }
268 } else {
269 initallfs(mtab);
89e4b5bd 270 fsrallfs(mtab, howlong, leftofffile);
c988ea91
CH
271 }
272 return 0;
273}
274
275void
276usage(int ret)
277{
278 fprintf(stderr, _(
30626ef6
ES
279"Usage: %s [-d] [-v] [-g] [-t time] [-p passes] [-f leftf] [-m mtab]\n"
280" %s [-d] [-v] [-g] xfsdev | dir | file ...\n"
281" %s -V\n\n"
c988ea91 282"Options:\n"
c988ea91
CH
283" -g Print to syslog (default if stdout not a tty).\n"
284" -t time How long to run in seconds.\n"
30626ef6 285" -p passes Number of passes before terminating global re-org.\n"
c988ea91
CH
286" -f leftoff Use this instead of %s.\n"
287" -m mtab Use something other than /etc/mtab.\n"
288" -d Debug, print even more.\n"
30626ef6
ES
289" -v Verbose, more -v's more verbose.\n"
290" -V Print version number and exit.\n"
291 ), progname, progname, progname, _PATH_FSRLAST);
c988ea91
CH
292 exit(ret);
293}
294
295/*
296 * initallfs -- read the mount table and set up an internal form
297 */
298static void
299initallfs(char *mtab)
300{
7141fc5b 301 struct mntent_cursor cursor;
7849d55d 302 struct mntent *mnt= NULL;
c988ea91
CH
303 int mi;
304 char *cp;
f594a0d1 305 struct stat sb;
c988ea91
CH
306
307 /* malloc a number of descriptors, increased later if needed */
308 if (!(fsbase = (fsdesc_t *)malloc(fsbufsize * sizeof(fsdesc_t)))) {
309 fsrprintf(_("out of memory: %s\n"), strerror(errno));
310 exit(1);
311 }
312 fsend = (fsbase + fsbufsize - 1);
313
314 /* find all rw xfs file systems */
315 mi = 0;
316 fs = fsbase;
7141fc5b
JT
317
318 if (platform_mntent_open(&cursor, mtab) != 0){
319 fprintf(stderr, "Error: can't get mntent entries.\n");
320 exit(1);
321 }
322
7849d55d 323 while ((mnt = platform_mntent_next(&cursor)) != NULL) {
c988ea91
CH
324 int rw = 0;
325
7849d55d 326 if (strcmp(mnt->mnt_type, MNTTYPE_XFS ) != 0 ||
f594a0d1 327 stat(mnt->mnt_fsname, &sb) == -1 ||
c988ea91
CH
328 !S_ISBLK(sb.st_mode))
329 continue;
330
7849d55d 331 cp = strtok(mnt->mnt_opts,",");
c988ea91
CH
332 do {
333 if (strcmp("rw", cp) == 0)
334 rw++;
335 } while ((cp = strtok(NULL, ",")) != NULL);
336 if (rw == 0) {
337 if (dflag)
338 fsrprintf(_("Skipping %s: not mounted rw\n"),
7849d55d 339 mnt->mnt_fsname);
c988ea91
CH
340 continue;
341 }
342
343 if (mi == fsbufsize) {
344 fsbufsize += NMOUNT;
345 if ((fsbase = (fsdesc_t *)realloc((char *)fsbase,
346 fsbufsize * sizeof(fsdesc_t))) == NULL) {
347 fsrprintf(_("out of memory: %s\n"),
348 strerror(errno));
349 exit(1);
350 }
351 if (!fsbase) {
352 fsrprintf(_("out of memory on realloc: %s\n"),
353 strerror(errno));
354 exit(1);
355 }
356 fs = (fsbase + mi); /* Needed ? */
357 }
358
7849d55d
ES
359 fs->dev = strdup(mnt->mnt_fsname);
360 fs->mnt = strdup(mnt->mnt_dir);
c988ea91 361
758bcc92 362 if (fs->dev == NULL) {
7849d55d 363 fsrprintf(_("strdup(%s) failed\n"), mnt->mnt_fsname);
c988ea91
CH
364 exit(1);
365 }
758bcc92 366 if (fs->mnt == NULL) {
7849d55d 367 fsrprintf(_("strdup(%s) failed\n"), mnt->mnt_dir);
758bcc92
ES
368 exit(1);
369 }
c988ea91
CH
370 mi++;
371 fs++;
372 }
7141fc5b
JT
373 platform_mntent_close(&cursor);
374
c988ea91
CH
375 numfs = mi;
376 fsend = (fsbase + numfs);
c988ea91
CH
377 if (numfs == 0) {
378 fsrprintf(_("no rw xfs file systems in mtab: %s\n"), mtab);
379 exit(0);
380 }
381 if (vflag || dflag) {
382 fsrprintf(_("Found %d mounted, writable, XFS filesystems\n"),
383 numfs);
384 if (dflag)
385 for (fs = fsbase; fs < fsend; fs++)
386 fsrprintf("\t%-30.30s%-30.30s\n", fs->dev, fs->mnt);
387 }
388}
389
390static void
89e4b5bd 391fsrallfs(char *mtab, int howlong, char *leftofffile)
c988ea91
CH
392{
393 int fd;
394 int error;
395 int found = 0;
396 char *fsname;
397 char buf[SMBUFSZ];
398 int mdonly = Mflag;
399 char *ptr;
400 xfs_ino_t startino = 0;
401 fsdesc_t *fsp;
f594a0d1 402 struct stat sb, sb2;
c988ea91
CH
403
404 fsrprintf("xfs_fsr -m %s -t %d -f %s ...\n", mtab, howlong, leftofffile);
405
406 endtime = starttime + howlong;
407 fs = fsbase;
408
409 /* where'd we leave off last time? */
f594a0d1 410 if (lstat(leftofffile, &sb) == 0) {
c988ea91
CH
411 if ( (fd = open(leftofffile, O_RDONLY)) == -1 ) {
412 fsrprintf(_("%s: open failed\n"), leftofffile);
413 }
f594a0d1 414 else if ( fstat(fd, &sb2) == 0) {
c988ea91
CH
415 /*
416 * Verify that lstat & fstat point to the
417 * same regular file (no links/no quick spoofs)
418 */
419 if ( (sb.st_dev != sb2.st_dev) ||
420 (sb.st_ino != sb2.st_ino) ||
421 ((sb.st_mode & S_IFMT) != S_IFREG) ||
422 ((sb2.st_mode & S_IFMT) != S_IFREG) ||
423 (sb2.st_uid != ROOT) ||
424 (sb2.st_nlink != 1)
425 )
426 {
427 fsrprintf(_("Can't use %s: mode=0%o own=%d"
428 " nlink=%d\n"),
429 leftofffile, sb.st_mode,
430 sb.st_uid, sb.st_nlink);
431 close(fd);
432 fd = NULLFD;
433 }
434 }
435 else {
436 close(fd);
437 fd = NULLFD;
438 }
439 }
440 else {
441 fd = NULLFD;
442 }
443
444 if (fd != NULLFD) {
445 if (read(fd, buf, SMBUFSZ) == -1) {
446 fs = fsbase;
447 fsrprintf(_("could not read %s, starting with %s\n"),
448 leftofffile, *fs->dev);
449 } else {
eef20df0
ES
450 /* Ensure the buffer we read is null terminated */
451 buf[SMBUFSZ-1] = '\0';
c988ea91
CH
452 for (fs = fsbase; fs < fsend; fs++) {
453 fsname = fs->dev;
454 if ((strncmp(buf,fsname,strlen(fsname)) == 0)
455 && buf[strlen(fsname)] == ' ') {
456 found = 1;
457 break;
458 }
459 }
460 if (! found)
461 fs = fsbase;
462
463 ptr = strchr(buf, ' ');
464 if (ptr) {
465 startpass = atoi(++ptr);
466 ptr = strchr(ptr, ' ');
467 if (ptr) {
468 startino = strtoull(++ptr, NULL, 10);
469 }
470 }
471 if (startpass < 0)
472 startpass = 0;
473
474 /* Init pass counts */
475 for (fsp = fsbase; fsp < fs; fsp++) {
476 fsp->npass = startpass + 1;
477 }
478 for (fsp = fs; fsp <= fsend; fsp++) {
479 fsp->npass = startpass;
480 }
481 }
482 close(fd);
483 }
484
485 if (vflag) {
486 fsrprintf(_("START: pass=%d ino=%llu %s %s\n"),
487 fs->npass, (unsigned long long)startino,
488 fs->dev, fs->mnt);
489 }
490
491 signal(SIGABRT, aborter);
492 signal(SIGHUP, aborter);
493 signal(SIGINT, aborter);
494 signal(SIGQUIT, aborter);
495 signal(SIGTERM, aborter);
496
497 /* reorg for 'howlong' -- checked in 'fsrfs' */
3dd2705a 498 while (endtime > time(NULL)) {
c988ea91 499 pid_t pid;
8d2666e3 500
c988ea91
CH
501 if (npasses > 1 && !fs->npass)
502 Mflag = 1;
503 else
504 Mflag = mdonly;
505 pid = fork();
506 switch(pid) {
507 case -1:
508 fsrprintf(_("couldn't fork sub process:"));
509 exit(1);
510 break;
511 case 0:
512 error = fsrfs(fs->mnt, startino, TARGETRANGE);
513 exit (error);
514 break;
515 default:
516 wait(&error);
c988ea91
CH
517 if (WIFEXITED(error) && WEXITSTATUS(error) == 1) {
518 /* child timed out & did fsrall_cleanup */
519 exit(0);
520 }
521 break;
522 }
523 startino = 0; /* reset after the first time through */
524 fs->npass++;
525 fs++;
8d2666e3
JM
526 if (fs == fsend)
527 fs = fsbase;
528 if (fs->npass == npasses) {
529 fsrprintf(_("Completed all %d passes\n"), npasses);
530 break;
531 }
c988ea91 532 }
3dd2705a 533 fsrall_cleanup(endtime <= time(NULL));
c988ea91
CH
534}
535
536/*
537 * fsrall_cleanup -- close files, print next starting location, etc.
538 */
539static void
540fsrall_cleanup(int timeout)
541{
542 int fd;
543 int ret;
544 char buf[SMBUFSZ];
545
c988ea91 546 unlink(leftofffile);
d0e82db1
ES
547
548 if (timeout) {
549 fsrprintf(_("%s startpass %d, endpass %d, time %d seconds\n"),
550 progname, startpass, fs->npass,
3dd2705a 551 time(NULL) - endtime + howlong);
d0e82db1
ES
552
553 /* record where we left off */
554 fd = open(leftofffile, O_WRONLY|O_CREAT|O_EXCL, 0644);
555 if (fd == -1) {
556 fsrprintf(_("open(%s) failed: %s\n"),
557 leftofffile, strerror(errno));
558 } else {
c988ea91
CH
559 ret = sprintf(buf, "%s %d %llu\n", fs->dev,
560 fs->npass, (unsigned long long)leftoffino);
561 if (write(fd, buf, ret) < strlen(buf))
562 fsrprintf(_("write(%s) failed: %s\n"),
563 leftofffile, strerror(errno));
564 close(fd);
565 }
566 }
c988ea91
CH
567}
568
569/*
570 * fsrfs -- reorganize a file system
571 */
572static int
573fsrfs(char *mntdir, xfs_ino_t startino, int targetrange)
574{
f31b5e12
DW
575 struct xfs_fd fsxfd = XFS_FD_INIT_EMPTY;
576 int fd;
c988ea91
CH
577 int count = 0;
578 int ret;
f31b5e12 579 uint32_t buflenout;
b46789e2 580 struct xfs_bstat buf[GRABSZ];
c988ea91
CH
581 char fname[64];
582 char *tname;
583 jdm_fshandle_t *fshandlep;
584 xfs_ino_t lastino = startino;
585
586 fsrprintf(_("%s start inode=%llu\n"), mntdir,
587 (unsigned long long)startino);
588
589 fshandlep = jdm_getfshandle( mntdir );
590 if ( ! fshandlep ) {
591 fsrprintf(_("unable to get handle: %s: %s\n"),
592 mntdir, strerror( errno ));
593 return -1;
594 }
595
248af7cb 596 ret = xfd_open(&fsxfd, mntdir, O_RDONLY);
9612817d 597 if (ret) {
248af7cb
DW
598 fsrprintf(_("unable to open XFS file: %s: %s\n"),
599 mntdir, strerror(ret));
e3e2793d 600 free(fshandlep);
c988ea91
CH
601 return -1;
602 }
f31b5e12 603 memcpy(&fsgeom, &fsxfd.fsgeom, sizeof(fsgeom));
c988ea91
CH
604
605 tmp_init(mntdir);
606
f31b5e12
DW
607 while ((ret = xfrog_bulkstat(&fsxfd, &lastino, GRABSZ, &buf[0],
608 &buflenout)) == 0) {
b46789e2
DW
609 struct xfs_bstat *p;
610 struct xfs_bstat *endp;
c988ea91
CH
611
612 if (buflenout == 0)
613 goto out0;
614
615 /* Each loop through, defrag targetrange percent of the files */
616 count = (buflenout * targetrange) / 100;
617
618 qsort((char *)buf, buflenout, sizeof(struct xfs_bstat), cmp);
619
620 for (p = buf, endp = (buf + buflenout); p < endp ; p++) {
621 /* Do some obvious checks now */
622 if (((p->bs_mode & S_IFMT) != S_IFREG) ||
623 (p->bs_extents < 2))
624 continue;
625
108e985b
DC
626 fd = jdm_open(fshandlep, p, O_RDWR|O_DIRECT);
627 if (fd < 0) {
c988ea91
CH
628 /* This probably means the file was
629 * removed while in progress of handling
630 * it. Just quietly ignore this file.
631 */
632 if (dflag)
633 fsrprintf(_("could not open: "
634 "inode %llu\n"), p->bs_ino);
635 continue;
636 }
637
638 /* Don't know the pathname, so make up something */
639 sprintf(fname, "ino=%lld", (long long)p->bs_ino);
640
641 /* Get a tmp file name */
642 tname = tmp_next(mntdir);
643
644 ret = fsrfile_common(fname, tname, mntdir, fd, p);
645
646 leftoffino = p->bs_ino;
647
648 close(fd);
649
650 if (ret == 0) {
651 if (--count <= 0)
652 break;
653 }
654 }
3dd2705a 655 if (endtime && endtime < time(NULL)) {
c988ea91 656 tmp_close(mntdir);
f31b5e12 657 xfd_close(&fsxfd);
c988ea91
CH
658 fsrall_cleanup(1);
659 exit(1);
660 }
661 }
f31b5e12
DW
662 if (ret)
663 fsrprintf(_("%s: bulkstat: %s\n"), progname, strerror(ret));
c988ea91
CH
664out0:
665 tmp_close(mntdir);
f31b5e12 666 xfd_close(&fsxfd);
e3e2793d 667 free(fshandlep);
c988ea91
CH
668 return 0;
669}
670
671/*
672 * To compare bstat structs for qsort.
673 */
674int
675cmp(const void *s1, const void *s2)
676{
b46789e2
DW
677 return( ((struct xfs_bstat *)s2)->bs_extents -
678 ((struct xfs_bstat *)s1)->bs_extents);
c988ea91
CH
679
680}
681
682/*
683 * reorganize by directory hierarchy.
684 * Stay in dev (a restriction based on structure of this program -- either
685 * call efs_{n,u}mount() around each file, something smarter or this)
686 */
687static void
688fsrdir(char *dirname)
689{
690 fsrprintf(_("%s: Directory defragmentation not supported\n"), dirname);
691}
692
693/*
694 * Sets up the defragmentation of a file based on the
695 * filepath. It collects the bstat information, does
696 * an open on the file and passes this all to fsrfile_common.
697 */
698static int
f31b5e12
DW
699fsrfile(
700 char *fname,
701 xfs_ino_t ino)
c988ea91 702{
f31b5e12
DW
703 struct xfs_fd fsxfd = XFS_FD_INIT_EMPTY;
704 struct xfs_bstat statbuf;
705 jdm_fshandle_t *fshandlep;
706 int fd = -1;
707 int error = -1;
708 char *tname;
c988ea91
CH
709
710 fshandlep = jdm_getfshandle(getparent (fname) );
4f10a2fb 711 if (!fshandlep) {
c988ea91
CH
712 fsrprintf(_("unable to construct sys handle for %s: %s\n"),
713 fname, strerror(errno));
4f10a2fb 714 goto out;
c988ea91
CH
715 }
716
717 /*
718 * Need to open something on the same filesystem as the
719 * file. Open the parent.
720 */
248af7cb 721 error = xfd_open(&fsxfd, getparent(fname), O_RDONLY);
f31b5e12 722 if (error) {
248af7cb
DW
723 fsrprintf(_("unable to open sys handle for XFS file %s: %s\n"),
724 fname, strerror(error));
f31b5e12
DW
725 goto out;
726 }
727
728 error = xfrog_bulkstat_single(&fsxfd, ino, &statbuf);
729 if (error) {
c988ea91 730 fsrprintf(_("unable to get bstat on %s: %s\n"),
f31b5e12 731 fname, strerror(error));
4f10a2fb 732 goto out;
c988ea91
CH
733 }
734
108e985b 735 fd = jdm_open(fshandlep, &statbuf, O_RDWR|O_DIRECT);
c988ea91
CH
736 if (fd < 0) {
737 fsrprintf(_("unable to open handle %s: %s\n"),
738 fname, strerror(errno));
4f10a2fb 739 goto out;
c988ea91
CH
740 }
741
f31b5e12
DW
742 /* Stash the fs geometry for general use. */
743 memcpy(&fsgeom, &fsxfd.fsgeom, sizeof(fsgeom));
c988ea91 744
c988ea91
CH
745 tname = gettmpname(fname);
746
747 if (tname)
748 error = fsrfile_common(fname, tname, NULL, fd, &statbuf);
749
4f10a2fb 750out:
f31b5e12 751 xfd_close(&fsxfd);
4f10a2fb
ES
752 if (fd >= 0)
753 close(fd);
754 free(fshandlep);
c988ea91
CH
755
756 return error;
757}
758
759
760/*
761 * This is the common defrag code for either a full fs
762 * defragmentation or a single file. Check as much as
763 * possible with the file, fork a process to setuid to the
764 * target file owner's uid and defragment the file.
765 * This is done so the new extents created in a tmp file are
766 * reflected in the owners' quota without having to do any
767 * special code in the kernel. When the existing extents
768 * are removed, the quotas will be correct. It's ugly but
769 * it saves us from doing some quota re-construction in
770 * the extent swap. The price is that the defragmentation
771 * will fail if the owner of the target file is already at
772 * their quota limit.
773 */
774static int
775fsrfile_common(
776 char *fname,
777 char *tname,
778 char *fsname,
779 int fd,
b46789e2 780 struct xfs_bstat *statp)
c988ea91
CH
781{
782 int error;
09d38d96 783 struct statvfs vfss;
c988ea91
CH
784 struct fsxattr fsx;
785 unsigned long bsize;
786
787 if (vflag)
788 fsrprintf("%s\n", fname);
789
790 if (fsync(fd) < 0) {
791 fsrprintf(_("sync failed: %s: %s\n"), fname, strerror(errno));
792 return -1;
793 }
794
795 if (statp->bs_size == 0) {
796 if (vflag)
797 fsrprintf(_("%s: zero size, ignoring\n"), fname);
798 return(0);
799 }
800
801 /* Check if a mandatory lock is set on the file to try and
802 * avoid blocking indefinitely on the reads later. Note that
803 * someone could still set a mandatory lock after this check
804 * but before all reads have completed to block fsr reads.
805 * This change just closes the window a bit.
806 */
807 if ( (statp->bs_mode & S_ISGID) && ( ! (statp->bs_mode&S_IXGRP) ) ) {
808 struct flock fl;
809
810 fl.l_type = F_RDLCK;
811 fl.l_whence = SEEK_SET;
812 fl.l_start = (off_t)0;
813 fl.l_len = 0;
814 if ((fcntl(fd, F_GETLK, &fl)) < 0 ) {
815 if (vflag)
816 fsrprintf(_("locking check failed: %s\n"),
817 fname);
818 return(-1);
819 }
820 if (fl.l_type != F_UNLCK) {
821 /* Mandatory lock is set */
822 if (vflag)
823 fsrprintf(_("mandatory lock: %s: ignoring\n"),
824 fname);
825 return(-1);
826 }
827 }
828
829 /*
830 * Check if there is room to copy the file.
831 *
832 * Note that xfs_bstat.bs_blksize returns the filesystem blocksize,
833 * not the optimal I/O size as struct stat.
834 */
09d38d96 835 if (statvfs(fsname ? fsname : fname, &vfss) < 0) {
c988ea91
CH
836 fsrprintf(_("unable to get fs stat on %s: %s\n"),
837 fname, strerror(errno));
838 return -1;
839 }
840 bsize = vfss.f_frsize ? vfss.f_frsize : vfss.f_bsize;
841 if (statp->bs_blksize * statp->bs_blocks >
842 vfss.f_bfree * bsize - minimumfree) {
843 fsrprintf(_("insufficient freespace for: %s: "
844 "size=%lld: ignoring\n"), fname,
845 statp->bs_blksize * statp->bs_blocks);
846 return 1;
847 }
848
83f4b5ac 849 if ((ioctl(fd, FS_IOC_FSGETXATTR, &fsx)) < 0) {
c988ea91
CH
850 fsrprintf(_("failed to get inode attrs: %s\n"), fname);
851 return(-1);
852 }
83f4b5ac 853 if (fsx.fsx_xflags & (FS_XFLAG_IMMUTABLE|FS_XFLAG_APPEND)) {
c988ea91
CH
854 if (vflag)
855 fsrprintf(_("%s: immutable/append, ignoring\n"), fname);
856 return(0);
857 }
83f4b5ac 858 if (fsx.fsx_xflags & FS_XFLAG_NODEFRAG) {
c988ea91
CH
859 if (vflag)
860 fsrprintf(_("%s: marked as don't defrag, ignoring\n"),
861 fname);
862 return(0);
863 }
83f4b5ac 864 if (fsx.fsx_xflags & FS_XFLAG_REALTIME) {
c988ea91
CH
865 if (xfs_getrt(fd, &vfss) < 0) {
866 fsrprintf(_("cannot get realtime geometry for: %s\n"),
867 fname);
868 return(-1);
869 }
870 if (statp->bs_size > ((vfss.f_bfree * bsize) - minimumfree)) {
871 fsrprintf(_("low on realtime free space: %s: "
872 "ignoring file\n"), fname);
873 return(-1);
874 }
875 }
876
877 if ((RealUid != ROOT) && (RealUid != statp->bs_uid)) {
878 fsrprintf(_("cannot open: %s: Permission denied\n"), fname);
879 return -1;
880 }
881
882 /*
883 * Previously the code forked here, & the child changed it's uid to
884 * that of the file's owner and then called packfile(), to keep
885 * quota counts correct. (defragged files could use fewer blocks).
886 *
887 * Instead, just fchown() the temp file to the uid,gid of the
888 * file we're defragging, in packfile().
889 */
890
891 if ((error = packfile(fname, tname, fd, statp, &fsx)))
892 return error;
893 return -1; /* no error */
894}
895
bdb041f5
DC
896/*
897 * Attempt to set the attr fork up correctly. This is simple for attr1
898 * filesystems as they have a fixed inode fork offset. In that case
899 * just create an attribute and that's all we need to do.
900 *
901 * For attr2 filesystems, see if we have the actual fork offset in
902 * the bstat structure. If so, just create additional attributes on
903 * the temporary inode until the offset matches.
904 *
905 * If it doesn't exist, we can only do best effort. Add an attribute at a time
906 * to move the inode fork around, but take into account that the attribute
907 * might be too small to move the fork every time we add one. This should
908 * hopefully put the fork offset in the right place. It's not a big deal if we
909 * don't get it right - the kernel will reject it when we try to swap extents.
910 */
911static int
912fsr_setup_attr_fork(
913 int fd,
914 int tfd,
b46789e2 915 struct xfs_bstat *bstatp)
bdb041f5 916{
c14c7b79 917#ifdef HAVE_FSETXATTR
f31b5e12 918 struct xfs_fd txfd = XFS_FD_INIT(tfd);
f594a0d1 919 struct stat tstatbuf;
bdb041f5 920 int i;
27507775 921 int diff = 0;
bdb041f5
DC
922 int last_forkoff = 0;
923 int no_change_cnt = 0;
924 int ret;
925
83f4b5ac 926 if (!(bstatp->bs_xflags & FS_XFLAG_HASATTR))
bdb041f5
DC
927 return 0;
928
929 /*
930 * use the old method if we have attr1 or the kernel does not yet
931 * support passing the fork offset in the bulkstat data.
932 */
933 if (!(fsgeom.flags & XFS_FSOP_GEOM_FLAGS_ATTR2) ||
934 bstatp->bs_forkoff == 0) {
935 /* attr1 */
f31b5e12 936 ret = fsetxattr(txfd.fd, "user.X", "X", 1, XATTR_CREATE);
bdb041f5
DC
937 if (ret) {
938 fsrprintf(_("could not set ATTR\n"));
939 return -1;
940 }
941 goto out;
942 }
943
944 /* attr2 w/ fork offsets */
945
f31b5e12 946 if (fstat(txfd.fd, &tstatbuf) < 0) {
bdb041f5
DC
947 fsrprintf(_("unable to stat temp file: %s\n"),
948 strerror(errno));
949 return -1;
950 }
951
952 i = 0;
953 do {
b46789e2 954 struct xfs_bstat tbstat;
bdb041f5 955 char name[64];
f31b5e12 956 int ret;
bdb041f5
DC
957
958 /*
1adfe5c6 959 * bulkstat the temp inode to see what the forkoff is. Use
bdb041f5
DC
960 * this to compare against the target and determine what we
961 * need to do.
962 */
f31b5e12
DW
963 ret = xfrog_bulkstat_single(&txfd, tstatbuf.st_ino, &tbstat);
964 if (ret) {
bdb041f5 965 fsrprintf(_("unable to get bstat on temp file: %s\n"),
f31b5e12 966 strerror(ret));
bdb041f5
DC
967 return -1;
968 }
969 if (dflag)
970 fsrprintf(_("orig forkoff %d, temp forkoff %d\n"),
971 bstatp->bs_forkoff, tbstat.bs_forkoff);
1adfe5c6
ES
972 diff = tbstat.bs_forkoff - bstatp->bs_forkoff;
973
974 /* if they are equal, we are done */
975 if (!diff)
976 goto out;
bdb041f5
DC
977
978 snprintf(name, sizeof(name), "user.%d", i);
979
980 /*
981 * If there is no attribute, then we need to create one to get
982 * an attribute fork at the default location.
983 */
984 if (!tbstat.bs_forkoff) {
1adfe5c6 985 ASSERT(i == 0);
f31b5e12 986 ret = fsetxattr(txfd.fd, name, "XX", 2, XATTR_CREATE);
bdb041f5
DC
987 if (ret) {
988 fsrprintf(_("could not set ATTR\n"));
989 return -1;
990 }
991 continue;
1adfe5c6 992 } else if (i == 0) {
1adfe5c6
ES
993 /*
994 * First pass, and temp file already has an inline
995 * xattr, probably due to selinux.
996 *
997 * It's *possible* that the temp file attr area
e7e3152c 998 * is larger than the target file's:
1adfe5c6
ES
999 *
1000 * Target Temp
1001 * +-------+ 0 +-------+ 0
1002 * | | | |
1003 * | | | Data |
1004 * | Data | | |
1005 * | | v-------v forkoff
1006 * | | | |
1007 * v-------v forkoff | Attr | local
e7e3152c 1008 * | Attr | | |
1adfe5c6 1009 * +-------+ +-------+
1adfe5c6
ES
1010 */
1011
1adfe5c6 1012 /*
e7e3152c
ES
1013 * If target attr area is less than the temp's
1014 * (diff < 0) write a big attr to the temp file to knock
1015 * the attr out of local format.
1016 * (This should actually *increase* the temp file's
1017 * forkoffset when the attr moves out of the inode)
1adfe5c6 1018 */
e7e3152c 1019 if (diff < 0) {
1adfe5c6
ES
1020 char val[2048];
1021 memset(val, 'X', 2048);
f31b5e12 1022 if (fsetxattr(txfd.fd, name, val, 2048, 0)) {
1adfe5c6
ES
1023 fsrprintf(_("big ATTR set failed\n"));
1024 return -1;
1025 }
1026 /* Go back & see where we're at now */
1027 continue;
1028 }
bdb041f5
DC
1029 }
1030
1031 /*
1032 * make a progress check so we don't get stuck trying to extend
1033 * a large btree form attribute fork.
1034 */
1035 if (last_forkoff == tbstat.bs_forkoff) {
1036 if (no_change_cnt++ > 10)
1037 break;
ff85ea3f
ES
1038 } else /* progress! */
1039 no_change_cnt = 0;
bdb041f5
DC
1040 last_forkoff = tbstat.bs_forkoff;
1041
1042 /* work out which way to grow the fork */
bdb041f5
DC
1043 if (abs(diff) > fsgeom.inodesize - sizeof(struct xfs_dinode)) {
1044 fsrprintf(_("forkoff diff %d too large!\n"), diff);
1045 return -1;
1046 }
1047
bdb041f5 1048 /*
1adfe5c6
ES
1049 * if the temp inode fork offset is still smaller then we have
1050 * to grow the data fork
bdb041f5
DC
1051 */
1052 if (diff < 0) {
1053 /*
1054 * create some temporary extents in the inode to move
1055 * the fork in the direction we need. This can be done
1056 * by preallocating some single block extents at
1057 * non-contiguous offsets.
1058 */
1059 /* XXX: unimplemented! */
27507775
ES
1060 if (dflag)
1061 printf(_("data fork growth unimplemented\n"));
bdb041f5
DC
1062 goto out;
1063 }
1064
1065 /* we need to grow the attr fork, so create another attr */
f31b5e12 1066 ret = fsetxattr(txfd.fd, name, "XX", 2, XATTR_CREATE);
bdb041f5
DC
1067 if (ret) {
1068 fsrprintf(_("could not set ATTR\n"));
1069 return -1;
1070 }
1071
1072 } while (++i < 100); /* don't go forever */
1073
1074out:
1075 if (dflag)
1076 fsrprintf(_("set temp attr\n"));
27507775
ES
1077 /* We failed to resolve the fork difference */
1078 if (dflag && diff)
1079 fsrprintf(_("failed to match fork offset\n"));;
1080
c14c7b79 1081#endif /* HAVE_FSETXATTR */
bdb041f5
DC
1082 return 0;
1083}
c988ea91
CH
1084
1085/*
1086 * Do the defragmentation of a single file.
1087 * We already are pretty sure we can and want to
1088 * defragment the file. Create the tmp file, copy
1089 * the data (maintaining holes) and call the kernel
671632c6
ES
1090 * extent swap routine.
1091 *
1092 * Return values:
1093 * -1: Some error was encountered
1094 * 0: Successfully defragmented the file
1095 * 1: No change / No Error
c988ea91
CH
1096 */
1097static int
1098packfile(char *fname, char *tname, int fd,
b46789e2 1099 struct xfs_bstat *statp, struct fsxattr *fsxp)
c988ea91 1100{
671632c6 1101 int tfd = -1;
c988ea91 1102 int srval;
671632c6 1103 int retval = -1; /* Failure is the default */
c988ea91
CH
1104 int nextents, extent, cur_nextents, new_nextents;
1105 unsigned blksz_dio;
1106 unsigned dio_min;
1107 struct dioattr dio;
1108 static xfs_swapext_t sx;
1109 struct xfs_flock64 space;
1110 off64_t cnt, pos;
671632c6 1111 void *fbuf = NULL;
c988ea91
CH
1112 int ct, wc, wc_b4;
1113 char ffname[SMBUFSZ];
1114 int ffd = -1;
1115
1116 /*
1117 * Work out the extent map - nextents will be set to the
1118 * minimum number of extents needed for the file (taking
1119 * into account holes), cur_nextents is the current number
1120 * of extents.
1121 */
1122 nextents = read_fd_bmap(fd, statp, &cur_nextents);
1123
1124 if (cur_nextents == 1 || cur_nextents <= nextents) {
1125 if (vflag)
1126 fsrprintf(_("%s already fully defragmented.\n"), fname);
671632c6
ES
1127 retval = 1; /* indicates no change/no error */
1128 goto out;
c988ea91
CH
1129 }
1130
1131 if (dflag)
1132 fsrprintf(_("%s extents=%d can_save=%d tmp=%s\n"),
1133 fname, cur_nextents, (cur_nextents - nextents),
1134 tname);
1135
1136 if ((tfd = open(tname, openopts, 0666)) < 0) {
1137 if (vflag)
1138 fsrprintf(_("could not open tmp file: %s: %s\n"),
1139 tname, strerror(errno));
671632c6 1140 goto out;
c988ea91
CH
1141 }
1142 unlink(tname);
1143
1144 /* Setup extended attributes */
bdb041f5
DC
1145 if (fsr_setup_attr_fork(fd, tfd, statp) != 0) {
1146 fsrprintf(_("failed to set ATTR fork on tmp: %s:\n"), tname);
671632c6 1147 goto out;
c988ea91
CH
1148 }
1149
1150 /* Setup extended inode flags, project identifier, etc */
1151 if (fsxp->fsx_xflags || fsxp->fsx_projid) {
83f4b5ac 1152 if (ioctl(tfd, FS_IOC_FSSETXATTR, fsxp) < 0) {
c988ea91
CH
1153 fsrprintf(_("could not set inode attrs on tmp: %s\n"),
1154 tname);
671632c6 1155 goto out;
c988ea91
CH
1156 }
1157 }
1158
1159 if ((ioctl(tfd, XFS_IOC_DIOINFO, &dio)) < 0 ) {
1160 fsrprintf(_("could not get DirectIO info on tmp: %s\n"), tname);
671632c6 1161 goto out;
c988ea91
CH
1162 }
1163
1164 dio_min = dio.d_miniosz;
1165 if (statp->bs_size <= dio_min) {
1166 blksz_dio = dio_min;
1167 } else {
1168 blksz_dio = min(dio.d_maxiosz, BUFFER_MAX - pagesize);
1169 if (argv_blksz_dio != 0)
1170 blksz_dio = min(argv_blksz_dio, blksz_dio);
1171 blksz_dio = (min(statp->bs_size, blksz_dio) / dio_min) * dio_min;
1172 }
1173
1174 if (dflag) {
1175 fsrprintf(_("DEBUG: "
1176 "fsize=%lld blsz_dio=%d d_min=%d d_max=%d pgsz=%d\n"),
1177 statp->bs_size, blksz_dio, dio.d_miniosz,
1178 dio.d_maxiosz, pagesize);
1179 }
1180
1181 if (!(fbuf = (char *)memalign(dio.d_mem, blksz_dio))) {
1182 fsrprintf(_("could not allocate buf: %s\n"), tname);
671632c6 1183 goto out;
c988ea91
CH
1184 }
1185
1186 if (nfrags) {
1187 /* Create new tmp file in same AG as first */
1188 sprintf(ffname, "%s.frag", tname);
1189
1190 /* Open the new file for sync writes */
1191 if ((ffd = open(ffname, openopts, 0666)) < 0) {
1192 fsrprintf(_("could not open fragfile: %s : %s\n"),
1193 ffname, strerror(errno));
671632c6 1194 goto out;
c988ea91
CH
1195 }
1196 unlink(ffname);
1197 }
1198
1199 /* Loop through block map allocating new extents */
1200 for (extent = 0; extent < nextents; extent++) {
1201 pos = outmap[extent].bmv_offset;
1202 if (outmap[extent].bmv_block == -1) {
1203 space.l_whence = SEEK_SET;
1204 space.l_start = pos;
1205 space.l_len = outmap[extent].bmv_length;
1206 if (ioctl(tfd, XFS_IOC_UNRESVSP64, &space) < 0) {
1207 fsrprintf(_("could not trunc tmp %s\n"),
1208 tname);
1209 }
dc8878f4 1210 if (lseek(tfd, outmap[extent].bmv_length, SEEK_CUR) < 0) {
3d303baa
ES
1211 fsrprintf(_("could not lseek in tmpfile: %s : %s\n"),
1212 tname, strerror(errno));
1213 goto out;
1214 }
c988ea91
CH
1215 continue;
1216 } else if (outmap[extent].bmv_length == 0) {
1217 /* to catch holes at the beginning of the file */
1218 continue;
1219 }
1220 if (! nfrags) {
1221 space.l_whence = SEEK_CUR;
1222 space.l_start = 0;
1223 space.l_len = outmap[extent].bmv_length;
1224
1225 if (ioctl(tfd, XFS_IOC_RESVSP64, &space) < 0) {
1226 fsrprintf(_("could not pre-allocate tmp space:"
1227 " %s\n"), tname);
671632c6 1228 goto out;
c988ea91 1229 }
dc8878f4 1230 if (lseek(tfd, outmap[extent].bmv_length, SEEK_CUR) < 0) {
3d303baa
ES
1231 fsrprintf(_("could not lseek in tmpfile: %s : %s\n"),
1232 tname, strerror(errno));
1233 goto out;
1234 }
c988ea91
CH
1235 }
1236 } /* end of space allocation loop */
1237
dc8878f4 1238 if (lseek(tfd, 0, SEEK_SET)) {
c988ea91 1239 fsrprintf(_("Couldn't rewind on temporary file\n"));
671632c6 1240 goto out;
c988ea91
CH
1241 }
1242
1243 /* Check if the temporary file has fewer extents */
1244 new_nextents = getnextents(tfd);
1245 if (dflag)
1246 fsrprintf(_("Temporary file has %d extents (%d in original)\n"), new_nextents, cur_nextents);
1247 if (cur_nextents <= new_nextents) {
1248 if (vflag)
1249 fsrprintf(_("No improvement will be made (skipping): %s\n"), fname);
671632c6
ES
1250 retval = 1; /* no change/no error */
1251 goto out;
c988ea91
CH
1252 }
1253
1254 /* Loop through block map copying the file. */
1255 for (extent = 0; extent < nextents; extent++) {
1256 pos = outmap[extent].bmv_offset;
1257 if (outmap[extent].bmv_block == -1) {
dc8878f4 1258 if (lseek(tfd, outmap[extent].bmv_length, SEEK_CUR) < 0) {
3d303baa
ES
1259 fsrprintf(_("could not lseek in tmpfile: %s : %s\n"),
1260 tname, strerror(errno));
1261 goto out;
1262 }
dc8878f4 1263 if (lseek(fd, outmap[extent].bmv_length, SEEK_CUR) < 0) {
3d303baa
ES
1264 fsrprintf(_("could not lseek in file: %s : %s\n"),
1265 fname, strerror(errno));
1266 goto out;
1267 }
c988ea91
CH
1268 continue;
1269 } else if (outmap[extent].bmv_length == 0) {
1270 /* to catch holes at the beginning of the file */
1271 continue;
1272 }
1273 for (cnt = outmap[extent].bmv_length; cnt > 0;
1274 cnt -= ct, pos += ct) {
1275 if (nfrags && --nfrags) {
1276 ct = min(cnt, dio_min);
1277 } else if (cnt % dio_min == 0) {
1278 ct = min(cnt, blksz_dio);
1279 } else {
1280 ct = min(cnt + dio_min - (cnt % dio_min),
1281 blksz_dio);
1282 }
1283 ct = read(fd, fbuf, ct);
1284 if (ct == 0) {
1285 /* EOF, stop trying to read */
1286 extent = nextents;
1287 break;
1288 }
1289 /* Ensure we do direct I/O to correct block
1290 * boundaries.
1291 */
1292 if (ct % dio_min != 0) {
1293 wc = ct + dio_min - (ct % dio_min);
1294 } else {
1295 wc = ct;
1296 }
1297 wc_b4 = wc;
1298 if (ct < 0 || ((wc = write(tfd, fbuf, wc)) != wc_b4)) {
1299 if (ct < 0)
1300 fsrprintf(_("bad read of %d bytes "
1301 "from %s: %s\n"), wc_b4,
1302 fname, strerror(errno));
1303 else if (wc < 0)
1304 fsrprintf(_("bad write of %d bytes "
1305 "to %s: %s\n"), wc_b4,
1306 tname, strerror(errno));
1307 else {
1308 /*
1309 * Might be out of space
1310 *
1311 * Try to finish write
1312 */
1313 int resid = ct-wc;
1314
1315 if ((wc = write(tfd, ((char *)fbuf)+wc,
1316 resid)) == resid) {
1317 /* worked on second attempt? */
1318 continue;
1319 }
1320 else if (wc < 0) {
1321 fsrprintf(_("bad write2 of %d "
1322 "bytes to %s: %s\n"),
1323 resid, tname,
1324 strerror(errno));
1325 } else {
1326 fsrprintf(_("bad copy to %s\n"),
1327 tname);
1328 }
1329 }
671632c6 1330 goto out;
c988ea91
CH
1331 }
1332 if (nfrags) {
1333 /* Do a matching write to the tmp file */
431ec4e6 1334 wc_b4 = wc;
c988ea91
CH
1335 if (((wc = write(ffd, fbuf, wc)) != wc_b4)) {
1336 fsrprintf(_("bad write of %d bytes "
1337 "to %s: %s\n"),
1338 wc_b4, ffname, strerror(errno));
1339 }
1340 }
1341 }
1342 }
dde67673 1343 if (ftruncate(tfd, statp->bs_size) < 0) {
3d303baa
ES
1344 fsrprintf(_("could not truncate tmpfile: %s : %s\n"),
1345 fname, strerror(errno));
1346 goto out;
1347 }
1348 if (fsync(tfd) < 0) {
1349 fsrprintf(_("could not fsync tmpfile: %s : %s\n"),
1350 fname, strerror(errno));
1351 goto out;
1352 }
c988ea91 1353
c988ea91
CH
1354 sx.sx_stat = *statp; /* struct copy */
1355 sx.sx_version = XFS_SX_VERSION;
1356 sx.sx_fdtarget = fd;
1357 sx.sx_fdtmp = tfd;
1358 sx.sx_offset = 0;
1359 sx.sx_length = statp->bs_size;
1360
1361 /* switch to the owner's id, to keep quota in line */
1362 if (fchown(tfd, statp->bs_uid, statp->bs_gid) < 0) {
1363 if (vflag)
1364 fsrprintf(_("failed to fchown tmpfile %s: %s\n"),
1365 tname, strerror(errno));
671632c6 1366 goto out;
c988ea91
CH
1367 }
1368
1369 /* Swap the extents */
1370 srval = xfs_swapext(fd, &sx);
1371 if (srval < 0) {
1372 if (errno == ENOTSUP) {
1373 if (vflag || dflag)
1374 fsrprintf(_("%s: file type not supported\n"), fname);
1375 } else if (errno == EFAULT) {
1376 /* The file has changed since we started the copy */
1377 if (vflag || dflag)
1378 fsrprintf(_("%s: file modified defrag aborted\n"),
1379 fname);
1380 } else if (errno == EBUSY) {
1381 /* Timestamp has changed or mmap'ed file */
1382 if (vflag || dflag)
1383 fsrprintf(_("%s: file busy\n"), fname);
1384 } else {
1385 fsrprintf(_("XFS_IOC_SWAPEXT failed: %s: %s\n"),
1386 fname, strerror(errno));
1387 }
671632c6 1388 goto out;
c988ea91
CH
1389 }
1390
1391 /* Report progress */
1392 if (vflag)
1393 fsrprintf(_("extents before:%d after:%d %s %s\n"),
1394 cur_nextents, new_nextents,
1395 (new_nextents <= nextents ? "DONE" : " " ),
1396 fname);
671632c6
ES
1397 retval = 0;
1398
1399out:
1400 free(fbuf);
1401 if (tfd != -1)
1402 close(tfd);
1403 if (ffd != -1)
1404 close(ffd);
1405 return retval;
c988ea91
CH
1406}
1407
1408char *
1409gettmpname(char *fname)
1410{
1411 static char buf[PATH_MAX+1];
1412 char sbuf[SMBUFSZ];
1413 char *ptr;
1414
1415 sprintf(sbuf, "/.fsr%d", getpid());
1416
6063feca
ES
1417 strncpy(buf, fname, PATH_MAX);
1418 buf[PATH_MAX] = '\0';
c988ea91
CH
1419 ptr = strrchr(buf, '/');
1420 if (ptr) {
1421 *ptr = '\0';
1422 } else {
1423 strcpy(buf, ".");
1424 }
1425
1426 if ((strlen(buf) + strlen (sbuf)) > PATH_MAX) {
1427 fsrprintf(_("tmp file name too long: %s\n"), fname);
1428 return(NULL);
1429 }
1430
1431 strcat(buf, sbuf);
1432
1433 return(buf);
1434}
1435
1436char *
1437getparent(char *fname)
1438{
1439 static char buf[PATH_MAX+1];
1440 char *ptr;
1441
6063feca
ES
1442 strncpy(buf, fname, PATH_MAX);
1443 buf[PATH_MAX] = '\0';
c988ea91
CH
1444 ptr = strrchr(buf, '/');
1445 if (ptr) {
1446 if (ptr == &buf[0])
1447 ++ptr;
1448 *ptr = '\0';
1449 } else {
1450 strcpy(buf, ".");
1451 }
1452
1453 return(buf);
1454}
1455
1456/*
1457 * Read in block map of the input file, coalesce contiguous
1458 * extents into a single range, keep all holes. Convert from 512 byte
1459 * blocks to bytes.
1460 *
1461 * This code was borrowed from mv.c with some minor mods.
1462 */
1463#define MAPSIZE 128
1464#define OUTMAP_SIZE_INCREMENT MAPSIZE
1465
b46789e2 1466int read_fd_bmap(int fd, struct xfs_bstat *sin, int *cur_nextents)
c988ea91
CH
1467{
1468 int i, cnt;
1469 struct getbmap map[MAPSIZE];
1470
1471#define BUMP_CNT \
1472 if (++cnt >= outmap_size) { \
1473 outmap_size += OUTMAP_SIZE_INCREMENT; \
1474 outmap = (struct getbmap *)realloc(outmap, \
1475 outmap_size*sizeof(*outmap)); \
1476 if (outmap == NULL) { \
1477 fsrprintf(_("realloc failed: %s\n"), \
1478 strerror(errno)); \
1479 exit(1); \
1480 } \
1481 }
1482
1483 /* Initialize the outmap array. It always grows - never shrinks.
1484 * Left-over memory allocation is saved for the next files.
1485 */
1486 if (outmap_size == 0) {
1487 outmap_size = OUTMAP_SIZE_INCREMENT; /* Initial size */
1488 outmap = (struct getbmap *)malloc(outmap_size*sizeof(*outmap));
1489 if (!outmap) {
1490 fsrprintf(_("malloc failed: %s\n"),
1491 strerror(errno));
1492 exit(1);
1493 }
1494 }
1495
1496 outmap[0].bmv_block = 0;
1497 outmap[0].bmv_offset = 0;
1498 outmap[0].bmv_length = sin->bs_size;
1499
1500 /*
1501 * If a non regular file is involved then forget holes
1502 */
1503
1504 if (!S_ISREG(sin->bs_mode))
1505 return(1);
1506
1507 outmap[0].bmv_length = 0;
1508
1509 map[0].bmv_offset = 0;
1510 map[0].bmv_block = 0;
1511 map[0].bmv_entries = 0;
1512 map[0].bmv_count = MAPSIZE;
1513 map[0].bmv_length = -1;
1514
1515 cnt = 0;
1516 *cur_nextents = 0;
1517
1518 do {
1519 if (ioctl(fd, XFS_IOC_GETBMAP, map) < 0) {
1520 fsrprintf(_("failed reading extents: inode %llu"),
1521 (unsigned long long)sin->bs_ino);
1522 exit(1);
1523 }
1524
1525 /* Concatenate extents together and replicate holes into
1526 * the output map.
1527 */
1528 *cur_nextents += map[0].bmv_entries;
1529 for (i = 0; i < map[0].bmv_entries; i++) {
1530 if (map[i + 1].bmv_block == -1) {
1531 BUMP_CNT;
1532 outmap[cnt] = map[i+1];
1533 } else if (outmap[cnt].bmv_block == -1) {
1534 BUMP_CNT;
1535 outmap[cnt] = map[i+1];
1536 } else {
1537 outmap[cnt].bmv_length += map[i + 1].bmv_length;
1538 }
1539 }
1540 } while (map[0].bmv_entries == (MAPSIZE-1));
1541 for (i = 0; i <= cnt; i++) {
1542 outmap[i].bmv_offset = BBTOB(outmap[i].bmv_offset);
1543 outmap[i].bmv_length = BBTOB(outmap[i].bmv_length);
1544 }
1545
1546 outmap[cnt].bmv_length = sin->bs_size - outmap[cnt].bmv_offset;
1547
1548 return(cnt+1);
1549}
1550
1551/*
1552 * Read the block map and return the number of extents.
1553 */
00ff2b10 1554static int
c988ea91
CH
1555getnextents(int fd)
1556{
1557 int nextents;
1558 struct getbmap map[MAPSIZE];
1559
1560 map[0].bmv_offset = 0;
1561 map[0].bmv_block = 0;
1562 map[0].bmv_entries = 0;
1563 map[0].bmv_count = MAPSIZE;
1564 map[0].bmv_length = -1;
1565
1566 nextents = 0;
1567
1568 do {
1569 if (ioctl(fd,XFS_IOC_GETBMAP, map) < 0) {
1570 fsrprintf(_("failed reading extents"));
1571 exit(1);
1572 }
1573
1574 nextents += map[0].bmv_entries;
1575 } while (map[0].bmv_entries == (MAPSIZE-1));
1576
1577 return(nextents);
1578}
1579
c988ea91
CH
1580/*
1581 * Get xfs realtime space information
1582 */
1583int
09d38d96 1584xfs_getrt(int fd, struct statvfs *sfbp)
c988ea91
CH
1585{
1586 unsigned long bsize;
1587 unsigned long factor;
1588 xfs_fsop_counts_t cnt;
1589
1590 if (!fsgeom.rtblocks)
1591 return -1;
1592
1593 if (xfs_fscounts(fd, &cnt) < 0) {
1594 close(fd);
1595 return -1;
1596 }
1597 bsize = (sfbp->f_frsize ? sfbp->f_frsize : sfbp->f_bsize);
1598 factor = fsgeom.blocksize / bsize; /* currently this is == 1 */
1599 sfbp->f_bfree = (cnt.freertx * fsgeom.rtextsize) * factor;
1600 return 0;
1601}
1602
1603int
1604fsrprintf(const char *fmt, ...)
1605{
1606 va_list ap;
1607
1608 va_start(ap, fmt);
1609 if (gflag) {
1610 static int didopenlog;
1611 if (!didopenlog) {
1612 openlog("fsr", LOG_PID, LOG_USER);
1613 didopenlog = 1;
1614 }
1615 vsyslog(LOG_INFO, fmt, ap);
1616 } else
1617 vprintf(fmt, ap);
1618 va_end(ap);
1619 return 0;
1620}
1621
c988ea91
CH
1622/*
1623 * Initialize a directory for tmp file use. This is used
1624 * by the full filesystem defragmentation when we're walking
1625 * the inodes and do not know the path for the individual
1626 * files. Multiple directories are used to spread out the
1627 * tmp data around to different ag's (since file data is
1628 * usually allocated to the same ag as the directory and
1629 * directories allocated round robin from the same
1630 * parent directory).
1631 */
1632static void
1633tmp_init(char *mnt)
1634{
1635 int i;
1636 static char buf[SMBUFSZ];
1637 mode_t mask;
1638
1639 tmp_agi = 0;
1640 sprintf(buf, "%s/.fsr", mnt);
1641
1642 mask = umask(0);
1643 if (mkdir(buf, 0700) < 0) {
1644 if (errno == EEXIST) {
1645 if (dflag)
1646 fsrprintf(_("tmpdir already exists: %s\n"),
1647 buf);
1648 } else {
1649 fsrprintf(_("could not create tmpdir: %s: %s\n"),
1650 buf, strerror(errno));
1651 exit(-1);
1652 }
1653 }
1654 for (i=0; i < fsgeom.agcount; i++) {
1655 sprintf(buf, "%s/.fsr/ag%d", mnt, i);
7d59f3fd 1656 if (mkdir(buf, 0700) < 0) {
c988ea91
CH
1657 if (errno == EEXIST) {
1658 if (dflag)
1659 fsrprintf(
1660 _("tmpdir already exists: %s\n"), buf);
1661 } else {
1662 fsrprintf(_("cannot create tmpdir: %s: %s\n"),
1663 buf, strerror(errno));
1664 exit(-1);
1665 }
1666 }
1667 }
1668 (void)umask(mask);
1669 return;
1670}
1671
1672static char *
1673tmp_next(char *mnt)
1674{
1675 static char buf[SMBUFSZ];
1676
1677 sprintf(buf, "%s/.fsr/ag%d/tmp%d",
1678 ( (strcmp(mnt, "/") == 0) ? "" : mnt),
1679 tmp_agi,
1680 getpid());
1681
1682 if (++tmp_agi == fsgeom.agcount)
1683 tmp_agi = 0;
1684
1685 return(buf);
1686}
1687
1688static void
1689tmp_close(char *mnt)
1690{
1691 static char buf[SMBUFSZ];
1692 int i;
1693
1694 /* No data is ever actually written so we can just do rmdir's */
1695 for (i=0; i < fsgeom.agcount; i++) {
1696 sprintf(buf, "%s/.fsr/ag%d", mnt, i);
1697 if (rmdir(buf) < 0) {
1698 if (errno != ENOENT) {
1699 fsrprintf(
1700 _("could not remove tmpdir: %s: %s\n"),
1701 buf, strerror(errno));
1702 }
1703 }
1704 }
1705 sprintf(buf, "%s/.fsr", mnt);
1706 if (rmdir(buf) < 0) {
1707 if (errno != ENOENT) {
1708 fsrprintf(_("could not remove tmpdir: %s: %s\n"),
1709 buf, strerror(errno));
1710 }
1711 }
1712}