|  | // 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; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Push the rt block quota numbers into the regular block quota numbers so that | 
|  | * we don't have to rewrite fstests.  The limits have to match, and the regular | 
|  | * block timer cannot be active.  Only call this if you know what you are | 
|  | * doing! | 
|  | */ | 
|  | void | 
|  | __dquot_fudge_numbers( | 
|  | struct fs_disk_quota	*d) | 
|  | { | 
|  | if (!d->d_btimer && !d->d_btimer_hi) { | 
|  | d->d_btimer = d->d_rtbtimer; | 
|  | d->d_btimer_hi = d->d_rtbtimer_hi; | 
|  | d->d_rtbtimer = 0; | 
|  | d->d_rtbtimer_hi = 0; | 
|  | } | 
|  |  | 
|  | if (d->d_blk_hardlimit == d->d_rtb_hardlimit) | 
|  | d->d_rtb_hardlimit = 0; | 
|  | if (d->d_blk_softlimit == d->d_rtb_softlimit) | 
|  | d->d_rtb_softlimit = 0; | 
|  |  | 
|  | d->d_bcount += d->d_rtbcount; | 
|  | d->d_rtbcount = 0; | 
|  | } |