| /* | 
 |  * Copyright (c) 2005 Silicon Graphics, Inc. | 
 |  * All Rights Reserved. | 
 |  * | 
 |  * This program is free software; you can redistribute it and/or | 
 |  * modify it under the terms of the GNU General Public License as | 
 |  * published by the Free Software Foundation. | 
 |  * | 
 |  * This program is distributed in the hope that it would be useful, | 
 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
 |  * GNU General Public License for more details. | 
 |  * | 
 |  * You should have received a copy of the GNU General Public License | 
 |  * along with this program; if not, write the Free Software Foundation, | 
 |  * Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | 
 |  */ | 
 |  | 
 | #include <sys/types.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( | 
 | 	time_t		origin, | 
 | 	uint		flags) | 
 | { | 
 | 	static char	timestamp[32]; | 
 | 	time_t		now, timer; | 
 | 	uint		days, hours, minutes, seconds; | 
 |  | 
 | 	if (flags & ABSOLUTE_FLAG) { | 
 | 		timer = origin; | 
 | 	} else { | 
 | 		time(&now); | 
 | 		timer = MAX(origin - now, 0); | 
 | 	} | 
 | 	if (timer > 60)	/* roundup */ | 
 | 		timer += 30; | 
 |  | 
 | 	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; | 
 | } |