/* -*- mode: c; c-basic-offset: 8; -*- * vim: noexpandtab sw=8 ts=8 sts=0: * * dirblock.c * * Directory block routines for the OCFS2 userspace library. * * Copyright (C) 2004 Oracle. 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, version 2, as published by the Free Software Foundation. * * This program is distributed in the hope that it will 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 to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 021110-1307, USA. * * This code is a port of e2fsprogs/lib/ext2fs/dir_iterate.c * Copyright (C) 1993, 1994, 1994, 1995, 1996, 1997 Theodore Ts'o. */ #define _XOPEN_SOURCE 600 /* Triggers magic in features.h */ #define _LARGEFILE64_SOURCE #include #include "ocfs2/ocfs2.h" #include "dir_iterate.h" #include "dir_util.h" static int ocfs2_inline_dir_iterate(ocfs2_filesys *fs, struct ocfs2_dinode *di, struct dir_context *ctx); /* * This function checks to see whether or not a potential deleted * directory entry looks valid. What we do is check the deleted entry * and each successive entry to make sure that they all look valid and * that the last deleted entry ends at the beginning of the next * undeleted entry. Returns 1 if the deleted entry looks valid, zero * if not valid. */ static int ocfs2_validate_entry(char *buf, int offset, int final_offset) { struct ocfs2_dir_entry *dirent; while (offset < final_offset) { dirent = (struct ocfs2_dir_entry *)(buf + offset); offset += dirent->rec_len; if ((dirent->rec_len < 8) || ((dirent->rec_len % 4) != 0) || (((dirent->name_len & 0xFF)+8) > dirent->rec_len)) return 0; } return (offset == final_offset); } errcode_t ocfs2_dir_iterate2(ocfs2_filesys *fs, uint64_t dir, int flags, char *block_buf, int (*func)(uint64_t dir, int entry, struct ocfs2_dir_entry *dirent, uint64_t blocknr, int offset, int blocksize, char *buf, void *priv_data), void *priv_data) { struct ocfs2_dinode *di; struct dir_context ctx; errcode_t retval; retval = ocfs2_check_directory(fs, dir); if (retval) return retval; ctx.dir = dir; ctx.flags = flags; if (block_buf) ctx.buf = block_buf; else { retval = ocfs2_malloc_block(fs->fs_io, &ctx.buf); if (retval) return retval; } ctx.func = func; ctx.priv_data = priv_data; ctx.errcode = 0; retval = ocfs2_malloc_block(fs->fs_io, &ctx.di); if (retval) goto out; retval = ocfs2_read_inode(fs, dir, ctx.buf); if (retval) goto out; /* * Save off the inode - some paths use the buffer for dirent * data. */ memcpy(ctx.di, ctx.buf, fs->fs_blocksize); di = (struct ocfs2_dinode *)ctx.buf; if (ocfs2_support_inline_data(OCFS2_RAW_SB(fs->fs_super)) && di->i_dyn_features & OCFS2_INLINE_DATA_FL) retval = ocfs2_inline_dir_iterate(fs, di, &ctx); else retval = ocfs2_block_iterate(fs, dir, 0, ocfs2_process_dir_block, &ctx); out: if (!block_buf) ocfs2_free(&ctx.buf); if (ctx.di) ocfs2_free(&ctx.di); if (retval) return retval; return ctx.errcode; } struct xlate { int (*func)(struct ocfs2_dir_entry *dirent, uint64_t blocknr, int offset, int blocksize, char *buf, void *priv_data); void *real_private; }; static int xlate_func(uint64_t dir, int entry, struct ocfs2_dir_entry *dirent, uint64_t blocknr, int offset, int blocksize, char *buf, void *priv_data) { struct xlate *xl = (struct xlate *) priv_data; return (*xl->func)(dirent, blocknr, offset, blocksize, buf, xl->real_private); } extern errcode_t ocfs2_dir_iterate(ocfs2_filesys *fs, uint64_t dir, int flags, char *block_buf, int (*func)(struct ocfs2_dir_entry *dirent, uint64_t blocknr, int offset, int blocksize, char *buf, void *priv_data), void *priv_data) { struct xlate xl; xl.real_private = priv_data; xl.func = func; return ocfs2_dir_iterate2(fs, dir, flags, block_buf, xlate_func, &xl); } static int ocfs2_process_dir_entry(ocfs2_filesys *fs, uint64_t blocknr, unsigned int offset, int entry, int *changed, int *do_abort, struct dir_context *ctx) { errcode_t ret; struct ocfs2_dir_entry *dirent; unsigned int next_real_entry = 0; int size; while (offset < fs->fs_blocksize) { dirent = (struct ocfs2_dir_entry *) (ctx->buf + offset); if (((offset + dirent->rec_len) > fs->fs_blocksize) || (dirent->rec_len < 8) || ((dirent->rec_len % 4) != 0) || (((dirent->name_len & 0xFF)+8) > dirent->rec_len)) { ctx->errcode = OCFS2_ET_DIR_CORRUPTED; return OCFS2_BLOCK_ABORT; } if (ocfs2_skip_dir_trailer(fs, ctx->di, dirent, offset)) { if (!(ctx->flags & OCFS2_DIRENT_FLAG_INCLUDE_TRAILER)) goto next; } else if (!dirent->inode && !(ctx->flags & OCFS2_DIRENT_FLAG_INCLUDE_EMPTY)) { goto next; } else if ((ctx->flags & OCFS2_DIRENT_FLAG_EXCLUDE_DOTS) && is_dots(dirent->name, dirent->name_len)) { goto next; } ret = (ctx->func)(ctx->dir, (next_real_entry > offset) ? OCFS2_DIRENT_DELETED_FILE : entry, dirent, blocknr, offset, fs->fs_blocksize, ctx->buf, ctx->priv_data); if (entry < OCFS2_DIRENT_OTHER_FILE) entry++; if (ret & OCFS2_DIRENT_CHANGED) *changed += 1; if (ret & OCFS2_DIRENT_ABORT) { *do_abort += 1; break; } next: if (next_real_entry == offset) next_real_entry += dirent->rec_len; if (ctx->flags & OCFS2_DIRENT_FLAG_INCLUDE_REMOVED) { size = ((dirent->name_len & 0xFF) + 11) & ~3; if (dirent->rec_len != size) { unsigned int final_offset; final_offset = offset + dirent->rec_len; offset += size; while (offset < final_offset && !ocfs2_validate_entry(ctx->buf, offset, final_offset)) offset += 4; continue; } } offset += dirent->rec_len; } return 0; } static int ocfs2_inline_dir_iterate(ocfs2_filesys *fs, struct ocfs2_dinode *di, struct dir_context *ctx) { unsigned int offset = offsetof(struct ocfs2_dinode, id2.i_data.id_data); int ret = 0, changed = 0, do_abort = 0, entry; entry = OCFS2_DIRENT_DOT_FILE; ret = ocfs2_process_dir_entry(fs, di->i_blkno, offset, entry, &changed, &do_abort, ctx); if (ret) return ret; if (changed) { ctx->errcode = ocfs2_write_inode(fs, di->i_blkno, ctx->buf); if (ctx->errcode) return OCFS2_BLOCK_ABORT; } return 0; } /* * Helper function which is private to this module. Used by * ocfs2_dir_iterate() and ocfs2_dblist_dir_iterate() */ int ocfs2_process_dir_block(ocfs2_filesys *fs, uint64_t blocknr, uint64_t blockcnt, uint16_t ext_flags, void *priv_data) { struct dir_context *ctx = (struct dir_context *) priv_data; unsigned int offset = 0; int ret = 0; int changed = 0; int do_abort = 0; int entry; if (blockcnt < 0) return 0; entry = blockcnt ? OCFS2_DIRENT_OTHER_FILE : OCFS2_DIRENT_DOT_FILE; ctx->errcode = ocfs2_read_dir_block(fs, ctx->di, blocknr, ctx->buf); if (ctx->errcode) return OCFS2_BLOCK_ABORT; ret = ocfs2_process_dir_entry(fs, blocknr, offset, entry, &changed, &do_abort, ctx); if (ret) return ret; if (changed) { ctx->errcode = ocfs2_write_dir_block(fs, ctx->di, blocknr, ctx->buf); if (ctx->errcode) return OCFS2_BLOCK_ABORT; } if (do_abort) return OCFS2_BLOCK_ABORT; return 0; } struct dx_iterator_data { int (*dx_func)(ocfs2_filesys *fs, struct ocfs2_dx_entry_list *entry_list, struct ocfs2_dx_root_block *dx_root, struct ocfs2_dx_leaf *dx_leaf, void *priv_data); void *dx_priv_data; char *leaf_buf; struct ocfs2_dx_root_block *dx_root; errcode_t err; }; static int dx_iterator(ocfs2_filesys *fs, struct ocfs2_extent_rec *rec, int tree_depth, uint32_t ccount, uint64_t ref_blkno, int ref_recno, void *priv_data) { errcode_t err; int i; struct ocfs2_dx_leaf *dx_leaf; struct dx_iterator_data *iter = priv_data; uint64_t blkno, count; count = ocfs2_clusters_to_blocks(fs, rec->e_leaf_clusters); blkno = rec->e_blkno; for (i = 0; i < count; i++) { err = ocfs2_read_dx_leaf(fs, blkno, iter->leaf_buf); if (err) { iter->err = err; return OCFS2_EXTENT_ERROR; } dx_leaf = (struct ocfs2_dx_leaf *)iter->leaf_buf; err = iter->dx_func(fs, &dx_leaf->dl_list, iter->dx_root, dx_leaf, iter->dx_priv_data); /* callback dx_func() is defined by users, the return value does not * follow libocfs2 error codes. Don't touch iter->err and just stop * the iteration here.*/ if (err) return OCFS2_EXTENT_ERROR; blkno++; } return 0; } extern errcode_t ocfs2_dx_entries_iterate(ocfs2_filesys *fs, struct ocfs2_dinode *dir, int flags, int (*func)(ocfs2_filesys *fs, struct ocfs2_dx_entry_list *entry_list, struct ocfs2_dx_root_block *dx_root, struct ocfs2_dx_leaf *dx_leaf, void *priv_data), void *priv_data) { errcode_t ret = 0; struct ocfs2_dx_root_block *dx_root; uint64_t dx_blkno; char *buf = NULL, *eb_buf = NULL, *leaf_buf = NULL; struct dx_iterator_data data; if (!S_ISDIR(dir->i_mode) && !ocfs2_dir_indexed(dir)) { goto out; } ret = ocfs2_malloc_block(fs->fs_io, &buf); if (ret) goto out; dx_blkno = (uint64_t) dir->i_dx_root; ret = ocfs2_read_dx_root(fs, dx_blkno, buf); if (ret) goto out; dx_root = (struct ocfs2_dx_root_block *)buf; if (dx_root->dr_flags & OCFS2_DX_FLAG_INLINE) { ret = func(fs, &dx_root->dr_entries, dx_root, NULL, priv_data); goto out; } ret = ocfs2_malloc_block(fs->fs_io, &eb_buf); if (ret) goto out; ret = ocfs2_malloc_block(fs->fs_io, &leaf_buf); if (ret) goto out; data.dx_func = func; data.dx_priv_data = priv_data; data.leaf_buf = leaf_buf; data.dx_root = dx_root; data.err = 0; ret = ocfs2_extent_iterate_dx_root(fs, dx_root, OCFS2_EXTENT_FLAG_DATA_ONLY, eb_buf, dx_iterator, &data); /* dx_iterator may set the error code for non-extents-related * errors. If the error code is set by dx_iterator, no matter * what ocfs2_extent_iterate_dx_root() returns, we should take * data.err as retured error code. */ if (data.err) ret = data.err; out: if (buf) ocfs2_free(&buf); if (eb_buf) ocfs2_free(&eb_buf); if (leaf_buf) ocfs2_free(&leaf_buf); return ret; } extern errcode_t ocfs2_dx_frees_iterate(ocfs2_filesys *fs, struct ocfs2_dinode *dir, struct ocfs2_dx_root_block *dx_root, int flags, int (*func)(ocfs2_filesys *fs, uint64_t blkno, struct ocfs2_dir_block_trailer *trailer, char *dirblock, void *priv_data), void *priv_data) { errcode_t ret = 0; uint64_t blkno; char *buf = NULL; struct ocfs2_dir_block_trailer *trailer; if (!S_ISDIR(dir->i_mode) || !(ocfs2_dir_indexed(dir))) { goto out; } if (dx_root->dr_flags & OCFS2_DX_FLAG_INLINE) { goto out; } ret = ocfs2_malloc_block(fs->fs_io, &buf); if (ret) goto out; blkno = dx_root->dr_free_blk; while (blkno) { ret = ocfs2_read_dir_block(fs, dir, blkno, buf); if (ret) goto out; trailer = ocfs2_dir_trailer_from_block(fs, buf); func(fs, blkno, trailer, buf, priv_data); blkno = trailer->db_free_next; } out: if (buf) ocfs2_free(&buf); return ret; } #ifdef DEBUG_EXE #include #include #include static uint64_t read_number(const char *num) { uint64_t val; char *ptr; val = strtoull(num, &ptr, 0); if (!ptr || *ptr) return 0; return val; } static void print_usage(void) { fprintf(stderr, "Usage: dir_iterate -i \n"); } static int walk_names_func(struct ocfs2_dir_entry *dentry, int offset, int blocksize, char *buf, void *priv_data) { char name[256]; memcpy(name, dentry->name, dentry->name_len); name[dentry->name_len] = '\0'; fprintf(stdout, "%20"PRIu64" %s\n", dentry->inode, name); return 0; } extern int opterr, optind; extern char *optarg; int main(int argc, char *argv[]) { errcode_t ret; uint64_t blkno; int c; char *filename, *buf; ocfs2_filesys *fs; struct ocfs2_dinode *di; blkno = 0; initialize_ocfs_error_table(); while ((c = getopt(argc, argv, "i:")) != EOF) { switch (c) { case 'i': blkno = read_number(optarg); if (blkno <= OCFS2_SUPER_BLOCK_BLKNO) { fprintf(stderr, "Invalid inode block: %s\n", optarg); print_usage(); return 1; } break; default: print_usage(); return 1; break; } } if (optind >= argc) { fprintf(stderr, "Missing filename\n"); print_usage(); return 1; } filename = argv[optind]; ret = ocfs2_open(filename, OCFS2_FLAG_RO, 0, 0, &fs); if (ret) { com_err(argv[0], ret, "while opening file \"%s\"", filename); goto out; } ret = ocfs2_malloc_block(fs->fs_io, &buf); if (ret) { com_err(argv[0], ret, "while allocating inode buffer"); goto out_close; } if (blkno == 0) blkno = fs->fs_root_blkno; ret = ocfs2_read_inode(fs, blkno, buf); if (ret) { com_err(argv[0], ret, "while reading inode %"PRIu64, blkno); goto out_free; } di = (struct ocfs2_dinode *)buf; fprintf(stdout, "OCFS2 inode %"PRIu64" on \"%s\"\n", blkno, filename); ret = ocfs2_dir_iterate(fs, blkno, 0, NULL, walk_names_func, NULL); if (ret) { com_err(argv[0], ret, "while listing inode %"PRIu64" on \"%s\"\n", blkno, filename); goto out_free; } out_free: ocfs2_free(&buf); out_close: ret = ocfs2_close(fs); if (ret) { com_err(argv[0], ret, "while closing file \"%s\"", filename); } out: return 0; } #endif /* DEBUG_EXE */