mkquota.c revision 7943ccf5f2fa76f1dc164ddd1ffd5044a1dca31a
1/*
2 * mkquota.c --- create quota files for a filesystem
3 *
4 * Aditya Kali <adityakali@google.com>
5 */
6#include "config.h"
7#include <sys/types.h>
8#include <sys/stat.h>
9#include <unistd.h>
10#include <errno.h>
11#include <string.h>
12#include <fcntl.h>
13
14#include "ext2fs/ext2_fs.h"
15#include "ext2fs/ext2fs.h"
16#include "e2p/e2p.h"
17
18#include "quota.h"
19#include "quotaio.h"
20#include "quotaio_v2.h"
21#include "quotaio_tree.h"
22#include "mkquota.h"
23#include "common.h"
24
25/* Needed for architectures where sizeof(int) != sizeof(void *) */
26#define UINT_TO_VOIDPTR(val)  ((void *)(intptr_t)(val))
27#define VOIDPTR_TO_UINT(ptr)  ((unsigned int)(intptr_t)(ptr))
28
29static void print_inode(struct ext2_inode *inode)
30{
31	if (!inode)
32		return;
33
34	fprintf(stderr, "  i_mode = %d\n", inode->i_mode);
35	fprintf(stderr, "  i_uid = %d\n", inode->i_uid);
36	fprintf(stderr, "  i_size = %d\n", inode->i_size);
37	fprintf(stderr, "  i_atime = %d\n", inode->i_atime);
38	fprintf(stderr, "  i_ctime = %d\n", inode->i_ctime);
39	fprintf(stderr, "  i_mtime = %d\n", inode->i_mtime);
40	fprintf(stderr, "  i_dtime = %d\n", inode->i_dtime);
41	fprintf(stderr, "  i_gid = %d\n", inode->i_gid);
42	fprintf(stderr, "  i_links_count = %d\n", inode->i_links_count);
43	fprintf(stderr, "  i_blocks = %d\n", inode->i_blocks);
44	fprintf(stderr, "  i_flags = %d\n", inode->i_flags);
45
46	return;
47}
48
49int quota_is_on(ext2_filsys fs, int type)
50{
51	char tmp[1024];
52	qid_t id = (type == USRQUOTA) ? getuid() : getgid();
53
54#ifdef HAVE_QUOTACTL
55	if (!quotactl(QCMD(Q_V2_GETQUOTA, type), fs->device_name, id, tmp))
56		return 1;
57#endif
58	return 0;
59}
60
61/*
62 * Returns 0 if not able to find the quota file, otherwise returns its
63 * inode number.
64 */
65int quota_file_exists(ext2_filsys fs, int qtype, int fmt)
66{
67	char qf_name[256];
68	errcode_t ret;
69	ext2_ino_t ino;
70
71	if (qtype >= MAXQUOTAS)
72		return -EINVAL;
73
74	quota_get_qf_name(qtype, fmt, qf_name);
75
76	ret = ext2fs_lookup(fs, EXT2_ROOT_INO, qf_name, strlen(qf_name), 0,
77			    &ino);
78	if (ret)
79		return 0;
80
81	return ino;
82}
83
84/*
85 * Set the value for reserved quota inode number field in superblock.
86 */
87void quota_set_sb_inum(ext2_filsys fs, ext2_ino_t ino, int qtype)
88{
89	ext2_ino_t *inump;
90
91	inump = (qtype == USRQUOTA) ? &fs->super->s_usr_quota_inum :
92		&fs->super->s_grp_quota_inum;
93
94	log_debug("setting quota ino in superblock: ino=%u, type=%d", ino,
95		 qtype);
96	*inump = ino;
97	ext2fs_mark_super_dirty(fs);
98}
99
100errcode_t quota_remove_inode(ext2_filsys fs, int qtype)
101{
102	ext2_ino_t qf_ino;
103
104	ext2fs_read_bitmaps(fs);
105	qf_ino = (qtype == USRQUOTA) ? fs->super->s_usr_quota_inum :
106		fs->super->s_grp_quota_inum;
107	quota_set_sb_inum(fs, 0, qtype);
108	/* Truncate the inode only if its a reserved one. */
109	if (qf_ino < EXT2_FIRST_INODE(fs->super))
110		quota_inode_truncate(fs, qf_ino);
111
112	ext2fs_mark_super_dirty(fs);
113	ext2fs_write_bitmaps(fs);
114	return 0;
115}
116
117static void write_dquots(dict_t *dict, struct quota_handle *qh)
118{
119	dnode_t		*n;
120	struct dquot	*dq;
121
122	for (n = dict_first(dict); n; n = dict_next(dict, n)) {
123		dq = dnode_get(n);
124		if (dq) {
125			dq->dq_h = qh;
126			update_grace_times(dq);
127			qh->qh_ops->commit_dquot(dq);
128		}
129	}
130}
131
132errcode_t quota_write_inode(quota_ctx_t qctx, int qtype)
133{
134	int		retval = 0, i;
135	dict_t		*dict;
136	ext2_filsys	fs;
137	struct quota_handle *h;
138	int		fmt = QFMT_VFS_V1;
139
140	if (!qctx)
141		return 0;
142
143	fs = qctx->fs;
144	retval = ext2fs_get_mem(sizeof(struct quota_handle), &h);
145	if (retval) {
146		log_err("Unable to allocate quota handle", "");
147		goto out;
148	}
149
150	ext2fs_read_bitmaps(fs);
151
152	for (i = 0; i < MAXQUOTAS; i++) {
153		if ((qtype != -1) && (i != qtype))
154			continue;
155
156		dict = qctx->quota_dict[i];
157		if (!dict)
158			continue;
159
160		retval = quota_file_create(h, fs, i, fmt);
161		if (retval < 0) {
162			log_err("Cannot initialize io on quotafile", "");
163			continue;
164		}
165
166		write_dquots(dict, h);
167		retval = quota_file_close(h);
168		if (retval < 0) {
169			log_err("Cannot finish IO on new quotafile: %s",
170				strerror(errno));
171			if (h->qh_qf.e2_file)
172				ext2fs_file_close(h->qh_qf.e2_file);
173			quota_inode_truncate(fs, h->qh_qf.ino);
174			continue;
175		}
176
177		/* Set quota inode numbers in superblock. */
178		quota_set_sb_inum(fs, h->qh_qf.ino, i);
179		ext2fs_mark_super_dirty(fs);
180		ext2fs_mark_bb_dirty(fs);
181		fs->flags &= ~EXT2_FLAG_SUPER_ONLY;
182	}
183
184	ext2fs_write_bitmaps(fs);
185out:
186	if (h)
187		ext2fs_free_mem(&h);
188	return retval;
189}
190
191/******************************************************************/
192/* Helper functions for computing quota in memory.                */
193/******************************************************************/
194
195static int dict_uint_cmp(const void *a, const void *b)
196{
197	unsigned int	c, d;
198
199	c = VOIDPTR_TO_UINT(a);
200	d = VOIDPTR_TO_UINT(b);
201
202	return c - d;
203}
204
205static inline qid_t get_qid(struct ext2_inode *inode, int qtype)
206{
207	if (qtype == USRQUOTA)
208		return inode_uid(*inode);
209	return inode_gid(*inode);
210}
211
212static void quota_dnode_free(dnode_t *node,
213			     void *context EXT2FS_ATTR((unused)))
214{
215	void *ptr = node ? dnode_get(node) : 0;
216
217	ext2fs_free_mem(&ptr);
218	free(node);
219}
220
221/*
222 * Set up the quota tracking data structures.
223 */
224errcode_t quota_init_context(quota_ctx_t *qctx, ext2_filsys fs, int qtype)
225{
226	int	i, err = 0;
227	dict_t	*dict;
228	quota_ctx_t ctx;
229
230	err = ext2fs_get_mem(sizeof(struct quota_ctx), &ctx);
231	if (err) {
232		log_err("Failed to allocate quota context", "");
233		return err;
234	}
235
236	memset(ctx, 0, sizeof(struct quota_ctx));
237	for (i = 0; i < MAXQUOTAS; i++) {
238		if ((qtype != -1) && (i != qtype))
239			continue;
240		err = ext2fs_get_mem(sizeof(dict_t), &dict);
241		if (err) {
242			log_err("Failed to allocate dictionary", "");
243			return err;
244		}
245		ctx->quota_dict[i] = dict;
246		dict_init(dict, DICTCOUNT_T_MAX, dict_uint_cmp);
247		dict_set_allocator(dict, NULL, quota_dnode_free, NULL);
248	}
249
250	ctx->fs = fs;
251	*qctx = ctx;
252	return 0;
253}
254
255void quota_release_context(quota_ctx_t *qctx)
256{
257	dict_t	*dict;
258	int	i;
259	quota_ctx_t ctx;
260
261	if (!qctx)
262		return;
263
264	ctx = *qctx;
265	for (i = 0; i < MAXQUOTAS; i++) {
266		dict = ctx->quota_dict[i];
267		ctx->quota_dict[i] = 0;
268		if (dict) {
269			dict_free_nodes(dict);
270			free(dict);
271		}
272	}
273	*qctx = NULL;
274	free(ctx);
275}
276
277static struct dquot *get_dq(dict_t *dict, __u32 key)
278{
279	struct dquot	*dq;
280	dnode_t		*n;
281
282	n = dict_lookup(dict, UINT_TO_VOIDPTR(key));
283	if (n)
284		dq = dnode_get(n);
285	else {
286		if (ext2fs_get_mem(sizeof(struct dquot), &dq)) {
287			log_err("Unable to allocate dquot", "");
288			return NULL;
289		}
290		memset(dq, 0, sizeof(struct dquot));
291		dict_alloc_insert(dict, UINT_TO_VOIDPTR(key), dq);
292		dq->dq_id = key;
293	}
294	return dq;
295}
296
297
298/*
299 * Called to update the blocks used by a particular inode
300 */
301void quota_data_add(quota_ctx_t qctx, struct ext2_inode *inode, ext2_ino_t ino,
302		    qsize_t space)
303{
304	struct dquot	*dq;
305	dict_t		*dict;
306	int		i;
307
308	if (!qctx)
309		return;
310
311	log_debug("ADD_DATA: Inode: %u, UID/GID: %u/%u, space: %ld", ino,
312			inode_uid(*inode),
313			inode_gid(*inode), space);
314	for (i = 0; i < MAXQUOTAS; i++) {
315		dict = qctx->quota_dict[i];
316		if (dict) {
317			dq = get_dq(dict, get_qid(inode, i));
318			if (dq)
319				dq->dq_dqb.dqb_curspace += space;
320		}
321	}
322}
323
324/*
325 * Called to remove some blocks used by a particular inode
326 */
327void quota_data_sub(quota_ctx_t qctx, struct ext2_inode *inode, ext2_ino_t ino,
328		    qsize_t space)
329{
330	struct dquot	*dq;
331	dict_t		*dict;
332	int		i;
333
334	if (!qctx)
335		return;
336
337	log_debug("SUB_DATA: Inode: %u, UID/GID: %u/%u, space: %ld", ino,
338			inode_uid(*inode),
339			inode_gid(*inode), space);
340	for (i = 0; i < MAXQUOTAS; i++) {
341		dict = qctx->quota_dict[i];
342		if (dict) {
343			dq = get_dq(dict, get_qid(inode, i));
344			dq->dq_dqb.dqb_curspace -= space;
345		}
346	}
347}
348
349/*
350 * Called to count the files used by an inode's user/group
351 */
352void quota_data_inodes(quota_ctx_t qctx, struct ext2_inode *inode,
353		       ext2_ino_t ino, int adjust)
354{
355	struct dquot	*dq;
356	dict_t		*dict;
357	int		i;
358
359	if (!qctx)
360		return;
361
362	log_debug("ADJ_INODE: Inode: %u, UID/GID: %u/%u, adjust: %d", ino,
363			inode_uid(*inode),
364			inode_gid(*inode), adjust);
365	for (i = 0; i < MAXQUOTAS; i++) {
366		dict = qctx->quota_dict[i];
367		if (dict) {
368			dq = get_dq(dict, get_qid(inode, i));
369			dq->dq_dqb.dqb_curinodes += adjust;
370		}
371	}
372}
373
374errcode_t quota_compute_usage(quota_ctx_t qctx)
375{
376	ext2_filsys fs;
377	ext2_ino_t ino;
378	errcode_t ret;
379	struct ext2_inode inode;
380	qsize_t space;
381	ext2_inode_scan scan;
382
383	if (!qctx)
384		return 0;
385
386	fs = qctx->fs;
387	ret = ext2fs_open_inode_scan(fs, 0, &scan);
388	if (ret) {
389		log_err("while opening inode scan. ret=%ld", ret);
390		return ret;
391	}
392
393	while (1) {
394		ret = ext2fs_get_next_inode(scan, &ino, &inode);
395		if (ret) {
396			log_err("while getting next inode. ret=%ld", ret);
397			ext2fs_close_inode_scan(scan);
398			return ret;
399		}
400		if (ino == 0)
401			break;
402		if (inode.i_links_count) {
403			space = ext2fs_inode_i_blocks(fs, &inode) << 9;
404			quota_data_add(qctx, &inode, ino, space);
405			quota_data_inodes(qctx, &inode, ino, +1);
406		}
407	}
408
409	ext2fs_close_inode_scan(scan);
410
411	return 0;
412}
413
414struct scan_dquots_data {
415	dict_t		*quota_dict;
416	int             update_limits; /* update limits from disk */
417	int		update_usage;
418	int		usage_is_inconsistent;
419};
420
421static int scan_dquots_callback(struct dquot *dquot, void *cb_data)
422{
423	struct scan_dquots_data *scan_data = cb_data;
424	dict_t *quota_dict = scan_data->quota_dict;
425	struct dquot *dq;
426
427	dq = get_dq(quota_dict, dquot->dq_id);
428	dq->dq_id = dquot->dq_id;
429
430	/* Check if there is inconsistancy. */
431	if (dq->dq_dqb.dqb_curspace != dquot->dq_dqb.dqb_curspace ||
432	    dq->dq_dqb.dqb_curinodes != dquot->dq_dqb.dqb_curinodes) {
433		scan_data->usage_is_inconsistent = 1;
434		log_err("Usage inconsistent for ID %d: (%llu, %llu) != "
435			"(%llu,	%llu)", dq->dq_id, dq->dq_dqb.dqb_curspace,
436			dq->dq_dqb.dqb_curinodes, dquot->dq_dqb.dqb_curspace,
437			dquot->dq_dqb.dqb_curinodes);
438	}
439
440	if (scan_data->update_limits) {
441		dq->dq_dqb.dqb_ihardlimit = dquot->dq_dqb.dqb_ihardlimit;
442		dq->dq_dqb.dqb_isoftlimit = dquot->dq_dqb.dqb_isoftlimit;
443		dq->dq_dqb.dqb_bhardlimit = dquot->dq_dqb.dqb_bhardlimit;
444		dq->dq_dqb.dqb_bsoftlimit = dquot->dq_dqb.dqb_bsoftlimit;
445	}
446
447	if (scan_data->update_usage) {
448		dq->dq_dqb.dqb_curspace = dquot->dq_dqb.dqb_curspace;
449		dq->dq_dqb.dqb_curinodes = dquot->dq_dqb.dqb_curinodes;
450	}
451
452	return 0;
453}
454
455/*
456 * Read all dquots from quota file into memory
457 */
458static errcode_t quota_read_all_dquots(struct quota_handle *qh,
459                                       quota_ctx_t qctx, int update_limits)
460{
461	struct scan_dquots_data scan_data;
462
463	scan_data.quota_dict = qctx->quota_dict[qh->qh_type];
464	scan_data.update_limits = update_limits;
465	scan_data.update_usage = 0;
466
467	return qh->qh_ops->scan_dquots(qh, scan_dquots_callback, &scan_data);
468}
469
470/*
471 * Write all memory dquots into quota file
472 */
473static errcode_t quota_write_all_dquots(struct quota_handle *qh,
474                                        quota_ctx_t qctx)
475{
476	errcode_t err;
477
478	err = ext2fs_read_bitmaps(qctx->fs);
479	if (err)
480		return err;
481	write_dquots(qctx->quota_dict[qh->qh_type], qh);
482	ext2fs_mark_bb_dirty(qctx->fs);
483	qctx->fs->flags &= ~EXT2_FLAG_SUPER_ONLY;
484	ext2fs_write_bitmaps(qctx->fs);
485	return 0;
486}
487
488/*
489 * Update usage of in quota file, limits keep unchaged
490 */
491errcode_t quota_update_inode(quota_ctx_t qctx, ext2_ino_t qf_ino, int type)
492{
493	struct quota_handle *qh;
494	errcode_t err;
495
496	if (!qctx)
497		return 0;
498
499	err = ext2fs_get_mem(sizeof(struct quota_handle), &qh);
500	if (err) {
501		log_err("Unable to allocate quota handle", "");
502		return err;
503	}
504
505	err = quota_file_open(qh, qctx->fs, qf_ino, type, -1, EXT2_FILE_WRITE);
506	if (err) {
507		log_err("Open quota file failed", "");
508		goto out;
509	}
510
511	quota_read_all_dquots(qh, qctx, 1);
512	quota_write_all_dquots(qh, qctx);
513
514	err = quota_file_close(qh);
515	if (err) {
516		log_err("Cannot finish IO on new quotafile: %s",
517			strerror(errno));
518		if (qh->qh_qf.e2_file)
519			ext2fs_file_close(qh->qh_qf.e2_file);
520	}
521out:
522	ext2fs_free_mem(&qh);
523	return err;
524}
525
526/*
527 * Compares the measured quota in qctx->quota_dict with that in the quota inode
528 * on disk and updates the limits in qctx->quota_dict. 'usage_inconsistent' is
529 * set to 1 if the supplied and on-disk quota usage values are not identical.
530 */
531errcode_t quota_compare_and_update(quota_ctx_t qctx, int qtype,
532				   int *usage_inconsistent)
533{
534	ext2_filsys fs = qctx->fs;
535	struct quota_handle qh;
536	struct scan_dquots_data scan_data;
537	ext2_ino_t qf_ino;
538	errcode_t err = 0;
539
540	if (!qctx->quota_dict[qtype])
541		goto out;
542
543	qf_ino = qtype == USRQUOTA ? fs->super->s_usr_quota_inum :
544				     fs->super->s_grp_quota_inum;
545	err = quota_file_open(&qh, fs, qf_ino, qtype, -1, 0);
546	if (err) {
547		log_err("Open quota file failed", "");
548		goto out;
549	}
550
551	scan_data.quota_dict = qctx->quota_dict[qtype];
552	scan_data.update_limits = 1;
553	scan_data.update_usage = 0;
554	scan_data.usage_is_inconsistent = 0;
555	err = qh.qh_ops->scan_dquots(&qh, scan_dquots_callback, &scan_data);
556	if (err) {
557		log_err("Error scanning dquots", "");
558		goto out;
559	}
560	*usage_inconsistent = scan_data.usage_is_inconsistent;
561
562out:
563	return err;
564}
565