L4Re/departure

libext2fs/lib/libsupport/mkquota.c

618:7123a7307a82
8 months ago Paul Boddie Introduced some debugging output control.
     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 }