/* * Write out a new record. */ static int hammer_mirror_write(hammer_cursor_t cursor, struct hammer_ioc_mrecord_rec *mrec, char *udata) { hammer_transaction_t trans; hammer_buffer_t data_buffer; hammer_off_t ndata_offset; hammer_tid_t high_tid; void *ndata; int error; int doprop; trans = cursor->trans; data_buffer = NULL; /* * Get the sync lock so the whole mess is atomic */ hammer_sync_lock_sh(trans); /* * Allocate and adjust data */ if (mrec->leaf.data_len && mrec->leaf.data_offset) { ndata = hammer_alloc_data(trans, mrec->leaf.data_len, mrec->leaf.base.rec_type, &ndata_offset, &data_buffer, 0, &error); if (ndata == NULL) return(error); mrec->leaf.data_offset = ndata_offset; hammer_modify_buffer(trans, data_buffer, NULL, 0); error = copyin(udata, ndata, mrec->leaf.data_len); if (error == 0) { if (hammer_crc_test_leaf(ndata, &mrec->leaf) == 0) { kprintf("data crc mismatch on pipe\n"); error = EINVAL; } else { error = hammer_mirror_localize_data( ndata, &mrec->leaf); } } hammer_modify_buffer_done(data_buffer); } else { mrec->leaf.data_offset = 0; error = 0; ndata = NULL; } if (error) goto failed; /* * Do the insertion. This can fail with a EDEADLK or EALREADY */ cursor->flags |= HAMMER_CURSOR_INSERT; error = hammer_btree_lookup(cursor); if (error != ENOENT) { if (error == 0) error = EALREADY; goto failed; } error = hammer_btree_insert(cursor, &mrec->leaf, &doprop); /* * Cursor is left on the current element, we want to skip it now. */ cursor->flags |= HAMMER_CURSOR_ATEDISK; cursor->flags &= ~HAMMER_CURSOR_INSERT; /* * Track a count of active inodes. */ if (error == 0 && mrec->leaf.base.rec_type == HAMMER_RECTYPE_INODE && mrec->leaf.base.delete_tid == 0) { hammer_modify_volume_field(trans, trans->rootvol, vol0_stat_inodes); ++trans->hmp->rootvol->ondisk->vol0_stat_inodes; hammer_modify_volume_done(trans->rootvol); } /* * vol0_next_tid must track the highest TID stored in the filesystem. * We do not need to generate undo for this update. */ high_tid = mrec->leaf.base.create_tid; if (high_tid < mrec->leaf.base.delete_tid) high_tid = mrec->leaf.base.delete_tid; if (trans->rootvol->ondisk->vol0_next_tid < high_tid) { hammer_modify_volume(trans, trans->rootvol, NULL, 0); trans->rootvol->ondisk->vol0_next_tid = high_tid; hammer_modify_volume_done(trans->rootvol); } /* * WARNING! cursor's leaf pointer may have changed after * do_propagation returns. */ if (error == 0 && doprop) hammer_btree_do_propagation(cursor, NULL, &mrec->leaf); failed: /* * Cleanup */ if (error && mrec->leaf.data_offset) { hammer_blockmap_free(cursor->trans, mrec->leaf.data_offset, mrec->leaf.data_len); } hammer_sync_unlock(trans); if (data_buffer) hammer_rel_buffer(data_buffer, 0); return(error); }
int hammer_ioc_volume_add(hammer_transaction_t trans, hammer_inode_t ip, struct hammer_ioc_volume *ioc) { hammer_mount_t hmp = trans->hmp; struct mount *mp = hmp->mp; struct hammer_volume_ondisk ondisk; hammer_volume_t volume; int64_t total_bigblocks, empty_bigblocks; int free_vol_no = 0; int error; if (mp->mnt_flag & MNT_RDONLY) { hmkprintf(hmp, "Cannot add volume to read-only HAMMER filesystem\n"); return (EINVAL); } if (hammer_lock_ex_try(&hmp->volume_lock) != 0) { hmkprintf(hmp, "Another volume operation is in progress!\n"); return (EAGAIN); } if (hmp->nvolumes >= HAMMER_MAX_VOLUMES) { hammer_unlock(&hmp->volume_lock); hmkprintf(hmp, "Max number of HAMMER volumes exceeded\n"); return (EINVAL); } /* * Find an unused volume number. */ while (free_vol_no < HAMMER_MAX_VOLUMES && hammer_volume_number_test(hmp, free_vol_no)) { ++free_vol_no; } if (free_vol_no >= HAMMER_MAX_VOLUMES) { hmkprintf(hmp, "Max number of HAMMER volumes exceeded\n"); error = EINVAL; goto end; } error = hammer_format_volume_header(hmp, ioc, &ondisk, free_vol_no); if (error) goto end; error = hammer_install_volume(hmp, ioc->device_name, NULL, &ondisk); if (error) goto end; hammer_sync_lock_sh(trans); hammer_lock_ex(&hmp->blkmap_lock); volume = hammer_get_volume(hmp, free_vol_no, &error); KKASSERT(volume != NULL && error == 0); error = hammer_format_freemap(trans, volume); KKASSERT(error == 0); error = hammer_count_bigblocks(hmp, volume, &total_bigblocks, &empty_bigblocks); KKASSERT(error == 0); KKASSERT(total_bigblocks == empty_bigblocks); hammer_rel_volume(volume, 0); ++hmp->nvolumes; error = hammer_update_volumes_header(trans, total_bigblocks, empty_bigblocks); KKASSERT(error == 0); hammer_unlock(&hmp->blkmap_lock); hammer_sync_unlock(trans); KKASSERT(error == 0); end: hammer_unlock(&hmp->volume_lock); if (error) hmkprintf(hmp, "An error occurred: %d\n", error); return (error); }
/* * Copy records from userland to the target mirror. * * The PFS is identified in the mirror structure. The passed ip is just * some directory in the overall HAMMER filesystem and has nothing to * do with the PFS. In fact, there might not even be a root directory for * the PFS yet! */ int hammer_ioc_mirror_write(hammer_transaction_t trans, hammer_inode_t ip, struct hammer_ioc_mirror_rw *mirror) { union hammer_ioc_mrecord_any mrec; struct hammer_cursor cursor; u_int32_t localization; int checkspace_count = 0; int error; int bytes; char *uptr; int seq; localization = (u_int32_t)mirror->pfs_id << 16; seq = trans->hmp->flusher.done; /* * Validate the mirror structure and relocalize the tracking keys. */ if (mirror->size < 0 || mirror->size > 0x70000000) return(EINVAL); mirror->key_beg.localization &= HAMMER_LOCALIZE_MASK; mirror->key_beg.localization += localization; mirror->key_end.localization &= HAMMER_LOCALIZE_MASK; mirror->key_end.localization += localization; mirror->key_cur.localization &= HAMMER_LOCALIZE_MASK; mirror->key_cur.localization += localization; /* * Set up our tracking cursor for the loop. The tracking cursor * is used to delete records that are no longer present on the * master. The last handled record at key_cur must be skipped. */ error = hammer_init_cursor(trans, &cursor, NULL, NULL); cursor.key_beg = mirror->key_cur; cursor.key_end = mirror->key_end; cursor.flags |= HAMMER_CURSOR_BACKEND; error = hammer_btree_first(&cursor); if (error == 0) cursor.flags |= HAMMER_CURSOR_ATEDISK; if (error == ENOENT) error = 0; /* * Loop until our input buffer has been exhausted. */ while (error == 0 && mirror->count + sizeof(mrec.head) <= mirror->size) { /* * Don't blow out the buffer cache. Leave room for frontend * cache as well. * * WARNING: See warnings in hammer_unlock_cursor() function. */ while (hammer_flusher_meta_halflimit(trans->hmp) || hammer_flusher_undo_exhausted(trans, 2)) { hammer_unlock_cursor(&cursor); hammer_flusher_wait(trans->hmp, seq); hammer_lock_cursor(&cursor); seq = hammer_flusher_async_one(trans->hmp); } /* * If there is insufficient free space it may be due to * reserved bigblocks, which flushing might fix. */ if (hammer_checkspace(trans->hmp, HAMMER_CHKSPC_MIRROR)) { if (++checkspace_count == 10) { error = ENOSPC; break; } hammer_unlock_cursor(&cursor); hammer_flusher_wait(trans->hmp, seq); hammer_lock_cursor(&cursor); seq = hammer_flusher_async(trans->hmp, NULL); } /* * Acquire and validate header */ if ((bytes = mirror->size - mirror->count) > sizeof(mrec)) bytes = sizeof(mrec); uptr = (char *)mirror->ubuf + mirror->count; error = copyin(uptr, &mrec, bytes); if (error) break; if (mrec.head.signature != HAMMER_IOC_MIRROR_SIGNATURE) { error = EINVAL; break; } if (mrec.head.rec_size < sizeof(mrec.head) || mrec.head.rec_size > sizeof(mrec) + HAMMER_XBUFSIZE || mirror->count + mrec.head.rec_size > mirror->size) { error = EINVAL; break; } switch(mrec.head.type & HAMMER_MRECF_TYPE_MASK) { case HAMMER_MREC_TYPE_SKIP: if (mrec.head.rec_size != sizeof(mrec.skip)) error = EINVAL; if (error == 0) error = hammer_ioc_mirror_write_skip(&cursor, &mrec.skip, mirror, localization); break; case HAMMER_MREC_TYPE_REC: if (mrec.head.rec_size < sizeof(mrec.rec)) error = EINVAL; if (error == 0) error = hammer_ioc_mirror_write_rec(&cursor, &mrec.rec, mirror, localization, uptr + sizeof(mrec.rec)); break; case HAMMER_MREC_TYPE_REC_NODATA: case HAMMER_MREC_TYPE_REC_BADCRC: /* * Records with bad data payloads are ignored XXX. * Records with no data payload have to be skipped * (they shouldn't have been written in the first * place). */ if (mrec.head.rec_size < sizeof(mrec.rec)) error = EINVAL; break; case HAMMER_MREC_TYPE_PASS: if (mrec.head.rec_size != sizeof(mrec.rec)) error = EINVAL; if (error == 0) error = hammer_ioc_mirror_write_pass(&cursor, &mrec.rec, mirror, localization); break; default: error = EINVAL; break; } /* * Retry the current record on deadlock, otherwise setup * for the next loop. */ if (error == EDEADLK) { while (error == EDEADLK) { hammer_sync_lock_sh(trans); hammer_recover_cursor(&cursor); error = hammer_cursor_upgrade(&cursor); hammer_sync_unlock(trans); } } else { if (error == EALREADY) error = 0; if (error == 0) { mirror->count += HAMMER_HEAD_DOALIGN(mrec.head.rec_size); } } } hammer_done_cursor(&cursor); /* * cumulative error */ if (error) { mirror->head.flags |= HAMMER_IOC_HEAD_ERROR; mirror->head.error = error; } /* * ioctls don't update the RW data structure if an error is returned, * always return 0. */ return(0); }
int hammer_ioc_prune(hammer_transaction_t trans, hammer_inode_t ip, struct hammer_ioc_prune *prune) { struct hammer_cursor cursor; hammer_btree_leaf_elm_t elm; struct hammer_ioc_prune_elm *copy_elms; struct hammer_ioc_prune_elm *user_elms; int error; int isdir; int elm_array_size; int seq; if (prune->nelms < 0 || prune->nelms > HAMMER_MAX_PRUNE_ELMS) return(EINVAL); if ((prune->key_beg.localization | prune->key_end.localization) & HAMMER_LOCALIZE_PSEUDOFS_MASK) { return(EINVAL); } if (prune->key_beg.localization > prune->key_end.localization) return(EINVAL); if (prune->key_beg.localization == prune->key_end.localization) { if (prune->key_beg.obj_id > prune->key_end.obj_id) return(EINVAL); /* key-space limitations - no check needed */ } if ((prune->head.flags & HAMMER_IOC_PRUNE_ALL) && prune->nelms) return(EINVAL); /* 22 EINVAL */ prune->key_cur.localization = (prune->key_end.localization & HAMMER_LOCALIZE_MASK) + ip->obj_localization; prune->key_cur.obj_id = prune->key_end.obj_id; prune->key_cur.key = HAMMER_MAX_KEY; /* * Copy element array from userland */ elm_array_size = sizeof(*copy_elms) * prune->nelms; user_elms = prune->elms; copy_elms = kmalloc(elm_array_size, M_TEMP, M_WAITOK); if ((error = copyin(user_elms, copy_elms, elm_array_size)) != 0) goto failed; prune->elms = copy_elms; seq = trans->hmp->flusher.done; /* * Scan backwards. Retries typically occur if a deadlock is detected. */ retry: error = hammer_init_cursor(trans, &cursor, NULL, NULL); if (error) { hammer_done_cursor(&cursor); goto failed; } cursor.key_beg.localization = (prune->key_beg.localization & HAMMER_LOCALIZE_MASK) + ip->obj_localization; cursor.key_beg.obj_id = prune->key_beg.obj_id; cursor.key_beg.key = HAMMER_MIN_KEY; cursor.key_beg.create_tid = 1; cursor.key_beg.delete_tid = 0; cursor.key_beg.rec_type = HAMMER_MIN_RECTYPE; cursor.key_beg.obj_type = 0; cursor.key_end.localization = prune->key_cur.localization; cursor.key_end.obj_id = prune->key_cur.obj_id; cursor.key_end.key = prune->key_cur.key; cursor.key_end.create_tid = HAMMER_MAX_TID - 1; cursor.key_end.delete_tid = 0; cursor.key_end.rec_type = HAMMER_MAX_RECTYPE; cursor.key_end.obj_type = 0; cursor.flags |= HAMMER_CURSOR_END_INCLUSIVE; cursor.flags |= HAMMER_CURSOR_BACKEND; /* * This flag allows the B-Tree code to clean up loose ends. At * the moment (XXX) it also means we have to hold the sync lock * through the iteration. */ cursor.flags |= HAMMER_CURSOR_PRUNING; hammer_sync_lock_sh(trans); error = hammer_btree_last(&cursor); hammer_sync_unlock(trans); while (error == 0) { /* * Check for work */ elm = &cursor.node->ondisk->elms[cursor.index].leaf; prune->key_cur = elm->base; /* * Yield to more important tasks */ if ((error = hammer_signal_check(trans->hmp)) != 0) break; if (prune->stat_oldest_tid > elm->base.create_tid) prune->stat_oldest_tid = elm->base.create_tid; if (hammer_debug_general & 0x0200) { kprintf("check %016llx %016llx cre=%016llx del=%016llx\n", (long long)elm->base.obj_id, (long long)elm->base.key, (long long)elm->base.create_tid, (long long)elm->base.delete_tid); } if (prune_should_delete(prune, elm)) { if (hammer_debug_general & 0x0200) { kprintf("check %016llx %016llx: DELETE\n", (long long)elm->base.obj_id, (long long)elm->base.key); } /* * NOTE: This can return EDEADLK * * Acquiring the sync lock guarantees that the * operation will not cross a synchronization * boundary (see the flusher). * * We dont need to track inodes or next_tid when * we are destroying deleted records. */ isdir = (elm->base.rec_type == HAMMER_RECTYPE_DIRENTRY); hammer_sync_lock_sh(trans); error = hammer_delete_at_cursor(&cursor, HAMMER_DELETE_DESTROY, cursor.trans->tid, cursor.trans->time32, 0, &prune->stat_bytes); hammer_sync_unlock(trans); if (error) break; if (isdir) ++prune->stat_dirrecords; else ++prune->stat_rawrecords; /* * The current record might now be the one after * the one we deleted, set ATEDISK to force us * to skip it (since we are iterating backwards). */ cursor.flags |= HAMMER_CURSOR_ATEDISK; } else { /* * Nothing to delete, but we may have to check other * things. */ prune_check_nlinks(&cursor, elm); cursor.flags |= HAMMER_CURSOR_ATEDISK; if (hammer_debug_general & 0x0100) { kprintf("check %016llx %016llx: SKIP\n", (long long)elm->base.obj_id, (long long)elm->base.key); } } ++prune->stat_scanrecords; /* * WARNING: See warnings in hammer_unlock_cursor() function. */ while (hammer_flusher_meta_halflimit(trans->hmp) || hammer_flusher_undo_exhausted(trans, 2)) { hammer_unlock_cursor(&cursor); hammer_flusher_wait(trans->hmp, seq); hammer_lock_cursor(&cursor); seq = hammer_flusher_async_one(trans->hmp); } hammer_sync_lock_sh(trans); error = hammer_btree_iterate_reverse(&cursor); hammer_sync_unlock(trans); } if (error == ENOENT) error = 0; hammer_done_cursor(&cursor); if (error == EDEADLK) goto retry; if (error == EINTR) { prune->head.flags |= HAMMER_IOC_HEAD_INTR; error = 0; } failed: prune->key_cur.localization &= HAMMER_LOCALIZE_MASK; prune->elms = user_elms; kfree(copy_elms, M_TEMP); return(error); }
int hammer_ioc_reblock(hammer_transaction_t trans, hammer_inode_t ip, struct hammer_ioc_reblock *reblock) { struct hammer_cursor cursor; hammer_btree_elm_t elm; int checkspace_count; int error; int seq; int slop; /* * A fill level <= 20% is considered an emergency. free_level is * inverted from fill_level. */ if (reblock->free_level >= HAMMER_LARGEBLOCK_SIZE * 8 / 10) slop = HAMMER_CHKSPC_EMERGENCY; else slop = HAMMER_CHKSPC_REBLOCK; if ((reblock->key_beg.localization | reblock->key_end.localization) & HAMMER_LOCALIZE_PSEUDOFS_MASK) { return(EINVAL); } if (reblock->key_beg.obj_id >= reblock->key_end.obj_id) return(EINVAL); if (reblock->free_level < 0) return(EINVAL); reblock->key_cur = reblock->key_beg; reblock->key_cur.localization &= HAMMER_LOCALIZE_MASK; reblock->key_cur.localization += ip->obj_localization; checkspace_count = 0; seq = trans->hmp->flusher.done; retry: error = hammer_init_cursor(trans, &cursor, NULL, NULL); if (error) { hammer_done_cursor(&cursor); goto failed; } cursor.key_beg.localization = reblock->key_cur.localization; cursor.key_beg.obj_id = reblock->key_cur.obj_id; cursor.key_beg.key = HAMMER_MIN_KEY; cursor.key_beg.create_tid = 1; cursor.key_beg.delete_tid = 0; cursor.key_beg.rec_type = HAMMER_MIN_RECTYPE; cursor.key_beg.obj_type = 0; cursor.key_end.localization = (reblock->key_end.localization & HAMMER_LOCALIZE_MASK) + ip->obj_localization; cursor.key_end.obj_id = reblock->key_end.obj_id; cursor.key_end.key = HAMMER_MAX_KEY; cursor.key_end.create_tid = HAMMER_MAX_TID - 1; cursor.key_end.delete_tid = 0; cursor.key_end.rec_type = HAMMER_MAX_RECTYPE; cursor.key_end.obj_type = 0; cursor.flags |= HAMMER_CURSOR_END_INCLUSIVE; cursor.flags |= HAMMER_CURSOR_BACKEND; cursor.flags |= HAMMER_CURSOR_NOSWAPCACHE; /* * This flag allows the btree scan code to return internal nodes, * so we can reblock them in addition to the leafs. Only specify it * if we intend to reblock B-Tree nodes. */ if (reblock->head.flags & HAMMER_IOC_DO_BTREE) cursor.flags |= HAMMER_CURSOR_REBLOCKING; error = hammer_btree_first(&cursor); while (error == 0) { /* * Internal or Leaf node */ KKASSERT(cursor.index < cursor.node->ondisk->count); elm = &cursor.node->ondisk->elms[cursor.index]; reblock->key_cur.obj_id = elm->base.obj_id; reblock->key_cur.localization = elm->base.localization; /* * Yield to more important tasks */ if ((error = hammer_signal_check(trans->hmp)) != 0) break; /* * If there is insufficient free space it may be due to * reserved bigblocks, which flushing might fix. * * We must force a retest in case the unlocked cursor is * moved to the end of the leaf, or moved to an internal * node. * * WARNING: See warnings in hammer_unlock_cursor() function. */ if (hammer_checkspace(trans->hmp, slop)) { if (++checkspace_count == 10) { error = ENOSPC; break; } hammer_unlock_cursor(&cursor); cursor.flags |= HAMMER_CURSOR_RETEST; hammer_flusher_wait(trans->hmp, seq); hammer_lock_cursor(&cursor); seq = hammer_flusher_async(trans->hmp, NULL); goto skip; } /* * Acquiring the sync_lock prevents the operation from * crossing a synchronization boundary. * * NOTE: cursor.node may have changed on return. * * WARNING: See warnings in hammer_unlock_cursor() function. */ hammer_sync_lock_sh(trans); error = hammer_reblock_helper(reblock, &cursor, elm); hammer_sync_unlock(trans); while (hammer_flusher_meta_halflimit(trans->hmp) || hammer_flusher_undo_exhausted(trans, 2)) { hammer_unlock_cursor(&cursor); hammer_flusher_wait(trans->hmp, seq); hammer_lock_cursor(&cursor); seq = hammer_flusher_async_one(trans->hmp); } /* * Setup for iteration, our cursor flags may be modified by * other threads while we are unlocked. */ cursor.flags |= HAMMER_CURSOR_ATEDISK; /* * We allocate data buffers, which atm we don't track * dirty levels for because we allow the kernel to write * them. But if we allocate too many we can still deadlock * the buffer cache. * * WARNING: See warnings in hammer_unlock_cursor() function. * (The cursor's node and element may change!) */ if (bd_heatup()) { hammer_unlock_cursor(&cursor); bwillwrite(HAMMER_XBUFSIZE); hammer_lock_cursor(&cursor); } /* XXX vm_wait_nominal(); */ skip: if (error == 0) { error = hammer_btree_iterate(&cursor); } } if (error == ENOENT) error = 0; hammer_done_cursor(&cursor); if (error == EWOULDBLOCK) { hammer_flusher_sync(trans->hmp); goto retry; } if (error == EDEADLK) goto retry; if (error == EINTR) { reblock->head.flags |= HAMMER_IOC_HEAD_INTR; error = 0; } failed: reblock->key_cur.localization &= HAMMER_LOCALIZE_MASK; return(error); }
int hammer_ioc_volume_add(hammer_transaction_t trans, hammer_inode_t ip, struct hammer_ioc_volume *ioc) { struct hammer_mount *hmp = trans->hmp; struct mount *mp = hmp->mp; hammer_volume_t volume; int error; if (mp->mnt_flag & MNT_RDONLY) { kprintf("Cannot add volume to read-only HAMMER filesystem\n"); return (EINVAL); } if (hmp->nvolumes + 1 >= HAMMER_MAX_VOLUMES) { kprintf("Max number of HAMMER volumes exceeded\n"); return (EINVAL); } if (hammer_lock_ex_try(&hmp->volume_lock) != 0) { kprintf("Another volume operation is in progress!\n"); return (EAGAIN); } /* * Find an unused volume number. */ int free_vol_no = 0; while (free_vol_no < HAMMER_MAX_VOLUMES && RB_LOOKUP(hammer_vol_rb_tree, &hmp->rb_vols_root, free_vol_no)) { ++free_vol_no; } if (free_vol_no >= HAMMER_MAX_VOLUMES) { kprintf("Max number of HAMMER volumes exceeded\n"); hammer_unlock(&hmp->volume_lock); return (EINVAL); } struct vnode *devvp = NULL; error = hammer_setup_device(&devvp, ioc->device_name, 0); if (error) goto end; KKASSERT(devvp); error = hammer_format_volume_header( hmp, devvp, hmp->rootvol->ondisk->vol_name, free_vol_no, hmp->nvolumes+1, ioc->vol_size, ioc->boot_area_size, ioc->mem_area_size); hammer_close_device(&devvp, 0); if (error) goto end; error = hammer_install_volume(hmp, ioc->device_name, NULL); if (error) goto end; hammer_sync_lock_sh(trans); hammer_lock_ex(&hmp->blkmap_lock); ++hmp->nvolumes; /* * Set each volumes new value of the vol_count field. */ for (int vol_no = 0; vol_no < HAMMER_MAX_VOLUMES; ++vol_no) { volume = hammer_get_volume(hmp, vol_no, &error); if (volume == NULL && error == ENOENT) { /* * Skip unused volume numbers */ error = 0; continue; } KKASSERT(volume != NULL && error == 0); hammer_modify_volume_field(trans, volume, vol_count); volume->ondisk->vol_count = hmp->nvolumes; hammer_modify_volume_done(volume); /* * Only changes to the header of the root volume * are automatically flushed to disk. For all * other volumes that we modify we do it here. * * No interlock is needed, volume buffers are not * messed with by bioops. */ if (volume != trans->rootvol && volume->io.modified) { hammer_crc_set_volume(volume->ondisk); hammer_io_flush(&volume->io, 0); } hammer_rel_volume(volume, 0); } volume = hammer_get_volume(hmp, free_vol_no, &error); KKASSERT(volume != NULL && error == 0); struct bigblock_stat stat; error = hammer_format_freemap(trans, volume, &stat); KKASSERT(error == 0); /* * Increase the total number of bigblocks and update stat/vstat totals. */ hammer_modify_volume_field(trans, trans->rootvol, vol0_stat_bigblocks); trans->rootvol->ondisk->vol0_stat_bigblocks += stat.total_bigblocks; hammer_modify_volume_done(trans->rootvol); /* * Bigblock count changed so recompute the total number of blocks. */ mp->mnt_stat.f_blocks = trans->rootvol->ondisk->vol0_stat_bigblocks * (HAMMER_LARGEBLOCK_SIZE / HAMMER_BUFSIZE); mp->mnt_vstat.f_blocks = trans->rootvol->ondisk->vol0_stat_bigblocks * (HAMMER_LARGEBLOCK_SIZE / HAMMER_BUFSIZE); /* * Increase the number of free bigblocks * (including the copy in hmp) */ hammer_modify_volume_field(trans, trans->rootvol, vol0_stat_freebigblocks); trans->rootvol->ondisk->vol0_stat_freebigblocks += stat.total_free_bigblocks; hmp->copy_stat_freebigblocks = trans->rootvol->ondisk->vol0_stat_freebigblocks; hammer_modify_volume_done(trans->rootvol); hammer_rel_volume(volume, 0); hammer_unlock(&hmp->blkmap_lock); hammer_sync_unlock(trans); KKASSERT(error == 0); end: hammer_unlock(&hmp->volume_lock); if (error) kprintf("An error occurred: %d\n", error); return (error); }
/* * Remove a volume. */ int hammer_ioc_volume_del(hammer_transaction_t trans, hammer_inode_t ip, struct hammer_ioc_volume *ioc) { struct hammer_mount *hmp = trans->hmp; struct mount *mp = hmp->mp; hammer_volume_t volume; int error = 0; if (mp->mnt_flag & MNT_RDONLY) { kprintf("Cannot del volume from read-only HAMMER filesystem\n"); return (EINVAL); } if (hammer_lock_ex_try(&hmp->volume_lock) != 0) { kprintf("Another volume operation is in progress!\n"); return (EAGAIN); } volume = NULL; /* * find volume by volname */ for (int vol_no = 0; vol_no < HAMMER_MAX_VOLUMES; ++vol_no) { volume = hammer_get_volume(hmp, vol_no, &error); if (volume == NULL && error == ENOENT) { /* * Skip unused volume numbers */ error = 0; continue; } KKASSERT(volume != NULL && error == 0); if (strcmp(volume->vol_name, ioc->device_name) == 0) { break; } hammer_rel_volume(volume, 0); volume = NULL; } if (volume == NULL) { kprintf("Couldn't find volume\n"); error = EINVAL; goto end; } if (volume == trans->rootvol) { kprintf("Cannot remove root-volume\n"); hammer_rel_volume(volume, 0); error = EINVAL; goto end; } /* * */ hmp->volume_to_remove = volume->vol_no; struct hammer_ioc_reblock reblock; bzero(&reblock, sizeof(reblock)); reblock.key_beg.localization = HAMMER_MIN_LOCALIZATION; reblock.key_beg.obj_id = HAMMER_MIN_OBJID; reblock.key_end.localization = HAMMER_MAX_LOCALIZATION; reblock.key_end.obj_id = HAMMER_MAX_OBJID; reblock.head.flags = HAMMER_IOC_DO_FLAGS; reblock.free_level = 0; error = hammer_ioc_reblock(trans, ip, &reblock); if (reblock.head.flags & HAMMER_IOC_HEAD_INTR) { error = EINTR; } if (error) { if (error == EINTR) { kprintf("reblock was interrupted\n"); } else { kprintf("reblock failed: %d\n", error); } hmp->volume_to_remove = -1; hammer_rel_volume(volume, 0); goto end; } /* * Sync filesystem */ int count = 0; while (hammer_flusher_haswork(hmp)) { hammer_flusher_sync(hmp); ++count; if (count >= 5) { if (count == 5) kprintf("HAMMER: flushing."); else kprintf("."); tsleep(&count, 0, "hmrufl", hz); } if (count == 30) { kprintf("giving up"); break; } } kprintf("\n"); hammer_sync_lock_sh(trans); hammer_lock_ex(&hmp->blkmap_lock); /* * We use stat later to update rootvol's bigblock stats */ struct bigblock_stat stat; error = hammer_free_freemap(trans, volume, &stat); if (error) { kprintf("Failed to free volume. Volume not empty!\n"); hmp->volume_to_remove = -1; hammer_rel_volume(volume, 0); hammer_unlock(&hmp->blkmap_lock); hammer_sync_unlock(trans); goto end; } hmp->volume_to_remove = -1; hammer_rel_volume(volume, 0); /* * Unload buffers */ RB_SCAN(hammer_buf_rb_tree, &hmp->rb_bufs_root, NULL, hammer_unload_buffer, volume); error = hammer_unload_volume(volume, NULL); if (error == -1) { kprintf("Failed to unload volume\n"); hammer_unlock(&hmp->blkmap_lock); hammer_sync_unlock(trans); goto end; } volume = NULL; --hmp->nvolumes; /* * Set each volume's new value of the vol_count field. */ for (int vol_no = 0; vol_no < HAMMER_MAX_VOLUMES; ++vol_no) { volume = hammer_get_volume(hmp, vol_no, &error); if (volume == NULL && error == ENOENT) { /* * Skip unused volume numbers */ error = 0; continue; } KKASSERT(volume != NULL && error == 0); hammer_modify_volume_field(trans, volume, vol_count); volume->ondisk->vol_count = hmp->nvolumes; hammer_modify_volume_done(volume); /* * Only changes to the header of the root volume * are automatically flushed to disk. For all * other volumes that we modify we do it here. * * No interlock is needed, volume buffers are not * messed with by bioops. */ if (volume != trans->rootvol && volume->io.modified) { hammer_crc_set_volume(volume->ondisk); hammer_io_flush(&volume->io, 0); } hammer_rel_volume(volume, 0); } /* * Update the total number of bigblocks */ hammer_modify_volume_field(trans, trans->rootvol, vol0_stat_bigblocks); trans->rootvol->ondisk->vol0_stat_bigblocks -= stat.total_bigblocks; hammer_modify_volume_done(trans->rootvol); /* * Update the number of free bigblocks * (including the copy in hmp) */ hammer_modify_volume_field(trans, trans->rootvol, vol0_stat_freebigblocks); trans->rootvol->ondisk->vol0_stat_freebigblocks -= stat.total_free_bigblocks; hmp->copy_stat_freebigblocks = trans->rootvol->ondisk->vol0_stat_freebigblocks; hammer_modify_volume_done(trans->rootvol); /* * Bigblock count changed so recompute the total number of blocks. */ mp->mnt_stat.f_blocks = trans->rootvol->ondisk->vol0_stat_bigblocks * (HAMMER_LARGEBLOCK_SIZE / HAMMER_BUFSIZE); mp->mnt_vstat.f_blocks = trans->rootvol->ondisk->vol0_stat_bigblocks * (HAMMER_LARGEBLOCK_SIZE / HAMMER_BUFSIZE); hammer_unlock(&hmp->blkmap_lock); hammer_sync_unlock(trans); /* * Erase the volume header of the removed device. * * This is to not accidentally mount the volume again. */ struct vnode *devvp = NULL; error = hammer_setup_device(&devvp, ioc->device_name, 0); if (error) { kprintf("Failed to open device: %s\n", ioc->device_name); goto end; } KKASSERT(devvp); error = hammer_clear_volume_header(devvp); if (error) { kprintf("Failed to clear volume header of device: %s\n", ioc->device_name); goto end; } hammer_close_device(&devvp, 0); KKASSERT(error == 0); end: hammer_unlock(&hmp->volume_lock); return (error); }
int hammer_ioc_dedup(hammer_transaction_t trans, hammer_inode_t ip, struct hammer_ioc_dedup *dedup) { struct hammer_cursor cursor1, cursor2; int error; int seq; /* * Enforce hammer filesystem version requirements */ if (trans->hmp->version < HAMMER_VOL_VERSION_FIVE) { kprintf("hammer: Filesystem must be upgraded to v5 " "before you can run dedup\n"); return (EOPNOTSUPP); /* 95*/ } /* * Cursor1, return an error -> candidate goes to pass2 list */ error = hammer_init_cursor(trans, &cursor1, NULL, NULL); if (error) goto done_cursor; cursor1.key_beg = dedup->elm1; cursor1.flags |= HAMMER_CURSOR_BACKEND; error = hammer_btree_lookup(&cursor1); if (error) goto done_cursor; error = hammer_btree_extract(&cursor1, HAMMER_CURSOR_GET_LEAF | HAMMER_CURSOR_GET_DATA); if (error) goto done_cursor; /* * Cursor2, return an error -> candidate goes to pass2 list */ error = hammer_init_cursor(trans, &cursor2, NULL, NULL); if (error) goto done_cursors; cursor2.key_beg = dedup->elm2; cursor2.flags |= HAMMER_CURSOR_BACKEND; error = hammer_btree_lookup(&cursor2); if (error) goto done_cursors; error = hammer_btree_extract(&cursor2, HAMMER_CURSOR_GET_LEAF | HAMMER_CURSOR_GET_DATA); if (error) goto done_cursors; /* * Zone validation. We can't de-dup any of the other zones * (BTREE or META) or bad things will happen. * * Return with error = 0, but set an INVALID_ZONE flag. */ error = validate_zone(cursor1.leaf->data_offset) + validate_zone(cursor2.leaf->data_offset); if (error) { dedup->head.flags |= HAMMER_IOC_DEDUP_INVALID_ZONE; error = 0; goto done_cursors; } /* * Comparison checks * * If zones don't match or data_len fields aren't the same * we consider it to be a comparison failure. * * Return with error = 0, but set a CMP_FAILURE flag. */ if ((cursor1.leaf->data_offset & HAMMER_OFF_ZONE_MASK) != (cursor2.leaf->data_offset & HAMMER_OFF_ZONE_MASK)) { dedup->head.flags |= HAMMER_IOC_DEDUP_CMP_FAILURE; goto done_cursors; } if (cursor1.leaf->data_len != cursor2.leaf->data_len) { dedup->head.flags |= HAMMER_IOC_DEDUP_CMP_FAILURE; goto done_cursors; } /* byte-by-byte comparison to be sure */ if (bcmp(cursor1.data, cursor2.data, cursor1.leaf->data_len)) { dedup->head.flags |= HAMMER_IOC_DEDUP_CMP_FAILURE; goto done_cursors; } /* * Upgrade both cursors together to an exclusive lock * * Return an error -> candidate goes to pass2 list */ hammer_sync_lock_sh(trans); error = hammer_cursor_upgrade2(&cursor1, &cursor2); if (error) { hammer_sync_unlock(trans); goto done_cursors; } error = hammer_blockmap_dedup(cursor1.trans, cursor1.leaf->data_offset, cursor1.leaf->data_len); if (error) { if (error == ERANGE) { /* * Return with error = 0, but set an UNDERFLOW flag */ dedup->head.flags |= HAMMER_IOC_DEDUP_UNDERFLOW; error = 0; goto downgrade_cursors; } else { /* * Return an error -> block goes to pass2 list */ goto downgrade_cursors; } } /* * The cursor2's cache must be invalidated before calling * hammer_blockmap_free(), otherwise it will not be able to * invalidate the underlying data buffer. */ hammer_cursor_invalidate_cache(&cursor2); hammer_blockmap_free(cursor2.trans, cursor2.leaf->data_offset, cursor2.leaf->data_len); hammer_modify_node(cursor2.trans, cursor2.node, &cursor2.leaf->data_offset, sizeof(hammer_off_t)); cursor2.leaf->data_offset = cursor1.leaf->data_offset; hammer_modify_node_done(cursor2.node); downgrade_cursors: hammer_cursor_downgrade2(&cursor1, &cursor2); hammer_sync_unlock(trans); done_cursors: hammer_done_cursor(&cursor2); done_cursor: hammer_done_cursor(&cursor1); /* * Avoid deadlocking the buffer cache */ seq = trans->hmp->flusher.done; while (hammer_flusher_meta_halflimit(trans->hmp) || hammer_flusher_undo_exhausted(trans, 2)) { hammer_flusher_wait(trans->hmp, seq); seq = hammer_flusher_async_one(trans->hmp); } return (error); }