| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2005 Silicon Graphics, Inc. |
| * All Rights Reserved. |
| */ |
| |
| #include <stdbool.h> |
| #include "command.h" |
| #include "init.h" |
| #include "quota.h" |
| #include "libfrog/logging.h" |
| #include "libfrog/fsgeom.h" |
| |
| static cmdinfo_t free_cmd; |
| |
| static void |
| free_help(void) |
| { |
| printf(_( |
| "\n" |
| " reports the number of free disk blocks and inodes\n" |
| "\n" |
| " This command reports the number of total, used, and available disk blocks.\n" |
| " It can optionally report the same set of numbers for inodes and realtime\n" |
| " disk blocks, and will report on all known XFS filesystem mount points and\n" |
| " project quota paths by default (see 'print' command for a list).\n" |
| " -b -- report the block count values\n" |
| " -i -- report the inode count values\n" |
| " -r -- report the realtime block count values\n" |
| " -h -- report in a human-readable format\n" |
| " -N -- suppress the header from the output\n" |
| "\n")); |
| } |
| |
| /* |
| * The data and realtime block counts returned (count, used, and |
| * free) are all in basic block units. |
| */ |
| static int |
| mount_free_space_data( |
| struct fs_path *mount, |
| uint64_t *bcount, |
| uint64_t *bused, |
| uint64_t *bfree, |
| uint64_t *icount, |
| uint64_t *iused, |
| uint64_t *ifree, |
| uint64_t *rcount, |
| uint64_t *rused, |
| uint64_t *rfree) |
| { |
| struct xfs_fsop_counts fscounts; |
| struct xfs_fsop_geom fsgeo; |
| struct statfs st; |
| uint64_t logsize, count, free; |
| int fd, ret; |
| |
| if ((fd = open(mount->fs_dir, O_RDONLY)) < 0) { |
| exitcode = 1; |
| fprintf(stderr, "%s: cannot open %s: %s\n", |
| progname, mount->fs_dir, strerror(errno)); |
| return 0; |
| } |
| |
| if (platform_fstatfs(fd, &st) < 0) { |
| perror("fstatfs"); |
| close(fd); |
| return 0; |
| } |
| |
| if (!(mount->fs_flags & FS_FOREIGN)) { |
| ret = -xfrog_geometry(fd, &fsgeo); |
| if (ret) { |
| xfrog_perror(ret, "XFS_IOC_FSGEOMETRY"); |
| close(fd); |
| return 0; |
| } |
| if ((xfsctl(mount->fs_dir, fd, XFS_IOC_FSCOUNTS, |
| &fscounts)) < 0) { |
| perror("XFS_IOC_FSCOUNTS"); |
| close(fd); |
| return 0; |
| } |
| |
| logsize = fsgeo.logstart ? fsgeo.logblocks : 0; |
| count = (fsgeo.datablocks - logsize) * fsgeo.blocksize; |
| free = fscounts.freedata * fsgeo.blocksize; |
| *bcount = BTOBB(count); |
| *bfree = BTOBB(free); |
| *bused = BTOBB(count - free); |
| |
| count = fsgeo.rtextents * fsgeo.rtextsize * fsgeo.blocksize; |
| free = fscounts.freertx * fsgeo.rtextsize * fsgeo.blocksize; |
| *rcount = BTOBB(count); |
| *rfree = BTOBB(free); |
| *rused = BTOBB(count - free); |
| } else { |
| count = st.f_blocks * st.f_bsize; |
| free = st.f_bfree * st.f_bsize; |
| *bcount = BTOBB(count); |
| *bfree = BTOBB(free); |
| *bused = BTOBB(count - free); |
| |
| *rcount = BTOBB(0); |
| *rfree = BTOBB(0); |
| *rused = BTOBB(0); |
| } |
| |
| |
| *icount = st.f_files; |
| *ifree = st.f_ffree; |
| *iused = st.f_files - st.f_ffree; |
| |
| close(fd); |
| return 1; |
| } |
| |
| /* |
| * The data and realtime block counts returned (count, used, and |
| * free) are all in basic block units. |
| */ |
| static int |
| projects_free_space_data( |
| struct fs_path *path, |
| uint64_t *bcount, |
| uint64_t *bused, |
| uint64_t *bfree, |
| uint64_t *icount, |
| uint64_t *iused, |
| uint64_t *ifree, |
| uint64_t *rcount, |
| uint64_t *rused, |
| uint64_t *rfree) |
| { |
| fs_quota_stat_t qfs; |
| fs_disk_quota_t d; |
| struct fsxattr fsx; |
| uint type = XFS_PROJ_QUOTA; |
| char *dev = path->fs_name; |
| int fd; |
| |
| if (xfsquotactl(XFS_GETQSTAT, dev, type, 0, &qfs) < 0 || |
| !(qfs.qs_flags & XFS_QUOTA_PDQ_ACCT)) |
| return 0; |
| |
| if ((fd = open(path->fs_dir, O_RDONLY)) < 0) { |
| exitcode = 1; |
| fprintf(stderr, "%s: cannot open %s: %s\n", |
| progname, path->fs_dir, strerror(errno)); |
| return 0; |
| } |
| |
| if ((xfsctl(path->fs_dir, fd, FS_IOC_FSGETXATTR, &fsx)) < 0) { |
| exitcode = 1; |
| perror("FS_IOC_FSGETXATTR"); |
| close(fd); |
| return 0; |
| } |
| if (!(fsx.fsx_xflags & FS_XFLAG_PROJINHERIT)) { |
| exitcode = 1; |
| fprintf(stderr, _("%s: project quota flag not set on %s\n"), |
| progname, path->fs_dir); |
| close(fd); |
| return 0; |
| } |
| |
| if (path->fs_prid != fsx.fsx_projid) { |
| exitcode = 1; |
| fprintf(stderr, |
| _("%s: project ID %u (%s) doesn't match ID %u (%s)\n"), |
| progname, path->fs_prid, projects_file, |
| fsx.fsx_projid, path->fs_dir); |
| close(fd); |
| return 0; |
| } |
| |
| xfsquotactl(XFS_QSYNC, dev, type, fsx.fsx_projid, NULL); |
| if (xfsquotactl(XFS_GETQUOTA, dev, type, fsx.fsx_projid, &d) < 0) { |
| perror("XFS_GETQUOTA"); |
| close(fd); |
| return 0; |
| } |
| |
| /* If no softlimit is set for any of blk/ino/rt, get actual usage */ |
| if (!d.d_blk_softlimit || !d.d_ino_softlimit || !d.d_rtb_softlimit) { |
| mount_free_space_data(path, bcount, bused, bfree, |
| icount, iused, ifree, rcount, rused, rfree); |
| } |
| |
| if (d.d_blk_softlimit) { |
| *bcount = d.d_blk_softlimit; |
| *bfree = (d.d_blk_softlimit - d.d_bcount); |
| } |
| *bused = d.d_bcount; |
| |
| if (d.d_ino_softlimit) { |
| *icount = d.d_ino_softlimit; |
| *ifree = (d.d_ino_softlimit - d.d_icount); |
| } |
| *iused = d.d_icount; |
| |
| if (d.d_rtb_softlimit) { |
| *rcount = d.d_rtb_softlimit; |
| *rfree = (d.d_rtb_softlimit - d.d_rtbcount); |
| } |
| *rused = d.d_rtbcount; |
| |
| close(fd); |
| return 1; |
| } |
| |
| static int |
| free_space( |
| FILE *fp, |
| uint form, |
| fs_path_t *path, |
| uint flags) |
| { |
| uint64_t bcount, bused, bfree; |
| uint64_t icount, iused, ifree; |
| uint64_t rcount, rused, rfree; |
| char a[8], s[8], u[8], p[8]; |
| int count; |
| |
| count = (path->fs_flags & FS_PROJECT_PATH) ? |
| projects_free_space_data(path, &bcount, &bused, &bfree, |
| &icount, &iused, &ifree, |
| &rcount, &rused, &rfree) : |
| mount_free_space_data(path, &bcount, &bused, &bfree, |
| &icount, &iused, &ifree, |
| &rcount, &rused, &rfree); |
| if (!count) |
| return 0; |
| |
| if (!(flags & NO_HEADER_FLAG)) { |
| fprintf(fp, (flags & HUMAN_FLAG) ? |
| _("Filesystem ") : _("Filesystem ")); |
| if (form & (XFS_BLOCK_QUOTA|XFS_RTBLOCK_QUOTA)) |
| fprintf(fp, (flags & HUMAN_FLAG) ? |
| _(" Size Used Avail Use%%") : |
| _(" 1K-blocks Used Available Use%%")); |
| else if (form & XFS_INODE_QUOTA) |
| fprintf(fp, (flags & HUMAN_FLAG) ? |
| _(" Inodes Used Free Use%%") : |
| _(" Inodes IUsed IFree IUse%%")); |
| fprintf(fp, _(" Pathname\n")); |
| } |
| |
| if (flags & HUMAN_FLAG) { |
| count = fprintf(fp, "%-12s", path->fs_name); |
| if (count > 13) |
| fprintf(fp, "\n%12s", " "); |
| } else { |
| count = fprintf(fp, "%-19s", path->fs_name); |
| if (count > 20) |
| fprintf(fp, "\n%19s", " "); |
| } |
| |
| if (form & XFS_BLOCK_QUOTA) { |
| if (flags & HUMAN_FLAG) |
| fprintf(fp, " %6s %6s %6s %3s%%", |
| bbs_to_string(bcount, s, sizeof(s)), |
| bbs_to_string(bused, u, sizeof(u)), |
| bbs_to_string(bfree, a, sizeof(a)), |
| pct_to_string(bused, bcount, p, sizeof(p))); |
| else |
| fprintf(fp, " %10llu %10llu %10llu %3s%%", |
| (unsigned long long)bcount >> 1, |
| (unsigned long long)bused >> 1, |
| (unsigned long long)bfree >> 1, |
| pct_to_string(bused, bcount, p, sizeof(p))); |
| } else if (form & XFS_INODE_QUOTA) { |
| if (flags & HUMAN_FLAG) |
| fprintf(fp, " %6s %6s %6s %3s%%", |
| num_to_string(icount, s, sizeof(s)), |
| num_to_string(iused, u, sizeof(u)), |
| num_to_string(ifree, a, sizeof(a)), |
| pct_to_string(iused, icount, p, sizeof(p))); |
| else |
| fprintf(fp, " %10llu %10llu %10llu %3s%%", |
| (unsigned long long)icount, |
| (unsigned long long)iused, |
| (unsigned long long)ifree, |
| pct_to_string(iused, icount, p, sizeof(p))); |
| } else if (form & XFS_RTBLOCK_QUOTA) { |
| if (flags & HUMAN_FLAG) |
| fprintf(fp, " %6s %6s %6s %3s%%", |
| bbs_to_string(rcount, s, sizeof(s)), |
| bbs_to_string(rused, u, sizeof(u)), |
| bbs_to_string(rfree, a, sizeof(a)), |
| pct_to_string(rused, rcount, p, sizeof(p))); |
| else |
| fprintf(fp, " %10llu %10llu %10llu %3s%%", |
| (unsigned long long)rcount >> 1, |
| (unsigned long long)rused >> 1, |
| (unsigned long long)rfree >> 1, |
| pct_to_string(rused, rcount, p, sizeof(p))); |
| } |
| fprintf(fp, " %s\n", path->fs_dir); |
| return 1; |
| } |
| |
| static void |
| free_space_list( |
| FILE *fp, |
| uint form, |
| char *dir, |
| uint flags) |
| { |
| fs_cursor_t cursor; |
| fs_path_t *path; |
| |
| fs_cursor_initialise(dir, 0, &cursor); |
| while ((path = fs_cursor_next_entry(&cursor))) { |
| if (free_space(fp, form, path, flags)) |
| flags |= NO_HEADER_FLAG; |
| } |
| } |
| |
| static int |
| free_f( |
| int argc, |
| char **argv) |
| { |
| FILE *fp = NULL; |
| char *fname = NULL; |
| int c, flags = 0, form = 0; |
| |
| while ((c = getopt(argc, argv, "bf:hNir")) != EOF) { |
| switch (c) { |
| case 'f': |
| fname = optarg; |
| break; |
| case 'b': |
| form |= XFS_BLOCK_QUOTA; |
| break; |
| case 'i': |
| form |= XFS_INODE_QUOTA; |
| break; |
| case 'r': |
| form |= XFS_RTBLOCK_QUOTA; |
| break; |
| case 'h': |
| flags |= HUMAN_FLAG; |
| break; |
| case 'N': |
| flags |= NO_HEADER_FLAG; |
| break; |
| default: |
| return command_usage(&free_cmd); |
| } |
| } |
| |
| if (!form) |
| form = XFS_BLOCK_QUOTA; |
| |
| if ((fp = fopen_write_secure(fname)) == NULL) |
| return 0; |
| |
| if (argc == optind) |
| free_space_list(fp, form, NULL, flags); |
| else while (argc > optind) |
| free_space_list(fp, form, argv[optind++], flags); |
| |
| if (fname) |
| fclose(fp); |
| return 0; |
| } |
| |
| void |
| free_init(void) |
| { |
| free_cmd.name = "df"; |
| free_cmd.altname = "free"; |
| free_cmd.cfunc = free_f; |
| free_cmd.argmin = 0; |
| free_cmd.argmax = -1; |
| free_cmd.args = _("[-bir] [-hN] [-f file]"); |
| free_cmd.oneline = _("show free and used counts for blocks and inodes"); |
| free_cmd.help = free_help; |
| free_cmd.flags = CMD_FLAG_FOREIGN_OK; |
| |
| add_command(&free_cmd); |
| } |