L4Re/departure

libext2fs/lib/libsupport/mkquota.c

617:2733e5770ee9
9 months ago Paul Boddie Made the run command wait for completion, introducing the spawn command to run programs in the background. Introduced conveniences for waiting for the last job to be initiated and for piping from the last job, also subscribing to signals from pipe-supplying jobs so that they may be transparently removed from the job list upon completion. Augmented the job listing with the "+" notation familiar from Unix. Prevented new jobs from being started when no job slots are available.
     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 "quotaio.h"    19 #include "quotaio_v2.h"    20 #include "quotaio_tree.h"    21 #include "common.h"    22 #include "dict.h"    23     24 /* Needed for architectures where sizeof(int) != sizeof(void *) */    25 #define UINT_TO_VOIDPTR(val)  ((void *)(intptr_t)(val))    26 #define VOIDPTR_TO_UINT(ptr)  ((unsigned int)(intptr_t)(ptr))    27     28 #if DEBUG_QUOTA    29 static 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     49 static void print_dquot(const char *desc, struct dquot *dq)    50 {    51 	if (desc)    52 		fprintf(stderr, "%s: ", desc);    53 	fprintf(stderr, "%u %lld:%lld:%lld %lld:%lld:%lld\n",    54 		dq->dq_id, (long long) dq->dq_dqb.dqb_curspace,    55 		(long long) dq->dq_dqb.dqb_bsoftlimit,    56 		(long long) dq->dq_dqb.dqb_bhardlimit,    57 		(long long) dq->dq_dqb.dqb_curinodes,    58 		(long long) dq->dq_dqb.dqb_isoftlimit,    59 		(long long) dq->dq_dqb.dqb_ihardlimit);    60 }    61 #else    62 static void print_dquot(const char *desc EXT2FS_ATTR((unused)),    63 			struct dquot *dq EXT2FS_ATTR((unused)))    64 {    65 }    66 #endif    67     68 /*    69  * Returns 0 if not able to find the quota file, otherwise returns its    70  * inode number.    71  */    72 int quota_file_exists(ext2_filsys fs, enum quota_type qtype)    73 {    74 	char qf_name[256];    75 	errcode_t ret;    76 	ext2_ino_t ino;    77     78 	if (qtype >= MAXQUOTAS)    79 		return -EINVAL;    80     81 	quota_get_qf_name(qtype, QFMT_VFS_V1, qf_name);    82     83 	ret = ext2fs_lookup(fs, EXT2_ROOT_INO, qf_name, strlen(qf_name), 0,    84 			    &ino);    85 	if (ret)    86 		return 0;    87     88 	return ino;    89 }    90     91 /*    92  * Set the value for reserved quota inode number field in superblock.    93  */    94 void quota_set_sb_inum(ext2_filsys fs, ext2_ino_t ino, enum quota_type qtype)    95 {    96 	ext2_ino_t *inump;    97     98 	inump = quota_sb_inump(fs->super, qtype);    99    100 	log_debug("setting quota ino in superblock: ino=%u, type=%d", ino,   101 		 qtype);   102 	*inump = ino;   103 	ext2fs_mark_super_dirty(fs);   104 }   105    106 errcode_t quota_remove_inode(ext2_filsys fs, enum quota_type qtype)   107 {   108 	ext2_ino_t qf_ino;   109 	errcode_t	retval;   110    111 	retval = ext2fs_read_bitmaps(fs);   112 	if (retval) {   113 		log_debug("Couldn't read bitmaps: %s", error_message(retval));   114 		return retval;   115 	}   116    117 	qf_ino = *quota_sb_inump(fs->super, qtype);   118 	if (qf_ino == 0)   119 		return 0;   120 	retval = quota_inode_truncate(fs, qf_ino);   121 	if (retval)   122 		return retval;   123 	if (qf_ino >= EXT2_FIRST_INODE(fs->super)) {   124 		struct ext2_inode inode;   125    126 		retval = ext2fs_read_inode(fs, qf_ino, &inode);   127 		if (!retval) {   128 			memset(&inode, 0, sizeof(struct ext2_inode));   129 			ext2fs_write_inode(fs, qf_ino, &inode);   130 		}   131 		ext2fs_inode_alloc_stats2(fs, qf_ino, -1, 0);   132 		ext2fs_mark_ib_dirty(fs);   133    134 	}   135 	quota_set_sb_inum(fs, 0, qtype);   136    137 	ext2fs_mark_super_dirty(fs);   138 	fs->flags &= ~EXT2_FLAG_SUPER_ONLY;   139 	retval = ext2fs_write_bitmaps(fs);   140 	if (retval) {   141 		log_debug("Couldn't write bitmaps: %s", error_message(retval));   142 		return retval;   143 	}   144 	return 0;   145 }   146    147 static void write_dquots(dict_t *dict, struct quota_handle *qh)   148 {   149 	dnode_t		*n;   150 	struct dquot	*dq;   151    152 	for (n = dict_first(dict); n; n = dict_next(dict, n)) {   153 		dq = dnode_get(n);   154 		if (dq) {   155 			print_dquot("write", dq);   156 			dq->dq_h = qh;   157 			update_grace_times(dq);   158 			qh->qh_ops->commit_dquot(dq);   159 		}   160 	}   161 }   162    163 errcode_t quota_write_inode(quota_ctx_t qctx, unsigned int qtype_bits)   164 {   165 	int		retval = 0;   166 	enum quota_type	qtype;   167 	dict_t		*dict;   168 	ext2_filsys	fs;   169 	struct quota_handle *h = NULL;   170 	int		fmt = QFMT_VFS_V1;   171    172 	if (!qctx)   173 		return 0;   174    175 	fs = qctx->fs;   176 	retval = ext2fs_get_mem(sizeof(struct quota_handle), &h);   177 	if (retval) {   178 		log_debug("Unable to allocate quota handle: %s",   179 			error_message(retval));   180 		goto out;   181 	}   182    183 	retval = ext2fs_read_bitmaps(fs);   184 	if (retval) {   185 		log_debug("Couldn't read bitmaps: %s", error_message(retval));   186 		goto out;   187 	}   188    189 	for (qtype = 0; qtype < MAXQUOTAS; qtype++) {   190 		if (((1 << qtype) & qtype_bits) == 0)   191 			continue;   192    193 		dict = qctx->quota_dict[qtype];   194 		if (!dict)   195 			continue;   196    197 		retval = quota_file_create(h, fs, qtype, fmt);   198 		if (retval) {   199 			log_debug("Cannot initialize io on quotafile: %s",   200 				  error_message(retval));   201 			goto out;   202 		}   203    204 		write_dquots(dict, h);   205 		retval = quota_file_close(qctx, h);   206 		if (retval) {   207 			log_debug("Cannot finish IO on new quotafile: %s",   208 				  strerror(errno));   209 			if (h->qh_qf.e2_file)   210 				ext2fs_file_close(h->qh_qf.e2_file);   211 			(void) quota_inode_truncate(fs, h->qh_qf.ino);   212 			goto out;   213 		}   214    215 		/* Set quota inode numbers in superblock. */   216 		quota_set_sb_inum(fs, h->qh_qf.ino, qtype);   217 		ext2fs_mark_super_dirty(fs);   218 		ext2fs_mark_bb_dirty(fs);   219 		fs->flags &= ~EXT2_FLAG_SUPER_ONLY;   220 	}   221    222 	retval = ext2fs_write_bitmaps(fs);   223 	if (retval) {   224 		log_debug("Couldn't write bitmaps: %s", error_message(retval));   225 		goto out;   226 	}   227 out:   228 	if (h)   229 		ext2fs_free_mem(&h);   230 	return retval;   231 }   232    233 /******************************************************************/   234 /* Helper functions for computing quota in memory.                */   235 /******************************************************************/   236    237 static int dict_uint_cmp(const void *cmp_ctx, const void *a, const void *b)   238 {   239 	unsigned int	c, d;   240    241 	c = VOIDPTR_TO_UINT(a);   242 	d = VOIDPTR_TO_UINT(b);   243    244 	if (c == d)   245 		return 0;   246 	else if (c > d)   247 		return 1;   248 	else   249 		return -1;   250 }   251    252 static inline int project_quota_valid(quota_ctx_t qctx)   253 {   254 	return (EXT2_INODE_SIZE(qctx->fs->super) > EXT2_GOOD_OLD_INODE_SIZE);   255 }   256    257 static inline qid_t get_qid(struct ext2_inode_large *inode, enum quota_type qtype)   258 {   259 	unsigned int inode_size;   260    261 	switch (qtype) {   262 	case USRQUOTA:   263 		return inode_uid(*inode);   264 	case GRPQUOTA:   265 		return inode_gid(*inode);   266 	case PRJQUOTA:   267 		inode_size = EXT2_GOOD_OLD_INODE_SIZE +   268 			inode->i_extra_isize;   269 		if (inode_includes(inode_size, i_projid))   270 			return inode_projid(*inode);   271 		return 0;   272 	default:   273 		return 0;   274 	}   275    276 	return 0;   277 }   278    279 static void quota_dnode_free(dnode_t *node,   280 			     void *context EXT2FS_ATTR((unused)))   281 {   282 	void *ptr = node ? dnode_get(node) : 0;   283    284 	ext2fs_free_mem(&ptr);   285 	free(node);   286 }   287    288 /*   289  * Set up the quota tracking data structures.   290  */   291 errcode_t quota_init_context(quota_ctx_t *qctx, ext2_filsys fs,   292 			     unsigned int qtype_bits)   293 {   294 	errcode_t err;   295 	dict_t	*dict;   296 	quota_ctx_t ctx;   297 	enum quota_type	qtype;   298    299 	err = ext2fs_get_mem(sizeof(struct quota_ctx), &ctx);   300 	if (err) {   301 		log_debug("Failed to allocate quota context");   302 		return err;   303 	}   304    305 	memset(ctx, 0, sizeof(struct quota_ctx));   306 	for (qtype = 0; qtype < MAXQUOTAS; qtype++) {   307 		ctx->quota_file[qtype] = NULL;   308 		if (qtype_bits) {   309 			if (((1 << qtype) & qtype_bits) == 0)   310 				continue;   311 		} else {   312 			if (*quota_sb_inump(fs->super, qtype) == 0)   313 				continue;   314 		}   315 		err = ext2fs_get_mem(sizeof(dict_t), &dict);   316 		if (err) {   317 			log_debug("Failed to allocate dictionary");   318 			quota_release_context(&ctx);   319 			return err;   320 		}   321 		ctx->quota_dict[qtype] = dict;   322 		dict_init(dict, DICTCOUNT_T_MAX, dict_uint_cmp);   323 		dict_set_allocator(dict, NULL, quota_dnode_free, NULL);   324 	}   325    326 	ctx->fs = fs;   327 	*qctx = ctx;   328 	return 0;   329 }   330    331 void quota_release_context(quota_ctx_t *qctx)   332 {   333 	errcode_t err;   334 	dict_t	*dict;   335 	enum quota_type	qtype;   336 	quota_ctx_t ctx;   337    338 	if (!qctx)   339 		return;   340    341 	ctx = *qctx;   342 	for (qtype = 0; qtype < MAXQUOTAS; qtype++) {   343 		dict = ctx->quota_dict[qtype];   344 		ctx->quota_dict[qtype] = 0;   345 		if (dict) {   346 			dict_free_nodes(dict);   347 			free(dict);   348 		}   349 		if (ctx->quota_file[qtype]) {   350 			err = quota_file_close(ctx, ctx->quota_file[qtype]);   351 			if (err) {   352 				log_err("Cannot close quotafile: %s",   353 					strerror(errno));   354 				ext2fs_free_mem(&ctx->quota_file[qtype]);   355 			}   356 		}   357 	}   358 	*qctx = NULL;   359 	free(ctx);   360 }   361    362 static struct dquot *get_dq(dict_t *dict, __u32 key)   363 {   364 	struct dquot	*dq;   365 	dnode_t		*n;   366    367 	n = dict_lookup(dict, UINT_TO_VOIDPTR(key));   368 	if (n)   369 		dq = dnode_get(n);   370 	else {   371 		if (ext2fs_get_mem(sizeof(struct dquot), &dq)) {   372 			log_err("Unable to allocate dquot");   373 			return NULL;   374 		}   375 		memset(dq, 0, sizeof(struct dquot));   376 		dict_alloc_insert(dict, UINT_TO_VOIDPTR(key), dq);   377 		dq->dq_id = key;   378 	}   379 	return dq;   380 }   381    382    383 /*   384  * Called to update the blocks used by a particular inode   385  */   386 void quota_data_add(quota_ctx_t qctx, struct ext2_inode_large *inode,   387 		    ext2_ino_t ino EXT2FS_ATTR((unused)),   388 		    qsize_t space)   389 {   390 	struct dquot	*dq;   391 	dict_t		*dict;   392 	enum quota_type	qtype;   393    394 	if (!qctx)   395 		return;   396    397 	log_debug("ADD_DATA: Inode: %u, UID/GID: %u/%u, space: %ld", ino,   398 			inode_uid(*inode),   399 			inode_gid(*inode), space);   400 	for (qtype = 0; qtype < MAXQUOTAS; qtype++) {   401 		if (qtype == PRJQUOTA && !project_quota_valid(qctx))   402 			continue;   403 		dict = qctx->quota_dict[qtype];   404 		if (dict) {   405 			dq = get_dq(dict, get_qid(inode, qtype));   406 			if (dq)   407 				dq->dq_dqb.dqb_curspace += space;   408 		}   409 	}   410 }   411    412 /*   413  * Called to remove some blocks used by a particular inode   414  */   415 void quota_data_sub(quota_ctx_t qctx, struct ext2_inode_large *inode,   416 		    ext2_ino_t ino EXT2FS_ATTR((unused)),   417 		    qsize_t space)   418 {   419 	struct dquot	*dq;   420 	dict_t		*dict;   421 	enum quota_type	qtype;   422    423 	if (!qctx)   424 		return;   425    426 	log_debug("SUB_DATA: Inode: %u, UID/GID: %u/%u, space: %ld", ino,   427 			inode_uid(*inode),   428 			inode_gid(*inode), space);   429 	for (qtype = 0; qtype < MAXQUOTAS; qtype++) {   430 		if (qtype == PRJQUOTA && !project_quota_valid(qctx))   431 			continue;   432 		dict = qctx->quota_dict[qtype];   433 		if (dict) {   434 			dq = get_dq(dict, get_qid(inode, qtype));   435 			dq->dq_dqb.dqb_curspace -= space;   436 		}   437 	}   438 }   439    440 /*   441  * Called to count the files used by an inode's user/group   442  */   443 void quota_data_inodes(quota_ctx_t qctx, struct ext2_inode_large *inode,   444 		       ext2_ino_t ino EXT2FS_ATTR((unused)), int adjust)   445 {   446 	struct dquot	*dq;   447 	dict_t		*dict;   448 	enum quota_type	qtype;   449    450 	if (!qctx)   451 		return;   452    453 	log_debug("ADJ_INODE: Inode: %u, UID/GID: %u/%u, adjust: %d", ino,   454 			inode_uid(*inode),   455 			inode_gid(*inode), adjust);   456 	for (qtype = 0; qtype < MAXQUOTAS; qtype++) {   457 		if (qtype == PRJQUOTA && !project_quota_valid(qctx))   458 			continue;   459 		dict = qctx->quota_dict[qtype];   460 		if (dict) {   461 			dq = get_dq(dict, get_qid(inode, qtype));   462 			dq->dq_dqb.dqb_curinodes += adjust;   463 		}   464 	}   465 }   466    467 errcode_t quota_compute_usage(quota_ctx_t qctx)   468 {   469 	ext2_filsys fs;   470 	ext2_ino_t ino;   471 	errcode_t ret;   472 	struct ext2_inode_large *inode;   473 	int inode_size;   474 	qsize_t space;   475 	ext2_inode_scan scan;   476    477 	if (!qctx)   478 		return 0;   479    480 	fs = qctx->fs;   481 	ret = ext2fs_open_inode_scan(fs, 0, &scan);   482 	if (ret) {   483 		log_err("while opening inode scan. ret=%ld", ret);   484 		return ret;   485 	}   486 	inode_size = fs->super->s_inode_size;   487 	inode = malloc(inode_size);   488 	if (!inode) {   489 		ext2fs_close_inode_scan(scan);   490 		return ENOMEM;   491 	}   492 	while (1) {   493 		ret = ext2fs_get_next_inode_full(scan, &ino,   494 						 EXT2_INODE(inode), inode_size);   495 		if (ret) {   496 			log_err("while getting next inode. ret=%ld", ret);   497 			ext2fs_close_inode_scan(scan);   498 			free(inode);   499 			return ret;   500 		}   501 		if (ino == 0)   502 			break;   503 		if (inode->i_links_count &&   504 		    (ino == EXT2_ROOT_INO ||   505 		     ino >= EXT2_FIRST_INODE(fs->super))) {   506 			space = ext2fs_get_stat_i_blocks(fs,   507 						EXT2_INODE(inode)) << 9;   508 			quota_data_add(qctx, inode, ino, space);   509 			quota_data_inodes(qctx, inode, ino, +1);   510 		}   511 	}   512    513 	ext2fs_close_inode_scan(scan);   514 	free(inode);   515 	return 0;   516 }   517    518 struct scan_dquots_data {   519 	dict_t		*quota_dict;   520 	int             update_limits; /* update limits from disk */   521 	int		update_usage;   522 	int		check_consistency;   523 	int		usage_is_inconsistent;   524 };   525    526 static int scan_dquots_callback(struct dquot *dquot, void *cb_data)   527 {   528 	struct scan_dquots_data *scan_data = cb_data;   529 	dict_t *quota_dict = scan_data->quota_dict;   530 	struct dquot *dq;   531    532 	dq = get_dq(quota_dict, dquot->dq_id);   533 	dq->dq_id = dquot->dq_id;   534 	dq->dq_flags |= DQF_SEEN;   535    536 	print_dquot("mem", dq);   537 	print_dquot("dsk", dquot);   538    539 	/* Check if there is inconsistency */   540 	if (scan_data->check_consistency &&   541 	    (dq->dq_dqb.dqb_curspace != dquot->dq_dqb.dqb_curspace ||   542 	     dq->dq_dqb.dqb_curinodes != dquot->dq_dqb.dqb_curinodes)) {   543 		scan_data->usage_is_inconsistent = 1;   544 		fprintf(stderr, "[QUOTA WARNING] Usage inconsistent for ID %u:"   545 			"actual (%lld, %lld) != expected (%lld, %lld)\n",   546 			dq->dq_id, (long long) dq->dq_dqb.dqb_curspace,   547 			(long long) dq->dq_dqb.dqb_curinodes,   548 			(long long) dquot->dq_dqb.dqb_curspace,   549 			(long long) dquot->dq_dqb.dqb_curinodes);   550 	}   551    552 	if (scan_data->update_limits) {   553 		dq->dq_dqb.dqb_ihardlimit = dquot->dq_dqb.dqb_ihardlimit;   554 		dq->dq_dqb.dqb_isoftlimit = dquot->dq_dqb.dqb_isoftlimit;   555 		dq->dq_dqb.dqb_bhardlimit = dquot->dq_dqb.dqb_bhardlimit;   556 		dq->dq_dqb.dqb_bsoftlimit = dquot->dq_dqb.dqb_bsoftlimit;   557 	}   558    559 	if (scan_data->update_usage) {   560 		dq->dq_dqb.dqb_curspace = dquot->dq_dqb.dqb_curspace;   561 		dq->dq_dqb.dqb_curinodes = dquot->dq_dqb.dqb_curinodes;   562 	}   563    564 	return 0;   565 }   566    567 /*   568  * Read all dquots from quota file into memory   569  */   570 static errcode_t quota_read_all_dquots(struct quota_handle *qh,   571                                        quota_ctx_t qctx,   572 				       int update_limits EXT2FS_ATTR((unused)))   573 {   574 	struct scan_dquots_data scan_data;   575    576 	scan_data.quota_dict = qctx->quota_dict[qh->qh_type];   577 	scan_data.check_consistency = 0;   578 	scan_data.update_limits = 0;   579 	scan_data.update_usage = 1;   580    581 	return qh->qh_ops->scan_dquots(qh, scan_dquots_callback, &scan_data);   582 }   583    584 /*   585  * Write all memory dquots into quota file   586  */   587 #if 0 /* currently unused, but may be useful in the future? */   588 static errcode_t quota_write_all_dquots(struct quota_handle *qh,   589                                         quota_ctx_t qctx)   590 {   591 	errcode_t err;   592    593 	err = ext2fs_read_bitmaps(qctx->fs);   594 	if (err)   595 		return err;   596 	write_dquots(qctx->quota_dict[qh->qh_type], qh);   597 	ext2fs_mark_bb_dirty(qctx->fs);   598 	qctx->fs->flags &= ~EXT2_FLAG_SUPER_ONLY;   599 	ext2fs_write_bitmaps(qctx->fs);   600 	return 0;   601 }   602 #endif   603    604 /*   605  * Updates the in-memory quota limits from the given quota inode.   606  */   607 errcode_t quota_update_limits(quota_ctx_t qctx, ext2_ino_t qf_ino,   608 			      enum quota_type qtype)   609 {   610 	struct quota_handle *qh;   611 	errcode_t err;   612    613 	if (!qctx)   614 		return 0;   615    616 	err = ext2fs_get_mem(sizeof(struct quota_handle), &qh);   617 	if (err) {   618 		log_debug("Unable to allocate quota handle");   619 		return err;   620 	}   621    622 	err = quota_file_open(qctx, qh, qf_ino, qtype, -1, 0);   623 	if (err) {   624 		log_debug("Open quota file failed");   625 		goto out;   626 	}   627    628 	quota_read_all_dquots(qh, qctx, 1);   629    630 	err = quota_file_close(qctx, qh);   631 	if (err) {   632 		log_debug("Cannot finish IO on new quotafile: %s",   633 			strerror(errno));   634 		if (qh->qh_qf.e2_file)   635 			ext2fs_file_close(qh->qh_qf.e2_file);   636 	}   637 out:   638 	ext2fs_free_mem(&qh);   639 	return err;   640 }   641    642 /*   643  * Compares the measured quota in qctx->quota_dict with that in the quota inode   644  * on disk and updates the limits in qctx->quota_dict. 'usage_inconsistent' is   645  * set to 1 if the supplied and on-disk quota usage values are not identical.   646  */   647 errcode_t quota_compare_and_update(quota_ctx_t qctx, enum quota_type qtype,   648 				   int *usage_inconsistent)   649 {   650 	struct quota_handle qh;   651 	struct scan_dquots_data scan_data;   652 	struct dquot *dq;   653 	dnode_t *n;   654 	dict_t *dict = qctx->quota_dict[qtype];   655 	errcode_t err = 0;   656    657 	if (!dict)   658 		goto out;   659    660 	err = quota_file_open(qctx, &qh, 0, qtype, -1, 0);   661 	if (err) {   662 		log_debug("Open quota file failed");   663 		goto out;   664 	}   665    666 	scan_data.quota_dict = qctx->quota_dict[qtype];   667 	scan_data.update_limits = 1;   668 	scan_data.update_usage = 0;   669 	scan_data.check_consistency = 1;   670 	scan_data.usage_is_inconsistent = 0;   671 	err = qh.qh_ops->scan_dquots(&qh, scan_dquots_callback, &scan_data);   672 	if (err) {   673 		log_debug("Error scanning dquots");   674 		*usage_inconsistent = 1;   675 		goto out_close_qh;   676 	}   677    678 	for (n = dict_first(dict); n; n = dict_next(dict, n)) {   679 		dq = dnode_get(n);   680 		if (!dq)   681 			continue;   682 		if ((dq->dq_flags & DQF_SEEN) == 0) {   683 			fprintf(stderr, "[QUOTA WARNING] "   684 				"Missing quota entry ID %d\n", dq->dq_id);   685 			scan_data.usage_is_inconsistent = 1;   686 		}   687 	}   688 	*usage_inconsistent = scan_data.usage_is_inconsistent;   689    690 out_close_qh:   691 	err = quota_file_close(qctx, &qh);   692 	if (err) {   693 		log_debug("Cannot close quotafile: %s", error_message(errno));   694 		if (qh.qh_qf.e2_file)   695 			ext2fs_file_close(qh.qh_qf.e2_file);   696 	}   697 out:   698 	return err;   699 }   700    701 int parse_quota_opts(const char *opts, int (*func)(char *))   702 {   703 	char	*buf, *token, *next, *p;   704 	int	len;   705 	int	ret = 0;   706    707 	len = strlen(opts);   708 	buf = malloc(len + 1);   709 	if (!buf) {   710 		fprintf(stderr,   711 			"Couldn't allocate memory to parse quota options!\n");   712 		return -ENOMEM;   713 	}   714 	strcpy(buf, opts);   715 	for (token = buf; token && *token; token = next) {   716 		p = strchr(token, ',');   717 		next = 0;   718 		if (p) {   719 			*p = 0;   720 			next = p + 1;   721 		}   722 		ret = func(token);   723 		if (ret)   724 			break;   725 	}   726 	free(buf);   727 	return ret;   728 }