paul@181 | 1 | /** quotaio.c |
paul@181 | 2 | * |
paul@181 | 3 | * Generic IO operations on quotafiles |
paul@181 | 4 | * Jan Kara <jack@suse.cz> - sponsored by SuSE CR |
paul@181 | 5 | * Aditya Kali <adityakali@google.com> - Ported to e2fsprogs |
paul@181 | 6 | */ |
paul@181 | 7 | |
paul@181 | 8 | #include "config.h" |
paul@181 | 9 | #include <stdio.h> |
paul@181 | 10 | #include <errno.h> |
paul@181 | 11 | #include <string.h> |
paul@181 | 12 | #include <unistd.h> |
paul@181 | 13 | #include <stdlib.h> |
paul@181 | 14 | #include <time.h> |
paul@181 | 15 | #include <sys/types.h> |
paul@181 | 16 | #include <sys/stat.h> |
paul@181 | 17 | #include <sys/file.h> |
paul@181 | 18 | #include <assert.h> |
paul@181 | 19 | |
paul@181 | 20 | #include "common.h" |
paul@181 | 21 | #include "quotaio.h" |
paul@181 | 22 | |
paul@181 | 23 | static const char * const extensions[MAXQUOTAS] = { |
paul@181 | 24 | [USRQUOTA] = "user", |
paul@181 | 25 | [GRPQUOTA] = "group", |
paul@181 | 26 | [PRJQUOTA] = "project", |
paul@181 | 27 | }; |
paul@181 | 28 | static const char * const basenames[] = { |
paul@181 | 29 | "", /* undefined */ |
paul@181 | 30 | "quota", /* QFMT_VFS_OLD */ |
paul@181 | 31 | "aquota", /* QFMT_VFS_V0 */ |
paul@181 | 32 | "", /* QFMT_OCFS2 */ |
paul@181 | 33 | "aquota" /* QFMT_VFS_V1 */ |
paul@181 | 34 | }; |
paul@181 | 35 | |
paul@181 | 36 | /* Header in all newer quotafiles */ |
paul@181 | 37 | struct disk_dqheader { |
paul@181 | 38 | __le32 dqh_magic; |
paul@181 | 39 | __le32 dqh_version; |
paul@181 | 40 | } __attribute__ ((packed)); |
paul@181 | 41 | |
paul@181 | 42 | /** |
paul@181 | 43 | * Convert type of quota to written representation |
paul@181 | 44 | */ |
paul@181 | 45 | const char *quota_type2name(enum quota_type qtype) |
paul@181 | 46 | { |
paul@181 | 47 | if (qtype >= MAXQUOTAS) |
paul@181 | 48 | return "unknown"; |
paul@181 | 49 | return extensions[qtype]; |
paul@181 | 50 | } |
paul@181 | 51 | |
paul@181 | 52 | ext2_ino_t quota_type2inum(enum quota_type qtype, |
paul@181 | 53 | struct ext2_super_block *sb) |
paul@181 | 54 | { |
paul@181 | 55 | switch (qtype) { |
paul@181 | 56 | case USRQUOTA: |
paul@181 | 57 | return EXT4_USR_QUOTA_INO; |
paul@181 | 58 | case GRPQUOTA: |
paul@181 | 59 | return EXT4_GRP_QUOTA_INO; |
paul@181 | 60 | case PRJQUOTA: |
paul@181 | 61 | return sb->s_prj_quota_inum; |
paul@181 | 62 | default: |
paul@181 | 63 | return 0; |
paul@181 | 64 | } |
paul@181 | 65 | return 0; |
paul@181 | 66 | } |
paul@181 | 67 | |
paul@181 | 68 | /** |
paul@181 | 69 | * Creates a quota file name for given type and format. |
paul@181 | 70 | */ |
paul@181 | 71 | const char *quota_get_qf_name(enum quota_type type, int fmt, char *buf) |
paul@181 | 72 | { |
paul@181 | 73 | if (!buf) |
paul@181 | 74 | return NULL; |
paul@181 | 75 | snprintf(buf, QUOTA_NAME_LEN, "%s.%s", |
paul@181 | 76 | basenames[fmt], extensions[type]); |
paul@181 | 77 | |
paul@181 | 78 | return buf; |
paul@181 | 79 | } |
paul@181 | 80 | |
paul@181 | 81 | /* |
paul@181 | 82 | * Set grace time if needed |
paul@181 | 83 | */ |
paul@181 | 84 | void update_grace_times(struct dquot *q) |
paul@181 | 85 | { |
paul@181 | 86 | time_t now; |
paul@181 | 87 | |
paul@181 | 88 | time(&now); |
paul@181 | 89 | if (q->dq_dqb.dqb_bsoftlimit && toqb(q->dq_dqb.dqb_curspace) > |
paul@181 | 90 | q->dq_dqb.dqb_bsoftlimit) { |
paul@181 | 91 | if (!q->dq_dqb.dqb_btime) |
paul@181 | 92 | q->dq_dqb.dqb_btime = |
paul@181 | 93 | now + q->dq_h->qh_info.dqi_bgrace; |
paul@181 | 94 | } else { |
paul@181 | 95 | q->dq_dqb.dqb_btime = 0; |
paul@181 | 96 | } |
paul@181 | 97 | |
paul@181 | 98 | if (q->dq_dqb.dqb_isoftlimit && q->dq_dqb.dqb_curinodes > |
paul@181 | 99 | q->dq_dqb.dqb_isoftlimit) { |
paul@181 | 100 | if (!q->dq_dqb.dqb_itime) |
paul@181 | 101 | q->dq_dqb.dqb_itime = |
paul@181 | 102 | now + q->dq_h->qh_info.dqi_igrace; |
paul@181 | 103 | } else { |
paul@181 | 104 | q->dq_dqb.dqb_itime = 0; |
paul@181 | 105 | } |
paul@181 | 106 | } |
paul@181 | 107 | |
paul@181 | 108 | static int compute_num_blocks_proc(ext2_filsys fs EXT2FS_ATTR((unused)), |
paul@181 | 109 | blk64_t *blocknr EXT2FS_ATTR((unused)), |
paul@181 | 110 | e2_blkcnt_t blockcnt EXT2FS_ATTR((unused)), |
paul@181 | 111 | blk64_t ref_block EXT2FS_ATTR((unused)), |
paul@181 | 112 | int ref_offset EXT2FS_ATTR((unused)), |
paul@181 | 113 | void *private) |
paul@181 | 114 | { |
paul@181 | 115 | blk64_t *num_blocks = private; |
paul@181 | 116 | |
paul@181 | 117 | *num_blocks += 1; |
paul@181 | 118 | return 0; |
paul@181 | 119 | } |
paul@181 | 120 | |
paul@181 | 121 | errcode_t quota_inode_truncate(ext2_filsys fs, ext2_ino_t ino) |
paul@181 | 122 | { |
paul@181 | 123 | struct ext2_inode inode; |
paul@181 | 124 | errcode_t err; |
paul@181 | 125 | enum quota_type qtype; |
paul@181 | 126 | |
paul@181 | 127 | if ((err = ext2fs_read_inode(fs, ino, &inode))) |
paul@181 | 128 | return err; |
paul@181 | 129 | |
paul@181 | 130 | for (qtype = 0; qtype < MAXQUOTAS; qtype++) |
paul@181 | 131 | if (ino == quota_type2inum(qtype, fs->super)) |
paul@181 | 132 | break; |
paul@181 | 133 | |
paul@181 | 134 | if (qtype != MAXQUOTAS) { |
paul@181 | 135 | inode.i_dtime = fs->now ? fs->now : time(0); |
paul@181 | 136 | if (!ext2fs_inode_has_valid_blocks2(fs, &inode)) |
paul@181 | 137 | return 0; |
paul@181 | 138 | err = ext2fs_punch(fs, ino, &inode, NULL, 0, ~0ULL); |
paul@181 | 139 | if (err) |
paul@181 | 140 | return err; |
paul@181 | 141 | fs->flags &= ~EXT2_FLAG_SUPER_ONLY; |
paul@181 | 142 | memset(&inode, 0, sizeof(struct ext2_inode)); |
paul@181 | 143 | } else { |
paul@181 | 144 | inode.i_flags &= ~EXT2_IMMUTABLE_FL; |
paul@181 | 145 | } |
paul@181 | 146 | err = ext2fs_write_inode(fs, ino, &inode); |
paul@181 | 147 | return err; |
paul@181 | 148 | } |
paul@181 | 149 | |
paul@181 | 150 | static ext2_off64_t compute_inode_size(ext2_filsys fs, ext2_ino_t ino) |
paul@181 | 151 | { |
paul@181 | 152 | blk64_t num_blocks = 0; |
paul@181 | 153 | |
paul@181 | 154 | ext2fs_block_iterate3(fs, ino, |
paul@181 | 155 | BLOCK_FLAG_READ_ONLY, |
paul@181 | 156 | NULL, |
paul@181 | 157 | compute_num_blocks_proc, |
paul@181 | 158 | &num_blocks); |
paul@181 | 159 | return num_blocks * fs->blocksize; |
paul@181 | 160 | } |
paul@181 | 161 | |
paul@181 | 162 | /* Functions to read/write quota file. */ |
paul@181 | 163 | static unsigned int quota_write_nomount(struct quota_file *qf, |
paul@181 | 164 | ext2_loff_t offset, |
paul@181 | 165 | void *buf, unsigned int size) |
paul@181 | 166 | { |
paul@181 | 167 | ext2_file_t e2_file = qf->e2_file; |
paul@181 | 168 | unsigned int bytes_written = 0; |
paul@181 | 169 | errcode_t err; |
paul@181 | 170 | |
paul@181 | 171 | err = ext2fs_file_llseek(e2_file, offset, EXT2_SEEK_SET, NULL); |
paul@181 | 172 | if (err) { |
paul@181 | 173 | log_err("ext2fs_file_llseek failed: %ld", err); |
paul@181 | 174 | return 0; |
paul@181 | 175 | } |
paul@181 | 176 | |
paul@181 | 177 | err = ext2fs_file_write(e2_file, buf, size, &bytes_written); |
paul@181 | 178 | if (err) { |
paul@181 | 179 | log_err("ext2fs_file_write failed: %ld", err); |
paul@181 | 180 | return 0; |
paul@181 | 181 | } |
paul@181 | 182 | |
paul@181 | 183 | /* Correct inode.i_size is set in end_io. */ |
paul@181 | 184 | return bytes_written; |
paul@181 | 185 | } |
paul@181 | 186 | |
paul@181 | 187 | static unsigned int quota_read_nomount(struct quota_file *qf, |
paul@181 | 188 | ext2_loff_t offset, |
paul@181 | 189 | void *buf, unsigned int size) |
paul@181 | 190 | { |
paul@181 | 191 | ext2_file_t e2_file = qf->e2_file; |
paul@181 | 192 | unsigned int bytes_read = 0; |
paul@181 | 193 | errcode_t err; |
paul@181 | 194 | |
paul@181 | 195 | err = ext2fs_file_llseek(e2_file, offset, EXT2_SEEK_SET, NULL); |
paul@181 | 196 | if (err) { |
paul@181 | 197 | log_err("ext2fs_file_llseek failed: %ld", err); |
paul@181 | 198 | return 0; |
paul@181 | 199 | } |
paul@181 | 200 | |
paul@181 | 201 | err = ext2fs_file_read(e2_file, buf, size, &bytes_read); |
paul@181 | 202 | if (err) { |
paul@181 | 203 | log_err("ext2fs_file_read failed: %ld", err); |
paul@181 | 204 | return 0; |
paul@181 | 205 | } |
paul@181 | 206 | |
paul@181 | 207 | return bytes_read; |
paul@181 | 208 | } |
paul@181 | 209 | |
paul@181 | 210 | /* |
paul@181 | 211 | * Detect quota format and initialize quota IO |
paul@181 | 212 | */ |
paul@181 | 213 | errcode_t quota_file_open(quota_ctx_t qctx, struct quota_handle *h, |
paul@181 | 214 | ext2_ino_t qf_ino, enum quota_type qtype, |
paul@181 | 215 | int fmt, int flags) |
paul@181 | 216 | { |
paul@181 | 217 | ext2_filsys fs = qctx->fs; |
paul@181 | 218 | ext2_file_t e2_file; |
paul@181 | 219 | errcode_t err; |
paul@181 | 220 | int allocated_handle = 0; |
paul@181 | 221 | |
paul@181 | 222 | if (qtype >= MAXQUOTAS) |
paul@181 | 223 | return EINVAL; |
paul@181 | 224 | |
paul@181 | 225 | if (fmt == -1) |
paul@181 | 226 | fmt = QFMT_VFS_V1; |
paul@181 | 227 | |
paul@181 | 228 | err = ext2fs_read_bitmaps(fs); |
paul@181 | 229 | if (err) |
paul@181 | 230 | return err; |
paul@181 | 231 | |
paul@181 | 232 | if (qf_ino == 0) |
paul@181 | 233 | qf_ino = *quota_sb_inump(fs->super, qtype); |
paul@181 | 234 | |
paul@181 | 235 | log_debug("Opening quota ino=%u, type=%d", qf_ino, qtype); |
paul@181 | 236 | err = ext2fs_file_open(fs, qf_ino, flags, &e2_file); |
paul@181 | 237 | if (err) { |
paul@181 | 238 | log_err("ext2fs_file_open failed: %s", error_message(err)); |
paul@181 | 239 | return err; |
paul@181 | 240 | } |
paul@181 | 241 | |
paul@181 | 242 | if (!h) { |
paul@181 | 243 | if (qctx->quota_file[qtype]) { |
paul@181 | 244 | h = qctx->quota_file[qtype]; |
paul@181 | 245 | if (((flags & EXT2_FILE_WRITE) == 0) || |
paul@181 | 246 | (h->qh_file_flags & EXT2_FILE_WRITE)) { |
paul@181 | 247 | ext2fs_file_close(e2_file); |
paul@181 | 248 | return 0; |
paul@181 | 249 | } |
paul@181 | 250 | (void) quota_file_close(qctx, h); |
paul@181 | 251 | } |
paul@181 | 252 | err = ext2fs_get_mem(sizeof(struct quota_handle), &h); |
paul@181 | 253 | if (err) { |
paul@181 | 254 | log_err("Unable to allocate quota handle"); |
paul@181 | 255 | ext2fs_file_close(e2_file); |
paul@181 | 256 | return err; |
paul@181 | 257 | } |
paul@181 | 258 | allocated_handle = 1; |
paul@181 | 259 | } |
paul@181 | 260 | |
paul@181 | 261 | h->qh_qf.e2_file = e2_file; |
paul@181 | 262 | h->qh_qf.fs = fs; |
paul@181 | 263 | h->qh_qf.ino = qf_ino; |
paul@181 | 264 | h->e2fs_write = quota_write_nomount; |
paul@181 | 265 | h->e2fs_read = quota_read_nomount; |
paul@181 | 266 | h->qh_file_flags = flags; |
paul@181 | 267 | h->qh_io_flags = 0; |
paul@181 | 268 | h->qh_type = qtype; |
paul@181 | 269 | h->qh_fmt = fmt; |
paul@181 | 270 | memset(&h->qh_info, 0, sizeof(h->qh_info)); |
paul@181 | 271 | h->qh_ops = "afile_ops_2; |
paul@181 | 272 | |
paul@181 | 273 | if (h->qh_ops->check_file && |
paul@181 | 274 | (h->qh_ops->check_file(h, qtype, fmt) == 0)) { |
paul@181 | 275 | log_err("qh_ops->check_file failed"); |
paul@181 | 276 | err = EIO; |
paul@181 | 277 | goto errout; |
paul@181 | 278 | } |
paul@181 | 279 | |
paul@181 | 280 | if (h->qh_ops->init_io && (h->qh_ops->init_io(h) < 0)) { |
paul@181 | 281 | log_err("qh_ops->init_io failed"); |
paul@181 | 282 | err = EIO; |
paul@181 | 283 | goto errout; |
paul@181 | 284 | } |
paul@181 | 285 | if (allocated_handle) |
paul@181 | 286 | qctx->quota_file[qtype] = h; |
paul@181 | 287 | |
paul@181 | 288 | return 0; |
paul@181 | 289 | errout: |
paul@181 | 290 | ext2fs_file_close(e2_file); |
paul@181 | 291 | if (allocated_handle) |
paul@181 | 292 | ext2fs_free_mem(&h); |
paul@181 | 293 | return err; |
paul@181 | 294 | } |
paul@181 | 295 | |
paul@181 | 296 | static errcode_t quota_inode_init_new(ext2_filsys fs, ext2_ino_t ino) |
paul@181 | 297 | { |
paul@181 | 298 | struct ext2_inode inode; |
paul@181 | 299 | errcode_t err = 0; |
paul@181 | 300 | |
paul@181 | 301 | err = ext2fs_read_inode(fs, ino, &inode); |
paul@181 | 302 | if (err) { |
paul@181 | 303 | log_err("ex2fs_read_inode failed"); |
paul@181 | 304 | return err; |
paul@181 | 305 | } |
paul@181 | 306 | |
paul@181 | 307 | if (EXT2_I_SIZE(&inode)) { |
paul@181 | 308 | err = quota_inode_truncate(fs, ino); |
paul@181 | 309 | if (err) |
paul@181 | 310 | return err; |
paul@181 | 311 | } |
paul@181 | 312 | |
paul@181 | 313 | memset(&inode, 0, sizeof(struct ext2_inode)); |
paul@181 | 314 | ext2fs_iblk_set(fs, &inode, 0); |
paul@181 | 315 | inode.i_atime = inode.i_mtime = |
paul@181 | 316 | inode.i_ctime = fs->now ? fs->now : time(0); |
paul@181 | 317 | inode.i_links_count = 1; |
paul@181 | 318 | inode.i_mode = LINUX_S_IFREG | 0600; |
paul@181 | 319 | inode.i_flags |= EXT2_IMMUTABLE_FL; |
paul@181 | 320 | if (ext2fs_has_feature_extents(fs->super)) |
paul@181 | 321 | inode.i_flags |= EXT4_EXTENTS_FL; |
paul@181 | 322 | |
paul@181 | 323 | err = ext2fs_write_new_inode(fs, ino, &inode); |
paul@181 | 324 | if (err) { |
paul@181 | 325 | log_err("ext2fs_write_new_inode failed: %ld", err); |
paul@181 | 326 | return err; |
paul@181 | 327 | } |
paul@181 | 328 | return err; |
paul@181 | 329 | } |
paul@181 | 330 | |
paul@181 | 331 | /* |
paul@181 | 332 | * Create new quotafile of specified format on given filesystem |
paul@181 | 333 | */ |
paul@181 | 334 | errcode_t quota_file_create(struct quota_handle *h, ext2_filsys fs, |
paul@181 | 335 | enum quota_type qtype, int fmt) |
paul@181 | 336 | { |
paul@181 | 337 | ext2_file_t e2_file; |
paul@181 | 338 | errcode_t err; |
paul@181 | 339 | ext2_ino_t qf_inum = 0; |
paul@181 | 340 | |
paul@181 | 341 | if (fmt == -1) |
paul@181 | 342 | fmt = QFMT_VFS_V1; |
paul@181 | 343 | |
paul@181 | 344 | h->qh_qf.fs = fs; |
paul@181 | 345 | qf_inum = quota_type2inum(qtype, fs->super); |
paul@181 | 346 | if (qf_inum == 0 && qtype == PRJQUOTA) { |
paul@181 | 347 | err = ext2fs_new_inode(fs, EXT2_ROOT_INO, LINUX_S_IFREG | 0600, |
paul@181 | 348 | 0, &qf_inum); |
paul@181 | 349 | if (err) |
paul@181 | 350 | return err; |
paul@181 | 351 | ext2fs_inode_alloc_stats2(fs, qf_inum, +1, 0); |
paul@181 | 352 | ext2fs_mark_ib_dirty(fs); |
paul@181 | 353 | } else if (qf_inum == 0) { |
paul@181 | 354 | return EXT2_ET_BAD_INODE_NUM; |
paul@181 | 355 | } |
paul@181 | 356 | |
paul@181 | 357 | err = ext2fs_read_bitmaps(fs); |
paul@181 | 358 | if (err) |
paul@181 | 359 | goto out_err; |
paul@181 | 360 | |
paul@181 | 361 | err = quota_inode_init_new(fs, qf_inum); |
paul@181 | 362 | if (err) { |
paul@181 | 363 | log_err("init_new_quota_inode failed"); |
paul@181 | 364 | goto out_err; |
paul@181 | 365 | } |
paul@181 | 366 | h->qh_qf.ino = qf_inum; |
paul@181 | 367 | h->qh_file_flags = EXT2_FILE_WRITE | EXT2_FILE_CREATE; |
paul@181 | 368 | h->e2fs_write = quota_write_nomount; |
paul@181 | 369 | h->e2fs_read = quota_read_nomount; |
paul@181 | 370 | |
paul@181 | 371 | log_debug("Creating quota ino=%u, type=%d", qf_inum, qtype); |
paul@181 | 372 | err = ext2fs_file_open(fs, qf_inum, h->qh_file_flags, &e2_file); |
paul@181 | 373 | if (err) { |
paul@181 | 374 | log_err("ext2fs_file_open failed: %ld", err); |
paul@181 | 375 | goto out_err; |
paul@181 | 376 | } |
paul@181 | 377 | h->qh_qf.e2_file = e2_file; |
paul@181 | 378 | |
paul@181 | 379 | h->qh_io_flags = 0; |
paul@181 | 380 | h->qh_type = qtype; |
paul@181 | 381 | h->qh_fmt = fmt; |
paul@181 | 382 | memset(&h->qh_info, 0, sizeof(h->qh_info)); |
paul@181 | 383 | h->qh_ops = "afile_ops_2; |
paul@181 | 384 | |
paul@181 | 385 | if (h->qh_ops->new_io && (h->qh_ops->new_io(h) < 0)) { |
paul@181 | 386 | log_err("qh_ops->new_io failed"); |
paul@181 | 387 | err = EIO; |
paul@181 | 388 | goto out_err1; |
paul@181 | 389 | } |
paul@181 | 390 | |
paul@181 | 391 | return 0; |
paul@181 | 392 | |
paul@181 | 393 | out_err1: |
paul@181 | 394 | ext2fs_file_close(e2_file); |
paul@181 | 395 | out_err: |
paul@181 | 396 | |
paul@181 | 397 | if (qf_inum) |
paul@181 | 398 | quota_inode_truncate(fs, qf_inum); |
paul@181 | 399 | |
paul@181 | 400 | return err; |
paul@181 | 401 | } |
paul@181 | 402 | |
paul@181 | 403 | /* |
paul@181 | 404 | * Close quotafile and release handle |
paul@181 | 405 | */ |
paul@181 | 406 | errcode_t quota_file_close(quota_ctx_t qctx, struct quota_handle *h) |
paul@181 | 407 | { |
paul@181 | 408 | if (h->qh_io_flags & IOFL_INFODIRTY) { |
paul@181 | 409 | if (h->qh_ops->write_info && h->qh_ops->write_info(h) < 0) |
paul@181 | 410 | return EIO; |
paul@181 | 411 | h->qh_io_flags &= ~IOFL_INFODIRTY; |
paul@181 | 412 | } |
paul@181 | 413 | |
paul@181 | 414 | if (h->qh_ops->end_io && h->qh_ops->end_io(h) < 0) |
paul@181 | 415 | return EIO; |
paul@181 | 416 | if (h->qh_qf.e2_file) { |
paul@181 | 417 | __u64 new_size, size; |
paul@181 | 418 | |
paul@181 | 419 | new_size = compute_inode_size(h->qh_qf.fs, h->qh_qf.ino); |
paul@181 | 420 | ext2fs_file_flush(h->qh_qf.e2_file); |
paul@181 | 421 | if (ext2fs_file_get_lsize(h->qh_qf.e2_file, &size)) |
paul@181 | 422 | new_size = 0; |
paul@181 | 423 | if (size != new_size) |
paul@181 | 424 | ext2fs_file_set_size2(h->qh_qf.e2_file, new_size); |
paul@181 | 425 | ext2fs_file_close(h->qh_qf.e2_file); |
paul@181 | 426 | } |
paul@181 | 427 | if (qctx->quota_file[h->qh_type] == h) |
paul@181 | 428 | ext2fs_free_mem(&qctx->quota_file[h->qh_type]); |
paul@181 | 429 | return 0; |
paul@181 | 430 | } |
paul@181 | 431 | |
paul@181 | 432 | /* |
paul@181 | 433 | * Create empty quota structure |
paul@181 | 434 | */ |
paul@181 | 435 | struct dquot *get_empty_dquot(void) |
paul@181 | 436 | { |
paul@181 | 437 | struct dquot *dquot; |
paul@181 | 438 | |
paul@181 | 439 | if (ext2fs_get_memzero(sizeof(struct dquot), &dquot)) { |
paul@181 | 440 | log_err("Failed to allocate dquot"); |
paul@181 | 441 | return NULL; |
paul@181 | 442 | } |
paul@181 | 443 | |
paul@181 | 444 | dquot->dq_id = -1; |
paul@181 | 445 | return dquot; |
paul@181 | 446 | } |