| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2005 Silicon Graphics, Inc. |
| * All Rights Reserved. |
| */ |
| |
| #include <sys/types.h> |
| #include <stdbool.h> |
| #include <pwd.h> |
| #include <grp.h> |
| #include <utmp.h> |
| #include "init.h" |
| #include "quota.h" |
| |
| #define SECONDS_IN_A_DAY (24 * 60 * 60) |
| #define SECONDS_IN_A_HOUR (60 * 60) |
| #define SECONDS_IN_A_MINUTE (60) |
| |
| char * |
| time_to_string( |
| time64_t origin, |
| uint flags) |
| { |
| static char timestamp[32]; |
| time64_t timer; |
| time_t now; |
| uint days, hours, minutes, seconds; |
| |
| if (flags & ABSOLUTE_FLAG) { |
| timer = origin; |
| } else { |
| time(&now); |
| timer = max(origin - now, 0); |
| } |
| |
| /* |
| * If we are in verbose mode, or if less than a day remains, we |
| * will show "X days hh:mm:ss" so the user knows the exact timer status. |
| * |
| * Otherwise, we round down to the nearest day - so we add 30s here |
| * such that setting and reporting a limit in rapid succession will |
| * show the limit which was just set, rather than immediately reporting |
| * one day less. |
| */ |
| if ((timer > SECONDS_IN_A_DAY) && !(flags & VERBOSE_FLAG)) |
| timer += 30; /* seconds */ |
| |
| days = timer / SECONDS_IN_A_DAY; |
| if (days) |
| timer %= SECONDS_IN_A_DAY; |
| hours = timer / SECONDS_IN_A_HOUR; |
| if (hours) |
| timer %= SECONDS_IN_A_HOUR; |
| minutes = timer / SECONDS_IN_A_MINUTE; |
| seconds = timer % SECONDS_IN_A_MINUTE; |
| |
| if (flags & LIMIT_FLAG) { |
| snprintf(timestamp, sizeof(timestamp), (flags & HUMAN_FLAG) ? |
| _("[-none-]") : _("[--none--]")); |
| } else if (origin == 0) { |
| snprintf(timestamp, sizeof(timestamp), (flags & HUMAN_FLAG) ? |
| _("[------]") : _("[--------]")); |
| } else if ((hours == 0 && minutes == 0 && seconds == 0) || |
| (!(flags & VERBOSE_FLAG) && days > 0)) { |
| snprintf(timestamp, sizeof(timestamp), "[%u %s]", |
| days, days == 1 ? _("day") : _("days")); |
| } else if (flags & VERBOSE_FLAG) { |
| snprintf(timestamp, sizeof(timestamp), "[%u %s %02u:%02u:%02u]", |
| days, days == 1 ? _("day") : _("days"), |
| hours, minutes, seconds); |
| } else { /* non-verbose, less than a day remaining */ |
| snprintf(timestamp, sizeof(timestamp), |
| (flags & HUMAN_FLAG) ? |
| "%02u:%02u:%02u" : "[%02u:%02u:%02u]", |
| hours, minutes, seconds); |
| } |
| return timestamp; |
| } |
| |
| static int |
| round_snprintf( |
| char *sp, |
| size_t size, |
| const char *fmt_round, |
| const char *fmt_not_round, |
| uint64_t value, |
| uint64_t divisor) |
| { |
| double v = (double)value / divisor; |
| |
| value /= divisor; |
| if (v == (double)value) |
| return snprintf(sp, size, fmt_round, (uint)value); |
| else |
| return snprintf(sp, size, fmt_not_round, v); |
| } |
| |
| /* Basic blocks (512) bytes are returned from quotactl */ |
| #define BBS_TO_EXABYTES(bbs) ((uint64_t)(bbs)>>51) |
| #define BBS_TO_PETABYTES(bbs) ((uint64_t)(bbs)>>41) |
| #define BBS_TO_TERABYTES(bbs) ((uint64_t)(bbs)>>31) |
| #define BBS_TO_GIGABYTES(bbs) ((uint64_t)(bbs)>>21) |
| #define BBS_TO_MEGABYTES(bbs) ((uint64_t)(bbs)>>11) |
| #define BBS_TO_KILOBYTES(bbs) ((uint64_t)(bbs)>>1) |
| |
| #define BBEXABYTE ((uint64_t)1<<51) |
| #define BBPETABYTE ((uint64_t)1<<41) |
| #define BBTERABYTE ((uint64_t)1<<31) |
| #define BBGIGABYTE ((uint64_t)1<<21) |
| #define BBMEGABYTE ((uint64_t)1<<11) |
| #define BBKILOBYTE ((uint64_t)1<< 1) |
| |
| char * |
| bbs_to_string( |
| uint64_t v, |
| char *sp, |
| uint size) |
| { |
| if (v == 0) |
| snprintf(sp, size, "%4u", (uint)v); |
| else if (BBS_TO_EXABYTES(v)) |
| round_snprintf(sp, size, "%3uE", "%3.1fE", v, BBEXABYTE); |
| else if (BBS_TO_PETABYTES(v)) |
| round_snprintf(sp, size, "%3uP", "%3.1fP", v, BBPETABYTE); |
| else if (BBS_TO_TERABYTES(v)) |
| round_snprintf(sp, size, "%3uT", "%3.1fT", v, BBTERABYTE); |
| else if (BBS_TO_GIGABYTES(v)) |
| round_snprintf(sp, size, "%3uG", "%3.1fG", v, BBGIGABYTE); |
| else if (BBS_TO_MEGABYTES(v)) |
| round_snprintf(sp, size, "%3uM", "%3.1fM", v, BBMEGABYTE); |
| else if (BBS_TO_KILOBYTES(v)) |
| round_snprintf(sp, size, "%3uK", "%3.1fK", v, BBKILOBYTE); |
| else |
| snprintf(sp, size, "%4u", (uint)v << BBSHIFT); /* bytes */ |
| return sp; |
| } |
| |
| #define THOUSAND ((uint64_t)1000) |
| #define MILLION ((uint64_t)1000*1000) |
| #define BILLION ((uint64_t)1000*1000*1000) |
| #define TRILLION ((uint64_t)1000*1000*1000*1000) |
| #define GAZILLION ((uint64_t)1000*1000*1000*1000*1000) |
| #define RIDICULOUS ((uint64_t)1000*1000*1000*1000*1000*1000) |
| #define STOPALREADY ((uint64_t)1000*1000*1000*1000*1000*1000*1000) |
| |
| char * |
| num_to_string( |
| uint64_t v, |
| char *sp, |
| uint size) |
| { |
| if (v == 0) |
| snprintf(sp, size, "%4u", (uint)v); |
| else if (v > STOPALREADY) |
| round_snprintf(sp, size, "%3us", "%3.1fs", v, STOPALREADY); |
| else if (v > RIDICULOUS) |
| round_snprintf(sp, size, "%3ur", "%3.1fr", v, RIDICULOUS); |
| else if (v > GAZILLION) |
| round_snprintf(sp, size, "%3ug", "%3.1fg", v, GAZILLION); |
| else if (v > TRILLION) |
| round_snprintf(sp, size, "%3ut", "%3.1ft", v, TRILLION); |
| else if (v > BILLION) |
| round_snprintf(sp, size, "%3ub", "%3.1fb", v, BILLION); |
| else if (v > MILLION) |
| round_snprintf(sp, size, "%3um", "%3.1fm", v, MILLION); |
| else if (v > THOUSAND) |
| round_snprintf(sp, size, "%3uk", "%3.1fk", v, THOUSAND); |
| else |
| snprintf(sp, size, "%4u", (uint)v); |
| return sp; |
| } |
| |
| char * |
| pct_to_string( |
| uint64_t portion, |
| uint64_t whole, |
| char *buf, |
| uint size) |
| { |
| uint percent; |
| |
| percent = whole ? (uint) (100.0 * portion / whole + 0.5) : 0; |
| if (snprintf(buf, size, "%3u", percent) < 0) |
| return "???"; |
| |
| return buf; |
| } |
| |
| char * |
| form_to_string( |
| uint form) |
| { |
| char *forms[] = { |
| _("Blocks"), _("Inodes"), _("Realtime Blocks") }; |
| |
| if (form & XFS_BLOCK_QUOTA) |
| return forms[0]; |
| if (form & XFS_INODE_QUOTA) |
| return forms[1]; |
| if (form & XFS_RTBLOCK_QUOTA) |
| return forms[2]; |
| return NULL; |
| } |
| |
| char * |
| type_to_string( |
| uint type) |
| { |
| char *types[] = { _("User"), _("Group"), _("Project") }; |
| |
| if (type & XFS_USER_QUOTA) |
| return types[0]; |
| if (type & XFS_GROUP_QUOTA) |
| return types[1]; |
| if (type & XFS_PROJ_QUOTA) |
| return types[2]; |
| return NULL; |
| } |
| |
| |
| /* |
| * Identifier caches - user/group/project names/IDs |
| */ |
| |
| #define NID 4096 |
| #define IDMASK (NID-1) |
| |
| typedef struct { |
| uint32_t id; |
| char name[NMAX+1]; |
| } idcache_t; |
| |
| static idcache_t uidnc[NID]; |
| static idcache_t gidnc[NID]; |
| static idcache_t pidnc[NID]; |
| static int uentriesleft = NID; |
| static int gentriesleft = NID; |
| static int pentriesleft = NID; |
| |
| static idcache_t * |
| getnextpwent( |
| uint32_t id, |
| int byid) |
| { |
| struct passwd *pw; |
| static idcache_t idc; |
| |
| /* /etc/passwd */ |
| if ((pw = byid? getpwuid(id) : getpwent()) == NULL) |
| return NULL; |
| idc.id = pw->pw_uid; |
| strncpy(idc.name, pw->pw_name, NMAX); |
| return &idc; |
| } |
| |
| static idcache_t * |
| getnextgrent( |
| uint32_t id, |
| int byid) |
| { |
| struct group *gr; |
| static idcache_t idc; |
| |
| if ((gr = byid? getgrgid(id) : getgrent()) == NULL) |
| return NULL; |
| idc.id = gr->gr_gid; |
| strncpy(idc.name, gr->gr_name, NMAX); |
| return &idc; |
| } |
| |
| static idcache_t * |
| getnextprent( |
| uint32_t id, |
| int byid) |
| { |
| fs_project_t *pr; |
| static idcache_t idc; |
| |
| if ((pr = byid? getprprid(id) : getprent()) == NULL) |
| return NULL; |
| idc.id = pr->pr_prid; |
| strncpy(idc.name, pr->pr_name, NMAX); |
| return &idc; |
| } |
| |
| char * |
| uid_to_name( |
| uint32_t id) |
| { |
| idcache_t *ncp, *idp; |
| |
| /* Check cache for name first */ |
| ncp = &uidnc[id & IDMASK]; |
| if (ncp->id == id && ncp->name[0]) |
| return ncp->name; |
| if (uentriesleft) { |
| /* |
| * Fill this cache while seaching for a name. |
| * This lets us run through the file serially. |
| */ |
| if (uentriesleft == NID) |
| setpwent(); |
| while (((idp = getnextpwent(id, 0)) != NULL) && uentriesleft) { |
| uentriesleft--; |
| ncp = &uidnc[idp->id & IDMASK]; |
| if (ncp->name[0] == '\0' || idp->id == id) |
| memcpy(ncp, idp, sizeof(idcache_t)); |
| if (idp->id == id) |
| return ncp->name; |
| } |
| endpwent(); |
| uentriesleft = 0; |
| ncp = &uidnc[id & IDMASK]; |
| } |
| |
| /* Not cached - do it the slow way & insert into cache */ |
| if ((idp = getnextpwent(id, 1)) == NULL) |
| return NULL; |
| memcpy(ncp, idp, sizeof(idcache_t)); |
| return ncp->name; |
| } |
| |
| char * |
| gid_to_name( |
| uint32_t id) |
| { |
| idcache_t *ncp, *idp; |
| |
| /* Check cache for name first */ |
| ncp = &gidnc[id & IDMASK]; |
| if (ncp->id == id && ncp->name[0]) |
| return ncp->name; |
| if (gentriesleft) { |
| /* |
| * Fill this cache while seaching for a name. |
| * This lets us run through the file serially. |
| */ |
| if (gentriesleft == NID) |
| setgrent(); |
| while (((idp = getnextgrent(id, 0)) != NULL) && gentriesleft) { |
| gentriesleft--; |
| ncp = &gidnc[idp->id & IDMASK]; |
| if (ncp->name[0] == '\0' || idp->id == id) |
| memcpy(ncp, idp, sizeof(idcache_t)); |
| if (idp->id == id) |
| return ncp->name; |
| } |
| endgrent(); |
| gentriesleft = 0; |
| ncp = &gidnc[id & IDMASK]; |
| } |
| |
| /* Not cached - do it the slow way & insert into cache */ |
| if ((idp = getnextgrent(id, 1)) == NULL) |
| return NULL; |
| memcpy(ncp, idp, sizeof(idcache_t)); |
| return ncp->name; |
| } |
| |
| char * |
| prid_to_name( |
| uint32_t id) |
| { |
| idcache_t *ncp, *idp; |
| |
| /* Check cache for name first */ |
| ncp = &pidnc[id & IDMASK]; |
| if (ncp->id == id && ncp->name[0]) |
| return ncp->name; |
| if (pentriesleft) { |
| /* |
| * Fill this cache while seaching for a name. |
| * This lets us run through the file serially. |
| */ |
| if (pentriesleft == NID) |
| setprent(); |
| while (((idp = getnextprent(id, 0)) != NULL) && pentriesleft) { |
| pentriesleft--; |
| ncp = &pidnc[idp->id & IDMASK]; |
| if (ncp->name[0] == '\0' || idp->id == id) |
| memcpy(ncp, idp, sizeof(idcache_t)); |
| if (idp->id == id) |
| return ncp->name; |
| } |
| endprent(); |
| pentriesleft = 0; |
| ncp = &pidnc[id & IDMASK]; |
| } |
| |
| /* Not cached - do it the slow way & insert into cache */ |
| if ((idp = getnextprent(id, 1)) == NULL) |
| return NULL; |
| memcpy(ncp, idp, sizeof(idcache_t)); |
| return ncp->name; |
| } |
| |
| |
| /* |
| * Utility routine for opening an output file so that it can |
| * be "securely" written to (i.e. without vulnerability to a |
| * symlink attack). |
| * |
| * Returns NULL on failure, stdout on NULL input. |
| */ |
| FILE * |
| fopen_write_secure( |
| char *fname) |
| { |
| FILE *fp; |
| int fd; |
| |
| if (!fname) |
| return stdout; |
| |
| if ((fd = open(fname, O_CREAT|O_WRONLY|O_EXCL, 0600)) < 0) { |
| exitcode = 1; |
| fprintf(stderr, _("%s: open on %s failed: %s\n"), |
| progname, fname, strerror(errno)); |
| return NULL; |
| } |
| if ((fp = fdopen(fd, "w")) == NULL) { |
| exitcode = 1; |
| fprintf(stderr, _("%s: fdopen on %s failed: %s\n"), |
| progname, fname, strerror(errno)); |
| close(fd); |
| return NULL; |
| } |
| return fp; |
| } |