/* $NetBSD: chfs_build.c,v 1.2 2011/11/24 21:22:39 agc Exp $ */ /*- * Copyright (c) 2010 Department of Software Engineering, * University of Szeged, Hungary * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by the Department of Software Engineering, University of Szeged, Hungary * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "chfs.h" //#include void chfs_calc_trigger_levels(struct chfs_mount *chmp) { uint32_t size; chmp->chm_resv_blocks_deletion = 2; size = chmp->chm_ebh->flash_size / 50; //2% of flash size size += chmp->chm_ebh->peb_nr * 100; size += chmp->chm_ebh->eb_size - 1; chmp->chm_resv_blocks_write = chmp->chm_resv_blocks_deletion + (size / chmp->chm_ebh->eb_size); chmp->chm_resv_blocks_gctrigger = chmp->chm_resv_blocks_write + 1; chmp->chm_resv_blocks_gcmerge = chmp->chm_resv_blocks_deletion + 1; chmp->chm_vdirty_blocks_gctrigger = chmp->chm_resv_blocks_gctrigger * 10; chmp->chm_nospc_dirty = chmp->chm_ebh->eb_size + (chmp->chm_ebh->flash_size / 100); } /** * chfs_build_set_vnodecache_nlink - set pvno and nlink in vnodecaches * @chmp: CHFS main descriptor structure * @vc: vnode cache * This function travels @vc's directory entries and sets the pvno and nlink * attribute of the vnode where the dirent's vno points. */ void chfs_build_set_vnodecache_nlink(struct chfs_mount *chmp, struct chfs_vnode_cache *vc) { struct chfs_dirent *fd; //dbg("set nlink\n"); // for (fd = vc->scan_dirents; fd; fd = fd->next) { TAILQ_FOREACH(fd, &vc->scan_dirents, fds) { struct chfs_vnode_cache *child_vc; if (!fd->vno) continue; mutex_enter(&chmp->chm_lock_vnocache); child_vc = chfs_vnode_cache_get(chmp, fd->vno); mutex_exit(&chmp->chm_lock_vnocache); if (!child_vc) { chfs_mark_node_obsolete(chmp, fd->nref); continue; } if (fd->type == VDIR) { if (child_vc->nlink < 1) child_vc->nlink = 1; if (child_vc->pvno) { chfs_err("found a hard link: child dir: %s" ", (vno: %llu) of dir vno: %llu\n", fd->name, (unsigned long long)fd->vno, (unsigned long long)vc->vno); } else { //dbg("child_vc->pvno = // vc->vno; pvno = %d\n", child_vc->pvno); child_vc->pvno = vc->vno; } } child_vc->nlink++; //dbg("child_vc->nlink++;\n"); //child_vc->nlink++; vc->nlink++; } } /** * chfs_build_remove_unlinked vnode */ /* static */ void chfs_build_remove_unlinked_vnode(struct chfs_mount *chmp, struct chfs_vnode_cache *vc, // struct chfs_dirent **unlinked) struct chfs_dirent_list *unlinked) { struct chfs_node_ref *nref; struct chfs_dirent *fd, *tmpfd; dbg("START\n"); dbg("vno: %llu\n", (unsigned long long)vc->vno); nref = vc->dnode; KASSERT(mutex_owned(&chmp->chm_lock_mountfields)); // The vnode cache is at the end of the data node's chain while (nref != (struct chfs_node_ref *)vc) { struct chfs_node_ref *next = nref->nref_next; dbg("mark dnode\n"); chfs_mark_node_obsolete(chmp, nref); nref = next; } nref = vc->dirents; // The vnode cache is at the end of the dirent node's chain while (nref != (struct chfs_node_ref *)vc) { struct chfs_node_ref *next = nref->nref_next; dbg("mark dirent\n"); chfs_mark_node_obsolete(chmp, nref); nref = next; } if (!TAILQ_EMPTY(&vc->scan_dirents)) { TAILQ_FOREACH_SAFE(fd, &vc->scan_dirents, fds, tmpfd) { // while (vc->scan_dirents) { struct chfs_vnode_cache *child_vc; // fd = vc->scan_dirents; dbg("dirent dump:\n"); dbg(" ->vno: %llu\n", (unsigned long long)fd->vno); dbg(" ->version: %llu\n", (unsigned long long)fd->version); dbg(" ->nhash: 0x%x\n", fd->nhash); dbg(" ->nsize: %d\n", fd->nsize); dbg(" ->name: %s\n", fd->name); dbg(" ->type: %d\n", fd->type); // vc->scan_dirents = fd->next; TAILQ_REMOVE(&vc->scan_dirents, fd, fds); if (!fd->vno) { chfs_free_dirent(fd); continue; } mutex_enter(&chmp->chm_lock_vnocache); child_vc = chfs_vnode_cache_get(chmp, fd->vno); mutex_exit(&chmp->chm_lock_vnocache); if (!child_vc) { chfs_free_dirent(fd); continue; } /** * Decrease nlink in child. If it is 0, add to unlinked * dirents or just free it otherwise. */ child_vc->nlink--; if (!child_vc->nlink) { //dbg("nlink is 0\n"); // fd->next = *unlinked; // *unlinked = fd; // XXX HEAD or TAIL? // original code did HEAD, but we could add // it to the TAIL easily with TAILQ. TAILQ_INSERT_TAIL(unlinked, fd, fds); } else { chfs_free_dirent(fd); } } } else { dbg("there are no scan dirents\n"); } nref = vc->v; while ((struct chfs_vnode_cache *)nref != vc) { if (!CHFS_REF_OBSOLETE(nref)) chfs_mark_node_obsolete(chmp, nref); nref = nref->nref_next; } mutex_enter(&chmp->chm_lock_vnocache); if (vc->vno != CHFS_ROOTINO) chfs_vnode_cache_set_state(chmp, vc, VNO_STATE_UNCHECKED); mutex_exit(&chmp->chm_lock_vnocache); dbg("END\n"); } /** * chfs_build_filesystem - build in-memory representation of filesystem * @chmp: super block information * * Step 1: * This function scans through the eraseblocks mapped in EBH. * During scan builds up the map of vnodes and directory entries and puts them * into the vnode_cache. * Step 2: * Scans the directory tree and set the nlink in the vnode caches. * Step 3: * Scans vnode caches with nlink = 0 */ int chfs_build_filesystem(struct chfs_mount *chmp) { int i,err = 0; struct chfs_vnode_cache *vc; struct chfs_dirent *fd, *tmpfd; // struct chfs_dirent *unlinked = NULL; struct chfs_node_ref **nref; struct chfs_dirent_list unlinked; struct chfs_vnode_cache *notregvc; TAILQ_INIT(&unlinked); mutex_enter(&chmp->chm_lock_mountfields); /** * Step 1 */ chmp->chm_flags |= CHFS_MP_FLAG_SCANNING; for (i = 0; i < chmp->chm_ebh->peb_nr; i++) { //dbg("processing block: %d\n", i); chmp->chm_blocks[i].lnr = i; chmp->chm_blocks[i].free_size = chmp->chm_ebh->eb_size; //If the LEB is add to free list skip it. if (chmp->chm_ebh->lmap[i] < 0) { //dbg("block %d is unmapped\n", i); TAILQ_INSERT_TAIL(&chmp->chm_free_queue, &chmp->chm_blocks[i], queue); chmp->chm_nr_free_blocks++; continue; } err = chfs_scan_eraseblock(chmp, &chmp->chm_blocks[i]); switch (err) { case CHFS_BLK_STATE_FREE: chmp->chm_nr_free_blocks++; TAILQ_INSERT_TAIL(&chmp->chm_free_queue, &chmp->chm_blocks[i], queue); break; case CHFS_BLK_STATE_CLEAN: TAILQ_INSERT_TAIL(&chmp->chm_clean_queue, &chmp->chm_blocks[i], queue); break; case CHFS_BLK_STATE_PARTDIRTY: //dbg("free size: %d\n", chmp->chm_blocks[i].free_size); if (chmp->chm_blocks[i].free_size > chmp->chm_wbuf_pagesize && (!chmp->chm_nextblock || chmp->chm_blocks[i].free_size > chmp->chm_nextblock->free_size)) { /* convert the old nextblock's free size to * dirty and put it on a list */ if (chmp->chm_nextblock) { err = chfs_close_eraseblock(chmp, chmp->chm_nextblock); if (err) return err; } chmp->chm_nextblock = &chmp->chm_blocks[i]; } else { /* convert the scanned block's free size to * dirty and put it on a list */ err = chfs_close_eraseblock(chmp, &chmp->chm_blocks[i]); if (err) return err; } break; case CHFS_BLK_STATE_ALLDIRTY: /* * The block has a valid EBH header, but it doesn't * contain any valid data. */ TAILQ_INSERT_TAIL(&chmp->chm_erase_pending_queue, &chmp->chm_blocks[i], queue); chmp->chm_nr_erasable_blocks++; break; default: /* It was an error, unknown state */ break; } } chmp->chm_flags &= ~CHFS_MP_FLAG_SCANNING; //TODO need bad block check (and bad block handling in EBH too!!) /* Now EBH only checks block is bad during its scan operation. * Need check at erase + write + read... */ /** * Step 2 */ chmp->chm_flags |= CHFS_MP_FLAG_BUILDING; for (i = 0; i < VNODECACHE_SIZE; i++) { vc = chmp->chm_vnocache_hash[i]; while (vc) { dbg("vc->vno: %llu\n", (unsigned long long)vc->vno); if (!TAILQ_EMPTY(&vc->scan_dirents)) chfs_build_set_vnodecache_nlink(chmp, vc); vc = vc->next; } } /** * Step 3 * Scan for vnodes with 0 nlink. */ for (i = 0; i < VNODECACHE_SIZE; i++) { vc = chmp->chm_vnocache_hash[i]; while (vc) { if (vc->nlink) { vc = vc->next; continue; } //dbg("remove unlinked start i: %d\n", i); chfs_build_remove_unlinked_vnode(chmp, vc, &unlinked); //dbg("remove unlinked end\n"); vc = vc->next; } } /* Remove the newly unlinked vnodes. They are on the unlinked list */ TAILQ_FOREACH_SAFE(fd, &unlinked, fds, tmpfd) { // while (unlinked) { // fd = unlinked; // unlinked = fd->next; TAILQ_REMOVE(&unlinked, fd, fds); mutex_enter(&chmp->chm_lock_vnocache); vc = chfs_vnode_cache_get(chmp, fd->vno); mutex_exit(&chmp->chm_lock_vnocache); if (vc) { chfs_build_remove_unlinked_vnode(chmp, vc, &unlinked); } chfs_free_dirent(fd); } chmp->chm_flags &= ~CHFS_MP_FLAG_BUILDING; /* Free all dirents */ for (i = 0; i < VNODECACHE_SIZE; i++) { vc = chmp->chm_vnocache_hash[i]; while (vc) { TAILQ_FOREACH_SAFE(fd, &vc->scan_dirents, fds, tmpfd) { // while (vc->scan_dirents) { // fd = vc->scan_dirents; // vc->scan_dirents = fd->next; TAILQ_REMOVE(&vc->scan_dirents, fd, fds); if (fd->vno == 0) { //for (nref = &vc->dirents; // *nref != fd->nref; // nref = &((*nref)->next)); nref = &fd->nref; *nref = fd->nref->nref_next; //fd->nref->nref_next = NULL; } else if (fd->type == VDIR) { //set state every non-VREG file's vc mutex_enter(&chmp->chm_lock_vnocache); notregvc = chfs_vnode_cache_get(chmp, fd->vno); chfs_vnode_cache_set_state(chmp, notregvc, VNO_STATE_PRESENT); mutex_exit(&chmp->chm_lock_vnocache); } chfs_free_dirent(fd); } // vc->scan_dirents = NULL; KASSERT(TAILQ_EMPTY(&vc->scan_dirents)); vc = vc->next; } } //Set up chmp->chm_wbuf_ofs for the first write if (chmp->chm_nextblock) { dbg("free_size: %d\n", chmp->chm_nextblock->free_size); chmp->chm_wbuf_ofs = chmp->chm_ebh->eb_size - chmp->chm_nextblock->free_size; } else { chmp->chm_wbuf_ofs = 0xffffffff; } mutex_exit(&chmp->chm_lock_mountfields); return 0; }