| // 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" |
| |
| static cmdinfo_t off_cmd; |
| static cmdinfo_t state_cmd; |
| static cmdinfo_t enable_cmd; |
| static cmdinfo_t disable_cmd; |
| static cmdinfo_t remove_cmd; |
| |
| static void |
| off_help(void) |
| { |
| printf(_( |
| "\n" |
| " turn filesystem quota off, both accounting and enforcement\n" |
| "\n" |
| " Example:\n" |
| " 'off -uv' (switch off user quota on the current filesystem)\n" |
| " This command is the equivalent of the traditional quotaoff command,\n" |
| " which disables quota completely on a mounted filesystem.\n" |
| " Note that there is no 'on' command - for XFS filesystems (with the\n" |
| " exception of the root filesystem on IRIX) quota can only be enabled\n" |
| " at mount time, through the use of one of the quota mount options.\n" |
| "\n" |
| " The state command is useful for displaying the current state. Using\n" |
| " the -v (verbose) option with the 'off' command will display the quota\n" |
| " state for the affected filesystem once the operation is complete.\n" |
| " The affected quota type is -g (groups), -p (projects) or -u (users)\n" |
| " and defaults to user quota (multiple types can be specified).\n" |
| "\n")); |
| } |
| |
| static void |
| state_help(void) |
| { |
| printf(_( |
| "\n" |
| " query the state of quota on the current filesystem\n" |
| "\n" |
| " This is a verbose status command, reporting whether or not accounting\n" |
| " and/or enforcement are enabled for a filesystem, which inodes are in\n" |
| " use as the quota state inodes, and how many extents and blocks are\n" |
| " presently being used to hold that information.\n" |
| " The quota type is specified via -g (groups), -p (projects) or -u (users)\n" |
| " and defaults to user quota (multiple types can be specified).\n" |
| "\n")); |
| } |
| |
| static void |
| enable_help(void) |
| { |
| printf(_( |
| "\n" |
| " enable quota enforcement on a filesystem\n" |
| "\n" |
| " If a filesystem is mounted and has quota accounting enabled, but not\n" |
| " quota enforcement, enforcement can be enabled with this command.\n" |
| " With the -v (verbose) option, the status of the filesystem will be\n" |
| " reported after the operation is complete.\n" |
| " The affected quota type is -g (groups), -p (projects) or -u (users)\n" |
| " and defaults to user quota (multiple types can be specified).\n" |
| "\n")); |
| } |
| |
| static void |
| disable_help(void) |
| { |
| printf(_( |
| "\n" |
| " disable quota enforcement on a filesystem\n" |
| "\n" |
| " If a filesystem is mounted and is currently enforcing quota, this\n" |
| " provides a mechanism to switch off the enforcement, but continue to\n" |
| " perform used space (and used inodes) accounting.\n" |
| " The affected quota type is -g (groups), -p (projects) or -u (users).\n" |
| "\n")); |
| } |
| |
| static void |
| remove_help(void) |
| { |
| printf(_( |
| "\n" |
| " remove any space being used by the quota subsystem\n" |
| "\n" |
| " Once quota has been switched 'off' on a filesystem, the space that\n" |
| " was allocated to holding quota metadata can be freed via this command.\n" |
| " The affected quota type is -g (groups), -p (projects) or -u (users)\n" |
| " and defaults to user quota (multiple types can be specified).\n" |
| "\n")); |
| } |
| |
| static void |
| state_qfilestat( |
| FILE *fp, |
| struct fs_path *mount, |
| uint type, |
| struct fs_qfilestatv *qfs, |
| int accounting, |
| int enforcing) |
| { |
| fprintf(fp, _("%s quota state on %s (%s)\n"), type_to_string(type), |
| mount->fs_dir, mount->fs_name); |
| fprintf(fp, _(" Accounting: %s\n"), accounting ? _("ON") : _("OFF")); |
| fprintf(fp, _(" Enforcement: %s\n"), enforcing ? _("ON") : _("OFF")); |
| if (qfs->qfs_ino != (__u64) -1) |
| fprintf(fp, _(" Inode: #%llu (%llu blocks, %lu extents)\n"), |
| (unsigned long long)qfs->qfs_ino, |
| (unsigned long long)qfs->qfs_nblks, |
| (unsigned long)qfs->qfs_nextents); |
| else |
| fprintf(fp, _(" Inode: N/A\n")); |
| } |
| |
| static void |
| state_timelimit( |
| FILE *fp, |
| uint form, |
| uint32_t timelimit) |
| { |
| fprintf(fp, _("%s grace time: %s\n"), |
| form_to_string(form), |
| time_to_string(timelimit, VERBOSE_FLAG | ABSOLUTE_FLAG)); |
| } |
| |
| static void |
| state_warnlimit( |
| FILE *fp, |
| uint form, |
| uint16_t warnlimit) |
| { |
| fprintf(fp, _("%s max warnings: %u\n"), |
| form_to_string(form), warnlimit); |
| } |
| |
| /* |
| * fs_quota_stat holds a subset of fs_quota_statv; this copies |
| * the smaller into the larger, leaving any not-present fields |
| * empty. This is so the same reporting function can be used |
| * for both XFS_GETQSTAT and XFS_GETQSTATV results. |
| */ |
| static void |
| state_stat_to_statv( |
| struct fs_quota_stat *s, |
| struct fs_quota_statv *sv) |
| { |
| memset(sv, 0, sizeof(struct fs_quota_statv)); |
| |
| /* shared information */ |
| sv->qs_version = s->qs_version; |
| sv->qs_flags = s->qs_flags; |
| sv->qs_incoredqs = s->qs_incoredqs; |
| sv->qs_btimelimit = s->qs_btimelimit; |
| sv->qs_itimelimit = s->qs_itimelimit; |
| sv->qs_rtbtimelimit = s->qs_rtbtimelimit; |
| sv->qs_bwarnlimit = s->qs_bwarnlimit; |
| sv->qs_iwarnlimit = s->qs_iwarnlimit; |
| |
| /* Always room for uquota */ |
| sv->qs_uquota.qfs_ino = s->qs_uquota.qfs_ino; |
| sv->qs_uquota.qfs_nblks = s->qs_uquota.qfs_nblks; |
| sv->qs_uquota.qfs_nextents = s->qs_uquota.qfs_nextents; |
| |
| /* |
| * If we are here, XFS_GETQSTATV failed and XFS_GETQSTAT passed; |
| * that is a very strong hint that we're on a kernel which predates |
| * the on-disk pquota inode; both were added in v3.12. So, we do |
| * some tricksy determination here. |
| * gs_gquota may hold either group quota inode info, or project |
| * quota if that is used instead; which one it actually holds depends |
| * on the quota flags. (If neither is set, neither is used) |
| */ |
| if (s->qs_flags & XFS_QUOTA_GDQ_ACCT) { |
| /* gs_gquota holds group quota info */ |
| sv->qs_gquota.qfs_ino = s->qs_gquota.qfs_ino; |
| sv->qs_gquota.qfs_nblks = s->qs_gquota.qfs_nblks; |
| sv->qs_gquota.qfs_nextents = s->qs_gquota.qfs_nextents; |
| } else if (s->qs_flags & XFS_QUOTA_PDQ_ACCT) { |
| /* gs_gquota actually holds project quota info */ |
| sv->qs_pquota.qfs_ino = s->qs_gquota.qfs_ino; |
| sv->qs_pquota.qfs_nblks = s->qs_gquota.qfs_nblks; |
| sv->qs_pquota.qfs_nextents = s->qs_gquota.qfs_nextents; |
| } |
| } |
| |
| static void |
| state_quotafile_stat( |
| FILE *fp, |
| uint type, |
| struct fs_path *mount, |
| struct fs_quota_statv *sv, |
| struct fs_quota_stat *s, |
| uint flags) |
| { |
| bool accounting, enforcing; |
| struct fs_qfilestatv *qsv; |
| char *dev = mount->fs_name; |
| |
| if (xfsquotactl(XFS_GETQSTATV, dev, type, 0, (void *)sv) < 0) { |
| if (xfsquotactl(XFS_GETQSTAT, dev, type, 0, (void *)s) < 0) { |
| if (flags & VERBOSE_FLAG) |
| fprintf(fp, |
| _("%s quota are not enabled on %s\n"), |
| type_to_string(type), dev); |
| return; |
| } |
| state_stat_to_statv(s, sv); |
| } |
| |
| switch(type) { |
| case XFS_USER_QUOTA: |
| qsv = &sv->qs_uquota; |
| accounting = sv->qs_flags & XFS_QUOTA_UDQ_ACCT; |
| enforcing = sv->qs_flags & XFS_QUOTA_UDQ_ENFD; |
| break; |
| case XFS_GROUP_QUOTA: |
| qsv = &sv->qs_gquota; |
| accounting = sv->qs_flags & XFS_QUOTA_GDQ_ACCT; |
| enforcing = sv->qs_flags & XFS_QUOTA_GDQ_ENFD; |
| break; |
| case XFS_PROJ_QUOTA: |
| qsv = &sv->qs_pquota; |
| accounting = sv->qs_flags & XFS_QUOTA_PDQ_ACCT; |
| enforcing = sv->qs_flags & XFS_QUOTA_PDQ_ENFD; |
| break; |
| default: |
| return; |
| } |
| |
| |
| state_qfilestat(fp, mount, type, qsv, accounting, enforcing); |
| |
| state_timelimit(fp, XFS_BLOCK_QUOTA, sv->qs_btimelimit); |
| state_warnlimit(fp, XFS_BLOCK_QUOTA, sv->qs_bwarnlimit); |
| |
| state_timelimit(fp, XFS_INODE_QUOTA, sv->qs_itimelimit); |
| state_warnlimit(fp, XFS_INODE_QUOTA, sv->qs_iwarnlimit); |
| |
| state_timelimit(fp, XFS_RTBLOCK_QUOTA, sv->qs_rtbtimelimit); |
| } |
| |
| static void |
| state_quotafile_mount( |
| FILE *fp, |
| uint type, |
| struct fs_path *mount, |
| uint flags) |
| { |
| struct fs_quota_stat s; |
| struct fs_quota_statv sv; |
| |
| sv.qs_version = FS_QSTATV_VERSION1; |
| |
| if (type & XFS_USER_QUOTA) { |
| state_quotafile_stat(fp, XFS_USER_QUOTA, mount, |
| &sv, &s, flags); |
| } |
| |
| if (type & XFS_GROUP_QUOTA) { |
| state_quotafile_stat(fp, XFS_GROUP_QUOTA, mount, |
| &sv, &s, flags); |
| } |
| |
| if (type & XFS_PROJ_QUOTA) { |
| state_quotafile_stat(fp, XFS_PROJ_QUOTA, mount, |
| &sv, &s, flags); |
| } |
| } |
| |
| static void |
| state_quotafile( |
| FILE *fp, |
| uint type, |
| char *dir, |
| uint flags) |
| { |
| fs_cursor_t cursor; |
| fs_path_t *mount; |
| |
| fs_cursor_initialise(dir, FS_MOUNT_POINT, &cursor); |
| while ((mount = fs_cursor_next_entry(&cursor))) |
| state_quotafile_mount(fp, type, mount, flags); |
| } |
| |
| static int |
| state_f( |
| int argc, |
| char **argv) |
| { |
| FILE *fp = NULL; |
| char *fname = NULL; |
| int c, flags = 0, type = 0; |
| |
| while ((c = getopt(argc, argv, "af:gpuv")) != EOF) { |
| switch (c) { |
| case 'a': |
| flags |= ALL_MOUNTS_FLAG; |
| break; |
| case 'f': |
| fname = optarg; |
| break; |
| case 'g': |
| type |= XFS_GROUP_QUOTA; |
| break; |
| case 'p': |
| type |= XFS_PROJ_QUOTA; |
| break; |
| case 'u': |
| type |= XFS_USER_QUOTA; |
| break; |
| case 'v': |
| flags |= VERBOSE_FLAG; |
| break; |
| default: |
| return command_usage(&state_cmd); |
| } |
| } |
| |
| if (argc != optind) |
| return command_usage(&state_cmd); |
| |
| if ((fp = fopen_write_secure(fname)) == NULL) |
| return 0; |
| |
| if (!type) |
| type = XFS_USER_QUOTA | XFS_GROUP_QUOTA | XFS_PROJ_QUOTA; |
| |
| if (flags & ALL_MOUNTS_FLAG) |
| state_quotafile(fp, type, NULL, flags); |
| else if (fs_path && fs_path->fs_flags & FS_MOUNT_POINT) |
| state_quotafile(fp, type, fs_path->fs_dir, flags); |
| |
| if (fname) |
| fclose(fp); |
| return 0; |
| } |
| |
| static void |
| enable_enforcement( |
| char *dir, |
| uint type, |
| uint qflags, |
| uint flags) |
| { |
| fs_path_t *mount; |
| |
| mount = fs_table_lookup(dir, FS_MOUNT_POINT); |
| if (!mount) { |
| exitcode = 1; |
| fprintf(stderr, "%s: unknown mount point %s\n", progname, dir); |
| return; |
| } |
| dir = mount->fs_name; |
| if (xfsquotactl(XFS_QUOTAON, dir, type, 0, (void *)&qflags) < 0) { |
| if (errno == EEXIST) |
| fprintf(stderr, |
| _("Quota enforcement already enabled.\n")); |
| else if (errno == EINVAL || errno == ENOSYS) |
| fprintf(stderr, |
| _("Can't enable enforcement when quota off.\n")); |
| else |
| perror("XFS_QUOTAON"); |
| } |
| else if (flags & VERBOSE_FLAG) |
| state_quotafile_mount(stdout, type, mount, flags); |
| } |
| |
| static void |
| disable_enforcement( |
| char *dir, |
| uint type, |
| uint qflags, |
| uint flags) |
| { |
| fs_path_t *mount; |
| |
| mount = fs_table_lookup(dir, FS_MOUNT_POINT); |
| if (!mount) { |
| exitcode = 1; |
| fprintf(stderr, "%s: unknown mount point %s\n", progname, dir); |
| return; |
| } |
| dir = mount->fs_name; |
| if (xfsquotactl(XFS_QUOTAOFF, dir, type, 0, (void *)&qflags) < 0) { |
| if (errno == EEXIST) |
| fprintf(stderr, |
| _("Quota enforcement already disabled.\n")); |
| else if (errno == EINVAL || errno == ENOSYS) |
| fprintf(stderr, |
| _("Can't disable enforcement when quota off.\n")); |
| else |
| perror("XFS_QUOTAOFF"); |
| } |
| else if (flags & VERBOSE_FLAG) |
| state_quotafile_mount(stdout, type, mount, flags); |
| } |
| |
| static void |
| quotaoff( |
| char *dir, |
| uint type, |
| uint qflags, |
| uint flags) |
| { |
| fs_path_t *mount; |
| |
| mount = fs_table_lookup(dir, FS_MOUNT_POINT); |
| if (!mount) { |
| exitcode = 1; |
| fprintf(stderr, "%s: unknown mount point %s\n", progname, dir); |
| return; |
| } |
| dir = mount->fs_name; |
| if (xfsquotactl(XFS_QUOTAOFF, dir, type, 0, (void *)&qflags) < 0) { |
| if (errno == EEXIST || errno == ENOSYS) |
| fprintf(stderr, _("Quota already off.\n")); |
| else |
| perror("XFS_QUOTAOFF"); |
| } |
| else if (flags & VERBOSE_FLAG) |
| state_quotafile_mount(stdout, type, mount, flags); |
| } |
| |
| static int |
| remove_qtype_extents( |
| char *dir, |
| uint type) |
| { |
| int error = 0; |
| |
| if ((error = xfsquotactl(XFS_QUOTARM, dir, type, 0, (void *)&type)) < 0) |
| perror("XFS_QUOTARM"); |
| return error; |
| } |
| |
| static void |
| remove_extents( |
| char *dir, |
| uint type, |
| uint flags) |
| { |
| fs_path_t *mount; |
| |
| mount = fs_table_lookup(dir, FS_MOUNT_POINT); |
| if (!mount) { |
| exitcode = 1; |
| fprintf(stderr, "%s: unknown mount point %s\n", progname, dir); |
| return; |
| } |
| dir = mount->fs_name; |
| if (type & XFS_USER_QUOTA) { |
| if (remove_qtype_extents(dir, XFS_USER_QUOTA) < 0) |
| return; |
| } |
| if (type & XFS_GROUP_QUOTA) { |
| if (remove_qtype_extents(dir, XFS_GROUP_QUOTA) < 0) |
| return; |
| } |
| if (type & XFS_PROJ_QUOTA) { |
| if (remove_qtype_extents(dir, XFS_PROJ_QUOTA) < 0) |
| return; |
| } |
| if (flags & VERBOSE_FLAG) |
| state_quotafile_mount(stdout, type, mount, flags); |
| } |
| |
| static int |
| enable_f( |
| int argc, |
| char **argv) |
| { |
| int c, flags = 0, qflags = 0, type = 0; |
| |
| while ((c = getopt(argc, argv, "gpuv")) != EOF) { |
| switch (c) { |
| case 'g': |
| type |= XFS_GROUP_QUOTA; |
| qflags |= XFS_QUOTA_GDQ_ACCT | XFS_QUOTA_GDQ_ENFD; |
| break; |
| case 'p': |
| type |= XFS_PROJ_QUOTA; |
| qflags |= XFS_QUOTA_PDQ_ACCT | XFS_QUOTA_PDQ_ENFD; |
| break; |
| case 'u': |
| type |= XFS_USER_QUOTA; |
| qflags |= XFS_QUOTA_UDQ_ACCT | XFS_QUOTA_UDQ_ENFD; |
| break; |
| case 'v': |
| flags |= VERBOSE_FLAG; |
| break; |
| default: |
| return command_usage(&enable_cmd); |
| } |
| } |
| |
| if (argc != optind) |
| return command_usage(&enable_cmd); |
| |
| if (!type) { |
| type |= XFS_USER_QUOTA; |
| qflags |= XFS_QUOTA_UDQ_ACCT | XFS_QUOTA_UDQ_ENFD; |
| } |
| |
| if (fs_path->fs_flags & FS_MOUNT_POINT) |
| enable_enforcement(fs_path->fs_dir, type, qflags, flags); |
| return 0; |
| } |
| |
| static int |
| disable_f( |
| int argc, |
| char **argv) |
| { |
| int c, flags = 0, qflags = 0, type = 0; |
| |
| while ((c = getopt(argc, argv, "gpuv")) != EOF) { |
| switch (c) { |
| case 'g': |
| type |= XFS_GROUP_QUOTA; |
| qflags |= XFS_QUOTA_GDQ_ENFD; |
| break; |
| case 'p': |
| type |= XFS_PROJ_QUOTA; |
| qflags |= XFS_QUOTA_PDQ_ENFD; |
| break; |
| case 'u': |
| type |= XFS_USER_QUOTA; |
| qflags |= XFS_QUOTA_UDQ_ENFD; |
| break; |
| case 'v': |
| flags |= VERBOSE_FLAG; |
| break; |
| default: |
| return command_usage(&disable_cmd); |
| } |
| } |
| |
| if (argc != optind) |
| return command_usage(&disable_cmd); |
| |
| if (!type) { |
| type |= XFS_USER_QUOTA; |
| qflags |= XFS_QUOTA_UDQ_ENFD; |
| } |
| |
| if (fs_path->fs_flags & FS_MOUNT_POINT) |
| disable_enforcement(fs_path->fs_dir, type, qflags, flags); |
| return 0; |
| } |
| |
| static int |
| off_f( |
| int argc, |
| char **argv) |
| { |
| int c, flags = 0, qflags = 0, type = 0; |
| |
| while ((c = getopt(argc, argv, "gpuv")) != EOF) { |
| switch (c) { |
| case 'g': |
| type |= XFS_GROUP_QUOTA; |
| qflags |= XFS_QUOTA_GDQ_ACCT | XFS_QUOTA_GDQ_ENFD; |
| break; |
| case 'p': |
| type |= XFS_PROJ_QUOTA; |
| qflags |= XFS_QUOTA_PDQ_ACCT | XFS_QUOTA_PDQ_ENFD; |
| break; |
| case 'u': |
| type |= XFS_USER_QUOTA; |
| qflags |= XFS_QUOTA_UDQ_ACCT | XFS_QUOTA_UDQ_ENFD; |
| break; |
| case 'v': |
| flags |= VERBOSE_FLAG; |
| break; |
| default: |
| return command_usage(&off_cmd); |
| } |
| } |
| |
| if (argc != optind) |
| return command_usage(&off_cmd); |
| |
| if (!type) { |
| type |= XFS_USER_QUOTA; |
| qflags |= XFS_QUOTA_UDQ_ACCT | XFS_QUOTA_UDQ_ENFD; |
| } |
| |
| if (fs_path->fs_flags & FS_MOUNT_POINT) |
| quotaoff(fs_path->fs_dir, type, qflags, flags); |
| return 0; |
| } |
| |
| static int |
| remove_f( |
| int argc, |
| char **argv) |
| { |
| int c, flags = 0, type = 0; |
| |
| while ((c = getopt(argc, argv, "gpuv")) != EOF) { |
| switch (c) { |
| case 'g': |
| type |= XFS_GROUP_QUOTA; |
| break; |
| case 'p': |
| type |= XFS_PROJ_QUOTA; |
| break; |
| case 'u': |
| type |= XFS_USER_QUOTA; |
| break; |
| case 'v': |
| flags |= VERBOSE_FLAG; |
| break; |
| default: |
| return command_usage(&remove_cmd); |
| } |
| } |
| |
| if (argc != optind) |
| return command_usage(&remove_cmd); |
| |
| if (!type) { |
| type |= XFS_USER_QUOTA; |
| } |
| |
| if (fs_path->fs_flags & FS_MOUNT_POINT) |
| remove_extents(fs_path->fs_dir, type, flags); |
| return 0; |
| } |
| |
| void |
| state_init(void) |
| { |
| off_cmd.name = "off"; |
| off_cmd.cfunc = off_f; |
| off_cmd.argmin = 0; |
| off_cmd.argmax = -1; |
| off_cmd.args = _("[-gpu] [-v]"); |
| off_cmd.oneline = _("permanently switch quota off for a path"); |
| off_cmd.help = off_help; |
| |
| state_cmd.name = "state"; |
| state_cmd.cfunc = state_f; |
| state_cmd.argmin = 0; |
| state_cmd.argmax = -1; |
| state_cmd.args = _("[-gpu] [-a] [-v] [-f file]"); |
| state_cmd.oneline = _("get overall quota state information"); |
| state_cmd.help = state_help; |
| state_cmd.flags = CMD_FLAG_FOREIGN_OK; |
| |
| enable_cmd.name = "enable"; |
| enable_cmd.cfunc = enable_f; |
| enable_cmd.argmin = 0; |
| enable_cmd.argmax = -1; |
| enable_cmd.args = _("[-gpu] [-v]"); |
| enable_cmd.oneline = _("enable quota enforcement"); |
| enable_cmd.help = enable_help; |
| |
| disable_cmd.name = "disable"; |
| disable_cmd.cfunc = disable_f; |
| disable_cmd.argmin = 0; |
| disable_cmd.argmax = -1; |
| disable_cmd.args = _("[-gpu] [-v]"); |
| disable_cmd.oneline = _("disable quota enforcement"); |
| disable_cmd.help = disable_help; |
| |
| remove_cmd.name = "remove"; |
| remove_cmd.cfunc = remove_f; |
| remove_cmd.argmin = 0; |
| remove_cmd.argmax = -1; |
| remove_cmd.args = _("[-gpu] [-v]"); |
| remove_cmd.oneline = _("remove quota extents from a filesystem"); |
| remove_cmd.help = remove_help; |
| |
| if (expert) { |
| add_command(&off_cmd); |
| add_command(&state_cmd); |
| add_command(&enable_cmd); |
| add_command(&disable_cmd); |
| add_command(&remove_cmd); |
| } |
| } |