@@ -11342,3 +11342,2580 @@ int ssdfs_btree_node_insert_range(struct ssdfs_btree_search *search)
return 0;
}
+
+/*
+ * ssdfs_btree_node_check_result_for_change() - check search result for change
+ * @search: btree search object
+ */
+static inline
+int ssdfs_btree_node_check_result_for_change(struct ssdfs_btree_search *search)
+{
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!search);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ switch (search->result.state) {
+ case SSDFS_BTREE_SEARCH_VALID_ITEM:
+ /* expected state */
+ break;
+
+ default:
+ SSDFS_WARN("invalid search result state\n");
+ return -ERANGE;
+ }
+
+ return 0;
+}
+
+/*
+ * ssdfs_btree_node_change_item() - change the item in the node
+ * @search: btree search object
+ *
+ * This method tries to change an item in the node.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-EINVAL - invalid input.
+ * %-ERANGE - internal error.
+ * %-ENODATA - node doesn't contain the item.
+ * %-ENOSPC - the new item's state cannot be stored in the node.
+ * %-ENOENT - node hasn't the items area.
+ * %-EACCES - node is under initialization yet.
+ * %-EAGAIN - search object contains obsolete result.
+ */
+int ssdfs_btree_node_change_item(struct ssdfs_btree_search *search)
+{
+ struct ssdfs_fs_info *fsi;
+ struct ssdfs_btree_node *node;
+ int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!search);
+ BUG_ON(search->request.start.hash > search->request.end.hash);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("type %#x, flags %#x, "
+ "start_hash %llx, end_hash %llx, "
+ "state %#x, node_id %u, height %u, "
+ "parent %p, child %p\n",
+ search->request.type, search->request.flags,
+ search->request.start.hash, search->request.end.hash,
+ search->node.state, search->node.id,
+ search->node.height, search->node.parent,
+ search->node.child);
+#else
+ SSDFS_DBG("type %#x, flags %#x, "
+ "start_hash %llx, end_hash %llx, "
+ "state %#x, node_id %u, height %u, "
+ "parent %p, child %p\n",
+ search->request.type, search->request.flags,
+ search->request.start.hash, search->request.end.hash,
+ search->node.state, search->node.id,
+ search->node.height, search->node.parent,
+ search->node.child);
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ node = search->node.child;
+ if (!node) {
+ SSDFS_WARN("child node is NULL\n");
+ return -ERANGE;
+ }
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!node->tree || !node->tree->fsi);
+ BUG_ON(!rwsem_is_locked(&node->tree->lock));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ fsi = node->tree->fsi;
+
+ switch (atomic_read(&node->state)) {
+ case SSDFS_BTREE_NODE_CREATED:
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("node %u is under initialization\n",
+ node->node_id);
+#endif /* CONFIG_SSDFS_DEBUG */
+ return -EACCES;
+
+ case SSDFS_BTREE_NODE_INITIALIZED:
+ case SSDFS_BTREE_NODE_DIRTY:
+ /* expected state */
+ break;
+
+ default:
+ SSDFS_ERR("invalid node state %#x\n",
+ atomic_read(&node->state));
+ return -ERANGE;
+ }
+
+ if (!is_btree_search_node_desc_consistent(search)) {
+ SSDFS_WARN("node descriptor is inconsistent\n");
+ return -ERANGE;
+ }
+
+ err = ssdfs_btree_node_check_result_for_change(search);
+ if (err)
+ return err;
+
+ switch (atomic_read(&node->items_area.state)) {
+ case SSDFS_BTREE_NODE_ITEMS_AREA_EXIST:
+ /* expected state */
+ break;
+
+ case SSDFS_BTREE_NODE_AREA_ABSENT:
+ SSDFS_WARN("items area is absent: node_id %u\n",
+ search->node.id);
+ return -ENOENT;
+
+ default:
+ SSDFS_WARN("invalid items area state: node_id %u\n",
+ search->node.id);
+ return -ERANGE;
+ }
+
+ if (!node->node_ops || !node->node_ops->change_item) {
+ SSDFS_WARN("unable to change item\n");
+ return -EOPNOTSUPP;
+ }
+
+ err = node->node_ops->change_item(node, search);
+ if (unlikely(err)) {
+ SSDFS_ERR("fail to change item: "
+ "node %u, "
+ "request (start_hash %llx, end_hash %llx), "
+ "err %d\n",
+ node->node_id,
+ search->request.start.hash,
+ search->request.end.hash,
+ err);
+
+ search->result.state = SSDFS_BTREE_SEARCH_FAILURE;
+ search->result.err = err;
+ return err;
+ }
+
+ spin_lock(&node->descriptor_lock);
+ search->result.search_cno = ssdfs_current_cno(fsi->sb);
+ node->update_cno = search->result.search_cno;
+ spin_unlock(&node->descriptor_lock);
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("node->update_cno %llu\n",
+ search->result.search_cno);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ set_ssdfs_btree_node_dirty(node);
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("finished\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ return 0;
+}
+
+/*
+ * ssdfs_btree_node_check_result_for_delete() - check search result for delete
+ * @search: btree search object
+ */
+static inline
+int ssdfs_btree_node_check_result_for_delete(struct ssdfs_btree_search *search)
+{
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!search);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ switch (search->result.state) {
+ case SSDFS_BTREE_SEARCH_VALID_ITEM:
+ /* expected state */
+ break;
+
+ default:
+ SSDFS_WARN("invalid search result state\n");
+ return -ERANGE;
+ }
+
+ return 0;
+}
+
+/*
+ * ssdfs_btree_node_delete_item() - delete the item from the node
+ * @search: btree search object
+ *
+ * This method tries to delete an item from the node.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-EINVAL - invalid input.
+ * %-ERANGE - internal error.
+ * %-ENODATA - node doesn't contain the item.
+ * %-ENOENT - node's items area is empty.
+ * %-EACCES - node is under initialization yet.
+ * %-EAGAIN - search object contains obsolete result.
+ */
+int ssdfs_btree_node_delete_item(struct ssdfs_btree_search *search)
+{
+ struct ssdfs_fs_info *fsi;
+ struct ssdfs_btree_node *node;
+ u16 items_count, index_count;
+ bool is_node_empty = false;
+ u16 flags;
+ int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!search);
+ BUG_ON(search->request.start.hash > search->request.end.hash);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("type %#x, flags %#x, "
+ "start_hash %llx, end_hash %llx, "
+ "state %#x, node_id %u, height %u, "
+ "parent %p, child %p\n",
+ search->request.type, search->request.flags,
+ search->request.start.hash, search->request.end.hash,
+ search->node.state, search->node.id,
+ search->node.height, search->node.parent,
+ search->node.child);
+#else
+ SSDFS_DBG("type %#x, flags %#x, "
+ "start_hash %llx, end_hash %llx, "
+ "state %#x, node_id %u, height %u, "
+ "parent %p, child %p\n",
+ search->request.type, search->request.flags,
+ search->request.start.hash, search->request.end.hash,
+ search->node.state, search->node.id,
+ search->node.height, search->node.parent,
+ search->node.child);
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ node = search->node.child;
+ if (!node) {
+ SSDFS_WARN("child node is NULL\n");
+ return -ERANGE;
+ }
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!node->tree || !node->tree->fsi);
+ BUG_ON(!rwsem_is_locked(&node->tree->lock));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ fsi = node->tree->fsi;
+
+ switch (atomic_read(&node->state)) {
+ case SSDFS_BTREE_NODE_CREATED:
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("node %u is under initialization\n",
+ node->node_id);
+#endif /* CONFIG_SSDFS_DEBUG */
+ return -EACCES;
+
+ case SSDFS_BTREE_NODE_INITIALIZED:
+ case SSDFS_BTREE_NODE_DIRTY:
+ /* expected state */
+ break;
+
+ default:
+ SSDFS_ERR("invalid node state %#x\n",
+ atomic_read(&node->state));
+ return -ERANGE;
+ }
+
+ if (!is_btree_search_node_desc_consistent(search)) {
+ SSDFS_WARN("node descriptor is inconsistent\n");
+ return -ERANGE;
+ }
+
+ err = ssdfs_btree_node_check_result_for_delete(search);
+ if (err)
+ return err;
+
+ switch (atomic_read(&node->items_area.state)) {
+ case SSDFS_BTREE_NODE_ITEMS_AREA_EXIST:
+ /* expected state */
+ break;
+
+ case SSDFS_BTREE_NODE_AREA_ABSENT:
+ SSDFS_WARN("items area is absent: node_id %u\n",
+ search->node.id);
+ return -ENOENT;
+
+ default:
+ SSDFS_WARN("invalid items area state: node_id %u\n",
+ search->node.id);
+ return -ERANGE;
+ }
+
+ if (!node->node_ops || !node->node_ops->delete_item) {
+ SSDFS_WARN("unable to delete item\n");
+ return -EOPNOTSUPP;
+ }
+
+ err = node->node_ops->delete_item(node, search);
+ if (unlikely(err)) {
+ SSDFS_ERR("fail to delete item: "
+ "node %u, "
+ "request (start_hash %llx, end_hash %llx), "
+ "err %d\n",
+ node->node_id,
+ search->request.start.hash,
+ search->request.end.hash,
+ err);
+
+ search->result.state = SSDFS_BTREE_SEARCH_FAILURE;
+ search->result.err = err;
+ return err;
+ }
+
+ down_read(&node->header_lock);
+
+ switch (atomic_read(&node->items_area.state)) {
+ case SSDFS_BTREE_NODE_ITEMS_AREA_EXIST:
+ items_count = node->items_area.items_count;
+ break;
+
+ default:
+ items_count = 0;
+ break;
+ }
+
+ switch (atomic_read(&node->index_area.state)) {
+ case SSDFS_BTREE_NODE_INDEX_AREA_EXIST:
+ index_count = node->index_area.index_count;
+ break;
+
+ default:
+ index_count = 0;
+ break;
+ }
+
+ is_node_empty = index_count == 0 && items_count == 0;
+
+ up_read(&node->header_lock);
+
+ spin_lock(&node->descriptor_lock);
+ search->result.search_cno = ssdfs_current_cno(fsi->sb);
+ node->update_cno = search->result.search_cno;
+ if (is_node_empty) {
+ flags = le16_to_cpu(node->node_index.flags);
+ flags = SSDFS_BTREE_INDEX_SHOW_EMPTY_NODE;
+ node->node_index.flags = cpu_to_le16(flags);
+ }
+ spin_unlock(&node->descriptor_lock);
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("node->update_cno %llu\n",
+ search->result.search_cno);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ set_ssdfs_btree_node_dirty(node);
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("finished\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ return 0;
+}
+
+/*
+ * ssdfs_btree_node_delete_range() - delete the range of items from the node
+ * @search: btree search object
+ *
+ * This method tries to delete a range of items from the node.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-EINVAL - invalid input.
+ * %-ERANGE - internal error.
+ * %-ENODATA - node doesn't contain the range of items.
+ * %-ENOENT - node's items area is empty.
+ * %-EACCES - node is under initialization yet.
+ * %-EAGAIN - search object contains obsolete result.
+ */
+int ssdfs_btree_node_delete_range(struct ssdfs_btree_search *search)
+{
+ struct ssdfs_fs_info *fsi;
+ struct ssdfs_btree_node *node;
+ u16 items_count, index_count;
+ bool is_node_empty = false;
+ u16 flags;
+ int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!search);
+ BUG_ON(search->request.start.hash > search->request.end.hash);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("type %#x, flags %#x, "
+ "start_hash %llx, end_hash %llx, "
+ "state %#x, node_id %u, height %u, "
+ "parent %p, child %p\n",
+ search->request.type, search->request.flags,
+ search->request.start.hash, search->request.end.hash,
+ search->node.state, search->node.id,
+ search->node.height, search->node.parent,
+ search->node.child);
+#else
+ SSDFS_DBG("type %#x, flags %#x, "
+ "start_hash %llx, end_hash %llx, "
+ "state %#x, node_id %u, height %u, "
+ "parent %p, child %p\n",
+ search->request.type, search->request.flags,
+ search->request.start.hash, search->request.end.hash,
+ search->node.state, search->node.id,
+ search->node.height, search->node.parent,
+ search->node.child);
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ node = search->node.child;
+ if (!node) {
+ SSDFS_WARN("child node is NULL\n");
+ return -ERANGE;
+ }
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!node->tree || !node->tree->fsi);
+ BUG_ON(!rwsem_is_locked(&node->tree->lock));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ fsi = node->tree->fsi;
+
+ switch (atomic_read(&node->state)) {
+ case SSDFS_BTREE_NODE_CREATED:
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("node %u is under initialization\n",
+ node->node_id);
+#endif /* CONFIG_SSDFS_DEBUG */
+ return -EACCES;
+
+ case SSDFS_BTREE_NODE_INITIALIZED:
+ case SSDFS_BTREE_NODE_DIRTY:
+ /* expected state */
+ break;
+
+ default:
+ SSDFS_ERR("invalid node state %#x\n",
+ atomic_read(&node->state));
+ return -ERANGE;
+ }
+
+ if (!is_btree_search_node_desc_consistent(search)) {
+ SSDFS_WARN("node descriptor is inconsistent\n");
+ return -ERANGE;
+ }
+
+ err = ssdfs_btree_node_check_result_for_delete(search);
+ if (err)
+ return err;
+
+ switch (atomic_read(&node->items_area.state)) {
+ case SSDFS_BTREE_NODE_ITEMS_AREA_EXIST:
+ /* expected state */
+ break;
+
+ case SSDFS_BTREE_NODE_AREA_ABSENT:
+ SSDFS_WARN("items area is absent: node_id %u\n",
+ search->node.id);
+ return -ENOENT;
+
+ default:
+ SSDFS_WARN("invalid items area state: node_id %u\n",
+ search->node.id);
+ return -ERANGE;
+ }
+
+ if (!node->node_ops || !node->node_ops->delete_range) {
+ SSDFS_WARN("unable to delete item\n");
+ return -EOPNOTSUPP;
+ }
+
+ err = node->node_ops->delete_range(node, search);
+ if (unlikely(err)) {
+ SSDFS_ERR("fail to delete range: "
+ "node %u, "
+ "request (start_hash %llx, end_hash %llx), "
+ "err %d\n",
+ node->node_id,
+ search->request.start.hash,
+ search->request.end.hash,
+ err);
+
+ search->result.state = SSDFS_BTREE_SEARCH_FAILURE;
+ search->result.err = err;
+ return err;
+ }
+
+ down_read(&node->header_lock);
+
+ switch (atomic_read(&node->items_area.state)) {
+ case SSDFS_BTREE_NODE_ITEMS_AREA_EXIST:
+ items_count = node->items_area.items_count;
+ break;
+
+ default:
+ items_count = 0;
+ break;
+ }
+
+ switch (atomic_read(&node->index_area.state)) {
+ case SSDFS_BTREE_NODE_INDEX_AREA_EXIST:
+ index_count = node->index_area.index_count;
+ break;
+
+ default:
+ index_count = 0;
+ break;
+ }
+
+ is_node_empty = index_count == 0 && items_count == 0;
+
+ up_read(&node->header_lock);
+
+ spin_lock(&node->descriptor_lock);
+ search->result.search_cno = ssdfs_current_cno(fsi->sb);
+ node->update_cno = search->result.search_cno;
+ if (is_node_empty) {
+ flags = le16_to_cpu(node->node_index.flags);
+ flags = SSDFS_BTREE_INDEX_SHOW_EMPTY_NODE;
+ node->node_index.flags = cpu_to_le16(flags);
+ }
+ spin_unlock(&node->descriptor_lock);
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("node->update_cno %llu\n",
+ search->result.search_cno);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ set_ssdfs_btree_node_dirty(node);
+
+#ifdef CONFIG_SSDFS_TRACK_API_CALL
+ SSDFS_ERR("finished\n");
+#endif /* CONFIG_SSDFS_TRACK_API_CALL */
+
+ return 0;
+}
+
+/*
+ * ssdfs_btree_node_clear_range() - clear range of deleted items
+ * @node: pointer on node object
+ * @area: items area descriptor
+ * @item_size: size of item in bytes
+ * @search: search object
+ *
+ * This method tries to clear the range of deleted items.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-ERANGE - internal error.
+ */
+int __ssdfs_btree_node_clear_range(struct ssdfs_btree_node *node,
+ struct ssdfs_btree_node_items_area *area,
+ size_t item_size,
+ u16 start_index,
+ unsigned int range_len)
+{
+ int page_index;
+ int dst_index;
+ struct page *page;
+ u32 item_offset;
+ u16 cleared_items = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!node || !area);
+ BUG_ON(!rwsem_is_locked(&node->full_lock));
+
+ SSDFS_DBG("node_id %u, item_size %zu\n",
+ node->node_id, item_size);
+ SSDFS_DBG("start_index %u, range_len %u\n",
+ start_index, range_len);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ if (range_len == 0) {
+ SSDFS_WARN("range_len == 0\n");
+ return -ERANGE;
+ }
+
+ if ((start_index + range_len) > area->items_capacity) {
+ SSDFS_ERR("range is out of capacity: "
+ "start_index %u, range_len %u, items_capacity %u\n",
+ start_index, range_len, area->items_capacity);
+ return -ERANGE;
+ }
+
+ dst_index = start_index;
+
+ do {
+ u32 clearing_items;
+ u32 vacant_positions;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("start_index %u, dst_index %d\n",
+ start_index, dst_index);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ item_offset = (u32)dst_index * item_size;
+ if (item_offset >= area->area_size) {
+ SSDFS_ERR("item_offset %u >= area_size %u\n",
+ item_offset, area->area_size);
+ return -ERANGE;
+ }
+
+ item_offset += area->offset;
+ if (item_offset >= node->node_size) {
+ SSDFS_ERR("item_offset %u >= node_size %u\n",
+ item_offset, node->node_size);
+ return -ERANGE;
+ }
+
+ page_index = item_offset >> PAGE_SHIFT;
+ if (page_index >= pagevec_count(&node->content.pvec)) {
+ SSDFS_ERR("invalid page_index: "
+ "index %d, pvec_size %u\n",
+ page_index,
+ pagevec_count(&node->content.pvec));
+ return -ERANGE;
+ }
+
+ if (page_index > 0)
+ item_offset %= page_index * PAGE_SIZE;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(start_index > dst_index);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ clearing_items = dst_index - start_index;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(clearing_items > range_len);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ clearing_items = range_len - clearing_items;
+
+ if (clearing_items == 0) {
+ SSDFS_WARN("no items for clearing\n");
+ return -ERANGE;
+ }
+
+ vacant_positions = PAGE_SIZE - item_offset;
+ vacant_positions /= item_size;
+
+ if (vacant_positions == 0) {
+ SSDFS_WARN("invalid vacant_positions %u\n",
+ vacant_positions);
+ return -ERANGE;
+ }
+
+ clearing_items = min_t(u32, clearing_items, vacant_positions);
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(clearing_items >= U16_MAX);
+
+ SSDFS_DBG("clearing_items %u, item_offset %u\n",
+ clearing_items, item_offset);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ if ((item_offset + (clearing_items * item_size)) > PAGE_SIZE) {
+ SSDFS_ERR("invalid request: "
+ "item_offset %u, clearing_items %u, "
+ "item_size %zu\n",
+ item_offset, clearing_items, item_size);
+ return -ERANGE;
+ }
+
+ page = node->content.pvec.pages[page_index];
+ ssdfs_memset_page(page, item_offset, PAGE_SIZE,
+ 0x0, clearing_items * item_size);
+
+ dst_index += clearing_items;
+ cleared_items += clearing_items;
+ } while (cleared_items < range_len);
+
+ if (cleared_items != range_len) {
+ SSDFS_ERR("cleared_items %u != range_len %u\n",
+ cleared_items, range_len);
+ return -ERANGE;
+ }
+
+ return 0;
+}
+
+/*
+ * ssdfs_btree_node_clear_range() - clear range of deleted items
+ * @node: pointer on node object
+ * @area: items area descriptor
+ * @item_size: size of item in bytes
+ * @search: search object
+ *
+ * This method tries to clear the range of deleted items.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-ERANGE - internal error.
+ */
+int ssdfs_btree_node_clear_range(struct ssdfs_btree_node *node,
+ struct ssdfs_btree_node_items_area *area,
+ size_t item_size,
+ struct ssdfs_btree_search *search)
+{
+ u16 start_index;
+ unsigned int range_len;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!node || !area || !search);
+ BUG_ON(!rwsem_is_locked(&node->full_lock));
+
+ SSDFS_DBG("node_id %u, item_size %zu\n",
+ node->node_id, item_size);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ start_index = search->result.start_index;
+ range_len = search->request.count;
+
+ return __ssdfs_btree_node_clear_range(node, area, item_size,
+ start_index, range_len);
+}
+
+/*
+ * ssdfs_btree_node_extract_range() - extract the range from the node
+ * @start_index: starting index in the node
+ * @count: count of items in the range
+ * @search: btree search object
+ *
+ * This method tries to extract a range of items from the node.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-EINVAL - invalid input.
+ * %-ERANGE - internal error.
+ * %-ENODATA - node doesn't contain items for the requested range.
+ * %-ENOENT - node hasn't the items area.
+ * %-EACCES - node is under initialization yet.
+ * %-EOPNOTSUPP - specialized extract method doesn't been implemented
+ */
+int ssdfs_btree_node_extract_range(u16 start_index, u16 count,
+ struct ssdfs_btree_search *search)
+{
+ struct ssdfs_btree_node *node;
+ int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!search);
+
+ SSDFS_DBG("type %#x, flags %#x, "
+ "start_hash %llx, end_hash %llx, "
+ "state %#x, node_id %u, height %u, "
+ "parent %p, child %p, "
+ "start_index %u, count %u\n",
+ search->request.type, search->request.flags,
+ search->request.start.hash, search->request.end.hash,
+ search->node.state, search->node.id,
+ search->node.height, search->node.parent,
+ search->node.child, start_index, count);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ node = search->node.child;
+ if (!node) {
+ SSDFS_WARN("child node is NULL\n");
+ return -ERANGE;
+ }
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!node->tree);
+ BUG_ON(!rwsem_is_locked(&node->tree->lock));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ switch (atomic_read(&node->state)) {
+ case SSDFS_BTREE_NODE_CREATED:
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("node %u is under initialization\n",
+ node->node_id);
+#endif /* CONFIG_SSDFS_DEBUG */
+ return -EACCES;
+
+ case SSDFS_BTREE_NODE_INITIALIZED:
+ case SSDFS_BTREE_NODE_DIRTY:
+ /* expected state */
+ break;
+
+ default:
+ SSDFS_ERR("invalid node state %#x\n",
+ atomic_read(&node->state));
+ return -ERANGE;
+ }
+
+ switch (atomic_read(&node->items_area.state)) {
+ case SSDFS_BTREE_NODE_ITEMS_AREA_EXIST:
+ /* expected state */
+ break;
+
+ case SSDFS_BTREE_NODE_AREA_ABSENT:
+ SSDFS_WARN("items area is absent: node_id %u\n",
+ search->node.id);
+ return -ENOENT;
+
+ default:
+ SSDFS_WARN("invalid items area state: node_id %u\n",
+ search->node.id);
+ return -ERANGE;
+ }
+
+ if (!node->node_ops || !node->node_ops->extract_range) {
+ SSDFS_WARN("unable to extract the range from the node\n");
+ return -EOPNOTSUPP;
+ }
+
+ err = node->node_ops->extract_range(node, start_index, count, search);
+ if (err == -ENODATA) {
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("node %u "
+ "hasn't item for request "
+ "(start_index %u, count %u)\n",
+ node->node_id,
+ start_index, count);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ search->result.state = SSDFS_BTREE_SEARCH_EMPTY_RESULT;
+ search->result.err = err;
+ } else if (err == -ENOENT) {
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("node %u hasn't items area\n",
+ node->node_id);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ search->result.state = SSDFS_BTREE_SEARCH_FAILURE;
+ search->result.err = err;
+ } else if (unlikely(err)) {
+ SSDFS_ERR("fail to extract the range: "
+ "node %u, "
+ "request (start_index %u, count %u), "
+ "err %d\n",
+ node->node_id,
+ start_index, count, err);
+
+ search->result.state = SSDFS_BTREE_SEARCH_FAILURE;
+ search->result.err = err;
+ }
+
+ return err;
+}
+
+/*
+ * __ssdfs_btree_node_move_items_range() - move range between nodes
+ * @src: source node
+ * @dst: destination node
+ * @start_item: starting index of the item
+ * @count: count of items in the range
+ *
+ * This method tries to move a range of items from @src node into
+ * @dst node.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-ERANGE - internal error.
+ * %-EFAULT - node is corrupted.
+ * %-ENOMEM - fail to allocate memory.
+ * %-ENODATA - no such range in the node.
+ */
+static
+int __ssdfs_btree_node_move_items_range(struct ssdfs_btree_node *src,
+ struct ssdfs_btree_node *dst,
+ u16 start_item, u16 count)
+{
+ struct ssdfs_btree_search *search;
+ int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!src || !dst);
+
+ SSDFS_DBG("src node_id %u, dst node_id %u, "
+ "start_item %u, count %u\n",
+ src->node_id, dst->node_id,
+ start_item, count);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ search = ssdfs_btree_search_alloc();
+ if (!search) {
+ SSDFS_ERR("fail to allocate btree search object\n");
+ return -ENOMEM;
+ }
+
+ ssdfs_btree_search_init(search);
+
+ if (!src->node_ops) {
+ if (!src->node_ops->extract_range) {
+ SSDFS_WARN("unable to extract the items range\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (!src->node_ops->delete_range) {
+ SSDFS_WARN("unable to delete the items range\n");
+ return -EOPNOTSUPP;
+ }
+ }
+
+ if (!dst->node_ops) {
+ if (!dst->node_ops->find_range) {
+ SSDFS_WARN("unable to find the items range\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (!dst->node_ops->insert_range) {
+ SSDFS_WARN("unable to insert the items range\n");
+ return -EOPNOTSUPP;
+ }
+ }
+
+ err = src->node_ops->extract_range(src, start_item, count, search);
+ if (unlikely(err)) {
+ SSDFS_ERR("fail to extract range: "
+ "node_id %u, start_item %u, "
+ "count %u, err %d\n",
+ src->node_id, start_item, count, err);
+ goto finish_move_items_range;
+ }
+
+ ssdfs_debug_btree_search_object(search);
+
+ if (count != search->result.count) {
+ err = -ERANGE;
+ SSDFS_ERR("invalid count (request %u, result %u)\n",
+ count, search->result.count);
+ goto finish_move_items_range;
+ }
+
+ switch (src->tree->type) {
+ case SSDFS_EXTENTS_BTREE:
+ case SSDFS_XATTR_BTREE:
+ search->request.flags |= SSDFS_BTREE_SEARCH_NOT_INVALIDATE;
+ break;
+
+ default:
+ /* continue logic */
+ break;
+ }
+
+ search->request.type = SSDFS_BTREE_SEARCH_DELETE_RANGE;
+
+ err = src->node_ops->delete_range(src, search);
+ if (unlikely(err)) {
+ SSDFS_ERR("fail to delete range: "
+ "node_id %u, start_item %u, "
+ "count %u, err %d\n",
+ src->node_id, start_item, count, err);
+ goto finish_move_items_range;
+ }
+
+ search->request.type = SSDFS_BTREE_SEARCH_ADD_RANGE;
+
+ err = dst->node_ops->find_range(dst, search);
+ if (err == -ENODATA) {
+ err = 0;
+ /*
+ * Node is empty. We are ready to insert.
+ */
+ } else if (unlikely(err)) {
+ SSDFS_ERR("fail to find range: "
+ "node_id %u, err %d\n",
+ dst->node_id, err);
+ goto finish_move_items_range;
+ }
+
+ err = dst->node_ops->insert_range(dst, search);
+ if (unlikely(err)) {
+ SSDFS_ERR("fail to insert range: "
+ "node_id %u, err %d\n",
+ dst->node_id, err);
+ goto finish_move_items_range;
+ }
+
+finish_move_items_range:
+ ssdfs_btree_search_free(search);
+ return err;
+}
+
+/*
+ * ssdfs_btree_node_move_items_range() - move items range
+ * @src: source node
+ * @dst: destination node
+ * @start_item: startig index of the item
+ * @count: count of items in the range
+ *
+ * This method tries to move the range of items from @src into @dst.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-EINVAL - invalid input.
+ * %-ERANGE - internal error.
+ * %-ENOENT - items area is absent.
+ * %-EOPNOTSUPP - btree doesn't support the items moving operation.
+ */
+int ssdfs_btree_node_move_items_range(struct ssdfs_btree_node *src,
+ struct ssdfs_btree_node *dst,
+ u16 start_item, u16 count)
+{
+ struct ssdfs_fs_info *fsi;
+ int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!src || !dst);
+ BUG_ON(!src->tree);
+ BUG_ON(!rwsem_is_locked(&src->tree->lock));
+
+ SSDFS_DBG("src node_id %u, dst node_id %u, "
+ "start_item %u, count %u\n",
+ src->node_id, dst->node_id,
+ start_item, count);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ fsi = src->tree->fsi;
+
+ switch (atomic_read(&src->state)) {
+ case SSDFS_BTREE_NODE_INITIALIZED:
+ case SSDFS_BTREE_NODE_DIRTY:
+ /* expected state */
+ break;
+
+ default:
+ SSDFS_ERR("invalid src node state %#x\n",
+ atomic_read(&src->state));
+ return -ERANGE;
+ }
+
+ switch (atomic_read(&dst->state)) {
+ case SSDFS_BTREE_NODE_CREATED:
+ case SSDFS_BTREE_NODE_INITIALIZED:
+ case SSDFS_BTREE_NODE_DIRTY:
+ /* expected state */
+ break;
+
+ default:
+ SSDFS_ERR("invalid dst node state %#x\n",
+ atomic_read(&dst->state));
+ return -ERANGE;
+ }
+
+ switch (atomic_read(&src->items_area.state)) {
+ case SSDFS_BTREE_NODE_ITEMS_AREA_EXIST:
+ /* expected state */
+ break;
+
+ case SSDFS_BTREE_NODE_AREA_ABSENT:
+ SSDFS_WARN("items area is absent: node_id %u\n",
+ src->node_id);
+ return -ENOENT;
+
+ default:
+ SSDFS_WARN("invalid items area state: node_id %u\n",
+ src->node_id);
+ return -ERANGE;
+ }
+
+ switch (atomic_read(&dst->items_area.state)) {
+ case SSDFS_BTREE_NODE_ITEMS_AREA_EXIST:
+ /* expected state */
+ break;
+
+ case SSDFS_BTREE_NODE_AREA_ABSENT:
+ SSDFS_WARN("items area is absent: node_id %u\n",
+ dst->node_id);
+ return -ENOENT;
+
+ default:
+ SSDFS_WARN("invalid items area state: node_id %u\n",
+ dst->node_id);
+ return -ERANGE;
+ }
+
+ if (!src->node_ops) {
+ SSDFS_WARN("unable to move the items range\n");
+ return -EOPNOTSUPP;
+ } else if (!src->node_ops->move_items_range) {
+ err = __ssdfs_btree_node_move_items_range(src, dst,
+ start_item, count);
+ } else {
+ err = src->node_ops->move_items_range(src, dst,
+ start_item, count);
+ }
+
+ if (unlikely(err)) {
+ SSDFS_ERR("fail to move the items range: "
+ "src node_id %u, dst node_id %u, "
+ "start_item %u, count %u\n",
+ src->node_id, dst->node_id,
+ start_item, count);
+ return err;
+ }
+
+ ssdfs_set_node_update_cno(src);
+ ssdfs_set_node_update_cno(dst);
+ set_ssdfs_btree_node_dirty(src);
+ set_ssdfs_btree_node_dirty(dst);
+
+ return 0;
+}
+
+/*
+ * ssdfs_copy_item_in_buffer() - copy item from node into buffer
+ * @node: pointer on node object
+ * @index: item index
+ * @item_size: size of item in bytes
+ * @search: pointer on search request object [in|out]
+ *
+ * This method tries to copy item from the node into buffer.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-ERANGE - internal error.
+ */
+int ssdfs_copy_item_in_buffer(struct ssdfs_btree_node *node,
+ u16 index,
+ size_t item_size,
+ struct ssdfs_btree_search *search)
+{
+ DEFINE_WAIT(wait);
+ struct ssdfs_state_bitmap *bmap;
+ u32 area_offset;
+ u32 area_size;
+ u32 item_offset;
+ u32 buf_offset;
+ int page_index;
+ struct page *page;
+ int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!node || !search);
+
+ SSDFS_DBG("node_id %u, index %u\n",
+ node->node_id, index);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ down_read(&node->header_lock);
+ area_offset = node->items_area.offset;
+ area_size = node->items_area.area_size;
+ up_read(&node->header_lock);
+
+ item_offset = (u32)index * item_size;
+ if (item_offset >= area_size) {
+ SSDFS_ERR("item_offset %u >= area_size %u\n",
+ item_offset, area_size);
+ return -ERANGE;
+ }
+
+ item_offset += area_offset;
+ if (item_offset >= node->node_size) {
+ SSDFS_ERR("item_offset %u >= node_size %u\n",
+ item_offset, node->node_size);
+ return -ERANGE;
+ }
+
+ page_index = item_offset >> PAGE_SHIFT;
+
+ if (page_index > 0)
+ item_offset %= page_index * PAGE_SIZE;
+
+ down_read(&node->full_lock);
+
+ if (page_index >= pagevec_count(&node->content.pvec)) {
+ err = -ERANGE;
+ SSDFS_ERR("invalid page_index: "
+ "index %d, pvec_size %u\n",
+ page_index,
+ pagevec_count(&node->content.pvec));
+ goto finish_copy_item;
+ }
+
+ page = node->content.pvec.pages[page_index];
+ bmap = &node->bmap_array.bmap[SSDFS_BTREE_NODE_LOCK_BMAP];
+
+ down_read(&node->bmap_array.lock);
+
+try_lock_area:
+ spin_lock(&bmap->lock);
+
+ err = bitmap_allocate_region(bmap->ptr, (unsigned int)index, 0);
+ if (err == -EBUSY) {
+ err = 0;
+ prepare_to_wait(&node->wait_queue, &wait,
+ TASK_UNINTERRUPTIBLE);
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("waiting unlocked state of item %u\n",
+ index);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ spin_unlock(&bmap->lock);
+
+ schedule();
+ finish_wait(&node->wait_queue, &wait);
+ goto try_lock_area;
+ }
+
+ spin_unlock(&bmap->lock);
+
+ up_read(&node->bmap_array.lock);
+
+ if (err) {
+ SSDFS_ERR("fail to lock: index %u, err %d\n",
+ index, err);
+ goto finish_copy_item;
+ }
+
+ if (!search->result.buf) {
+ err = -ERANGE;
+ SSDFS_ERR("buffer is not created\n");
+ goto finish_copy_item;
+ }
+
+ buf_offset = search->result.items_in_buffer * item_size;
+
+ err = ssdfs_memcpy_from_page(search->result.buf,
+ buf_offset, search->result.buf_size,
+ page, item_offset, PAGE_SIZE,
+ item_size);
+ if (unlikely(err)) {
+ SSDFS_ERR("fail to copy: err %d\n", err);
+ goto unlock_area;
+ }
+
+ search->result.items_in_buffer++;
+
+unlock_area:
+ down_read(&node->bmap_array.lock);
+ spin_lock(&bmap->lock);
+ bitmap_clear(bmap->ptr, (unsigned int)index, 1);
+ spin_unlock(&bmap->lock);
+ up_read(&node->bmap_array.lock);
+
+ wake_up_all(&node->wait_queue);
+
+finish_copy_item:
+ up_read(&node->full_lock);
+
+ if (unlikely(err))
+ return err;
+
+ return 0;
+}
+
+/*
+ * ssdfs_lock_items_range() - lock range of items in the node
+ * @node: pointer on node object
+ * @start_index: start index of the range
+ * @count: count of items in the range
+ *
+ * This method tries to lock range of items in the node.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-ERANGE - internal error.
+ * %-ENOENT - unable to lock the node's header
+ * %-ENODATA - unable to lock the range of items
+ */
+int ssdfs_lock_items_range(struct ssdfs_btree_node *node,
+ u16 start_index, u16 count)
+{
+ DEFINE_WAIT(wait);
+ struct ssdfs_state_bitmap *bmap;
+ unsigned long start_area;
+ int i = 0;
+ int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!node);
+ BUG_ON(!rwsem_is_locked(&node->full_lock));
+
+ SSDFS_DBG("start_index %u, count %u\n",
+ start_index, count);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ down_read(&node->bmap_array.lock);
+
+ start_area = node->bmap_array.item_start_bit;
+ if (start_area == ULONG_MAX) {
+ err = -ERANGE;
+ SSDFS_ERR("invalid items_area_start\n");
+ goto finish_lock;
+ }
+
+ bmap = &node->bmap_array.bmap[SSDFS_BTREE_NODE_LOCK_BMAP];
+ if (!bmap->ptr) {
+ err = -ERANGE;
+ SSDFS_WARN("lock bitmap is empty\n");
+ goto finish_lock;
+ }
+
+try_lock_area:
+ spin_lock(&bmap->lock);
+
+ for (; i < count; i++) {
+ err = bitmap_allocate_region(bmap->ptr,
+ start_area + start_index + i, 0);
+ if (err)
+ break;
+ }
+
+ if (err == -EBUSY) {
+ err = 0;
+ bitmap_clear(bmap->ptr, start_area + start_index, i);
+ prepare_to_wait(&node->wait_queue, &wait,
+ TASK_UNINTERRUPTIBLE);
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("waiting unlocked state of item %u\n",
+ start_index + i);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ spin_unlock(&bmap->lock);
+
+ schedule();
+ finish_wait(&node->wait_queue, &wait);
+ goto try_lock_area;
+ }
+
+ spin_unlock(&bmap->lock);
+
+finish_lock:
+ up_read(&node->bmap_array.lock);
+
+ return err;
+}
+
+/*
+ * ssdfs_unlock_items_range() - unlock range of items in the node
+ * @node: pointer on node object
+ * @start_index: start index of the range
+ * @count: count of items in the range
+ */
+void ssdfs_unlock_items_range(struct ssdfs_btree_node *node,
+ u16 start_index, u16 count)
+{
+ struct ssdfs_state_bitmap *bmap;
+ unsigned long start_area;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!node);
+ BUG_ON(!rwsem_is_locked(&node->full_lock));
+
+ SSDFS_DBG("start_index %u, count %u\n",
+ start_index, count);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ down_read(&node->bmap_array.lock);
+
+ bmap = &node->bmap_array.bmap[SSDFS_BTREE_NODE_LOCK_BMAP];
+ start_area = node->bmap_array.item_start_bit;
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!bmap->ptr);
+ BUG_ON(start_area == ULONG_MAX);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ spin_lock(&bmap->lock);
+ bitmap_clear(bmap->ptr, start_area + start_index, count);
+ spin_unlock(&bmap->lock);
+
+ up_read(&node->bmap_array.lock);
+ wake_up_all(&node->wait_queue);
+}
+
+/*
+ * ssdfs_allocate_items_range() - allocate range of items in bitmap
+ * @node: pointer on node object
+ * @search: pointer on search request object
+ * @items_capacity: items capacity in the node
+ * @start_index: start index of the range
+ * @count: count of items in the range
+ *
+ * This method tries to allocate range of items in bitmap.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-ERANGE - internal error.
+ * %-EEXIST - range is allocated already.
+ */
+int ssdfs_allocate_items_range(struct ssdfs_btree_node *node,
+ struct ssdfs_btree_search *search,
+ u16 items_capacity,
+ u16 start_index, u16 count)
+{
+ struct ssdfs_state_bitmap *bmap;
+ unsigned long found = ULONG_MAX;
+ unsigned long start_area;
+ int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!node || !search);
+ BUG_ON(!rwsem_is_locked(&node->full_lock));
+
+ SSDFS_DBG("items_capacity %u, start_index %u, count %u\n",
+ items_capacity, start_index, count);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ down_read(&node->bmap_array.lock);
+
+ start_area = node->bmap_array.item_start_bit;
+ if (start_area == ULONG_MAX) {
+ err = -ERANGE;
+ SSDFS_ERR("invalid items_area_start\n");
+ goto finish_allocate_items_range;
+ }
+
+ bmap = &node->bmap_array.bmap[SSDFS_BTREE_NODE_ALLOC_BMAP];
+ if (!bmap->ptr) {
+ err = -ERANGE;
+ SSDFS_WARN("alloc bitmap is empty\n");
+ goto finish_allocate_items_range;
+ }
+
+ spin_lock(&bmap->lock);
+
+ found = bitmap_find_next_zero_area(bmap->ptr,
+ start_area + items_capacity,
+ start_area + start_index,
+ count, 0);
+ if (search->request.flags & SSDFS_BTREE_SEARCH_HAS_VALID_HASH_RANGE &&
+ found != (start_area + start_index)) {
+ /* area is allocated already */
+ err = -EEXIST;
+ }
+
+ if (!err)
+ bitmap_set(bmap->ptr, found, count);
+
+ spin_unlock(&bmap->lock);
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("found %lu, start_area %lu, start_index %u\n",
+ found, start_area, start_index);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ if (unlikely(err)) {
+ SSDFS_ERR("found %lu != start %lu\n",
+ found, start_area + start_index);
+ }
+
+finish_allocate_items_range:
+ up_read(&node->bmap_array.lock);
+
+ return err;
+}
+
+/*
+ * is_ssdfs_node_items_range_allocated() - check that range is allocated
+ * @node: pointer on node object
+ * @items_capacity: items capacity in the node
+ * @start_index: start index of the range
+ * @count: count of items in the range
+ */
+bool is_ssdfs_node_items_range_allocated(struct ssdfs_btree_node *node,
+ u16 items_capacity,
+ u16 start_index, u16 count)
+{
+ struct ssdfs_state_bitmap *bmap;
+ unsigned long found = ULONG_MAX;
+ unsigned long start_area;
+ int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!node);
+ BUG_ON(!rwsem_is_locked(&node->full_lock));
+
+ SSDFS_DBG("start_index %u, count %u\n",
+ start_index, count);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ down_read(&node->bmap_array.lock);
+
+ start_area = node->bmap_array.item_start_bit;
+ BUG_ON(start_area == ULONG_MAX);
+
+ bmap = &node->bmap_array.bmap[SSDFS_BTREE_NODE_ALLOC_BMAP];
+ if (!bmap->ptr)
+ BUG();
+
+ spin_lock(&bmap->lock);
+ found = bitmap_find_next_zero_area(bmap->ptr,
+ start_area + items_capacity,
+ start_area + start_index, count, 0);
+ if (found != (start_area + start_index)) {
+ /* area is allocated already */
+ err = -EEXIST;
+ }
+ spin_unlock(&bmap->lock);
+
+ up_read(&node->bmap_array.lock);
+
+ if (err == -EEXIST)
+ return true;
+
+ return false;
+}
+
+/*
+ * ssdfs_free_items_range() - free range of items in bitmap
+ * @node: pointer on node object
+ * @start_index: start index of the range
+ * @count: count of items in the range
+ *
+ * This method tries to free the range of items in bitmap.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-ERANGE - internal error.
+ */
+int ssdfs_free_items_range(struct ssdfs_btree_node *node,
+ u16 start_index, u16 count)
+{
+ struct ssdfs_state_bitmap *bmap;
+ unsigned long start_area;
+ int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!node);
+ BUG_ON(!rwsem_is_locked(&node->full_lock));
+
+ SSDFS_DBG("start_index %u, count %u\n",
+ start_index, count);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ down_read(&node->bmap_array.lock);
+
+ start_area = node->bmap_array.item_start_bit;
+ if (start_area == ULONG_MAX) {
+ err = -ERANGE;
+ SSDFS_ERR("invalid items_area_start\n");
+ goto finish_free_items_range;
+ }
+
+ bmap = &node->bmap_array.bmap[SSDFS_BTREE_NODE_ALLOC_BMAP];
+ if (!bmap->ptr) {
+ err = -ERANGE;
+ SSDFS_WARN("alloc bitmap is empty\n");
+ goto finish_free_items_range;
+ }
+
+ spin_lock(&bmap->lock);
+ bitmap_clear(bmap->ptr, start_area + start_index, count);
+ spin_unlock(&bmap->lock);
+
+finish_free_items_range:
+ up_read(&node->bmap_array.lock);
+
+ return err;
+}
+
+/*
+ * ssdfs_set_node_header_dirty() - mark the node's header as dirty
+ * @node: pointer on node object
+ * @items_capacity: items capacity in the node
+ *
+ * This method tries to mark the node's header as dirty.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-ERANGE - internal error.
+ */
+int ssdfs_set_node_header_dirty(struct ssdfs_btree_node *node,
+ u16 items_capacity)
+{
+ struct ssdfs_state_bitmap *bmap;
+ unsigned long found = ULONG_MAX;
+ int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!node);
+ BUG_ON(!rwsem_is_locked(&node->full_lock));
+
+ SSDFS_DBG("node_id %u, items_capacity %u\n",
+ node->node_id, items_capacity);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ down_read(&node->bmap_array.lock);
+
+ bmap = &node->bmap_array.bmap[SSDFS_BTREE_NODE_DIRTY_BMAP];
+ if (!bmap->ptr) {
+ err = -ERANGE;
+ SSDFS_WARN("dirty bitmap is empty\n");
+ goto finish_set_header_dirty;
+ }
+
+ spin_lock(&bmap->lock);
+
+ found = bitmap_find_next_zero_area(bmap->ptr, items_capacity,
+ SSDFS_BTREE_NODE_HEADER_INDEX,
+ 1, 0);
+ if (found == SSDFS_BTREE_NODE_HEADER_INDEX)
+ bitmap_set(bmap->ptr, found, 1);
+
+ spin_unlock(&bmap->lock);
+
+finish_set_header_dirty:
+ up_read(&node->bmap_array.lock);
+
+ return err;
+}
+
+/*
+ * ssdfs_clear_node_header_dirty_state() - clear node's header dirty state
+ * @node: pointer on node object
+ *
+ * This method tries to clear the node's header dirty state.
+ */
+void ssdfs_clear_node_header_dirty_state(struct ssdfs_btree_node *node)
+{
+ struct ssdfs_state_bitmap *bmap;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!node);
+ BUG_ON(!rwsem_is_locked(&node->full_lock));
+
+ SSDFS_DBG("node_id %u\n", node->node_id);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ down_read(&node->bmap_array.lock);
+
+ bmap = &node->bmap_array.bmap[SSDFS_BTREE_NODE_DIRTY_BMAP];
+ if (!bmap->ptr)
+ BUG();
+
+ spin_lock(&bmap->lock);
+ bitmap_clear(bmap->ptr, SSDFS_BTREE_NODE_HEADER_INDEX, 1);
+ spin_unlock(&bmap->lock);
+
+ up_read(&node->bmap_array.lock);
+}
+
+/*
+ * ssdfs_set_dirty_items_range() - mark the range of items as dirty
+ * @node: pointer on node object
+ * @items_capacity: items capacity in the node
+ * @start_index: start index of the range
+ * @count: count of items in the range
+ *
+ * This method tries to mark the range of items as dirty.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-ERANGE - internal error.
+ */
+int ssdfs_set_dirty_items_range(struct ssdfs_btree_node *node,
+ u16 items_capacity,
+ u16 start_index, u16 count)
+{
+ struct ssdfs_state_bitmap *bmap;
+ unsigned long found = ULONG_MAX;
+ unsigned long start_area;
+ int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!node);
+ BUG_ON(!rwsem_is_locked(&node->full_lock));
+
+ SSDFS_DBG("items_capacity %u, start_index %u, count %u\n",
+ items_capacity, start_index, count);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ down_read(&node->bmap_array.lock);
+
+ start_area = node->bmap_array.item_start_bit;
+ if (start_area == ULONG_MAX) {
+ err = -ERANGE;
+ SSDFS_ERR("invalid items_area_start\n");
+ goto finish_set_dirty_items;
+ }
+
+ bmap = &node->bmap_array.bmap[SSDFS_BTREE_NODE_DIRTY_BMAP];
+ if (!bmap->ptr) {
+ err = -ERANGE;
+ SSDFS_WARN("dirty bitmap is empty\n");
+ goto finish_set_dirty_items;
+ }
+
+ spin_lock(&bmap->lock);
+
+ found = bitmap_find_next_zero_area(bmap->ptr,
+ start_area + items_capacity,
+ start_area + start_index,
+ count, 0);
+ if (found != (start_area + start_index)) {
+ /* area is dirty already */
+ err = -EEXIST;
+ }
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("set bit: start_area %lu, start_index %u, len %u\n",
+ start_area, start_index, count);
+
+ SSDFS_DBG("BMAP DUMP\n");
+ print_hex_dump_bytes("", DUMP_PREFIX_OFFSET,
+ bmap->ptr,
+ node->bmap_array.bmap_bytes);
+ SSDFS_DBG("\n");
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ bitmap_set(bmap->ptr, start_area + start_index, count);
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("BMAP DUMP\n");
+ print_hex_dump_bytes("", DUMP_PREFIX_OFFSET,
+ bmap->ptr,
+ node->bmap_array.bmap_bytes);
+ SSDFS_DBG("\n");
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ spin_unlock(&bmap->lock);
+
+ if (unlikely(err)) {
+ err = 0;
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("found %lu != start %lu\n",
+ found, start_area + start_index);
+#endif /* CONFIG_SSDFS_DEBUG */
+ }
+
+finish_set_dirty_items:
+ up_read(&node->bmap_array.lock);
+
+ if (!err) {
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("node %u, tree_type %#x, "
+ "start_index %u, count %u\n",
+ node->node_id, node->tree->type,
+ start_index, count);
+#endif /* CONFIG_SSDFS_DEBUG */
+ }
+
+ return err;
+}
+
+/*
+ * ssdfs_clear_dirty_items_range_state() - clear items range's dirty state
+ * @node: pointer on node object
+ * @start_index: start index of the range
+ * @count: count of items in the range
+ *
+ * This method tries to clear the range of items' dirty state.
+ */
+void ssdfs_clear_dirty_items_range_state(struct ssdfs_btree_node *node,
+ u16 start_index, u16 count)
+{
+ struct ssdfs_state_bitmap *bmap;
+ unsigned long start_area;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!node);
+ BUG_ON(!rwsem_is_locked(&node->full_lock));
+
+ SSDFS_DBG("start_index %u, count %u\n",
+ start_index, count);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ down_read(&node->bmap_array.lock);
+
+ start_area = node->bmap_array.item_start_bit;
+ BUG_ON(start_area == ULONG_MAX);
+
+ bmap = &node->bmap_array.bmap[SSDFS_BTREE_NODE_DIRTY_BMAP];
+ BUG_ON(!bmap->ptr);
+
+ spin_lock(&bmap->lock);
+ bitmap_clear(bmap->ptr, start_area + start_index, count);
+ spin_unlock(&bmap->lock);
+
+ up_read(&node->bmap_array.lock);
+}
+
+/*
+ * is_last_leaf_node_found() - check that found leaf node is the last
+ * @search: pointer on search object
+ */
+bool is_last_leaf_node_found(struct ssdfs_btree_search *search)
+{
+ struct ssdfs_btree_node *parent;
+ u64 leaf_end_hash;
+ u64 index_end_hash;
+ int node_type = SSDFS_BTREE_LEAF_NODE;
+ spinlock_t * lock;
+ int state;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!search);
+
+ SSDFS_DBG("start_hash %llx, end_hash %llx, node_id %u\n",
+ search->request.start.hash,
+ search->request.end.hash,
+ search->node.id);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ if (!search->node.child) {
+ SSDFS_WARN("empty child node pointer\n");
+ return false;
+ }
+
+ if (!search->node.parent) {
+ SSDFS_WARN("empty parent node pointer\n");
+ return false;
+ }
+
+ switch (atomic_read(&search->node.child->items_area.state)) {
+ case SSDFS_BTREE_NODE_ITEMS_AREA_EXIST:
+ /* expected state */
+ break;
+
+ default:
+ SSDFS_WARN("invalid area state %#x\n",
+ atomic_read(&search->node.child->items_area.state));
+ return false;
+ }
+
+ down_read(&search->node.child->header_lock);
+ leaf_end_hash = search->node.child->items_area.end_hash;
+ up_read(&search->node.child->header_lock);
+
+ if (leaf_end_hash >= U64_MAX) {
+ SSDFS_WARN("leaf node end_hash %llx\n",
+ leaf_end_hash);
+ return false;
+ }
+
+ parent = search->node.parent;
+
+ do {
+ if (!parent) {
+ SSDFS_WARN("empty parent node pointer\n");
+ return false;
+ }
+
+ node_type = atomic_read(&parent->type);
+
+ switch (node_type) {
+ case SSDFS_BTREE_ROOT_NODE:
+ case SSDFS_BTREE_INDEX_NODE:
+ case SSDFS_BTREE_HYBRID_NODE:
+ state = atomic_read(&parent->index_area.state);
+
+ switch (state) {
+ case SSDFS_BTREE_NODE_INDEX_AREA_EXIST:
+ /* expected state */
+ break;
+
+ default:
+ SSDFS_WARN("invalid area state %#x\n",
+ state);
+ return false;
+ }
+
+ down_read(&parent->header_lock);
+ index_end_hash = parent->index_area.end_hash;
+ up_read(&parent->header_lock);
+
+ if (index_end_hash >= U64_MAX) {
+ SSDFS_WARN("index area: end hash %llx\n",
+ index_end_hash);
+ return false;
+ }
+
+ if (leaf_end_hash < index_end_hash) {
+ /* internal node */
+ return false;
+ }
+ break;
+
+ default:
+ SSDFS_WARN("invalid node type %#x\n",
+ node_type);
+ return false;
+ }
+
+ lock = &parent->descriptor_lock;
+ spin_lock(lock);
+ parent = parent->parent_node;
+ spin_unlock(lock);
+ lock = NULL;
+ } while (node_type != SSDFS_BTREE_ROOT_NODE);
+
+ return true;
+}
+
+/*
+ * ssdfs_btree_node_find_lookup_index_nolock() - find lookup index
+ * @search: search object
+ * @lookup_table: lookup table
+ * @table_capacity: capacity of the lookup table
+ * @lookup_index: lookup index [out]
+ *
+ * This method tries to find a lookup index for requested items.
+ * It needs to lock the lookup table before calling this method.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-ERANGE - internal error.
+ * %-ENODATA - lookup index doesn't exist for requested hash.
+ */
+int
+ssdfs_btree_node_find_lookup_index_nolock(struct ssdfs_btree_search *search,
+ __le64 *lookup_table,
+ int table_capacity,
+ u16 *lookup_index)
+{
+ u64 hash;
+ u64 lower_bound, upper_bound;
+ int index;
+ int lower_index, upper_index;
+ int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!search || !lookup_table || !lookup_index);
+
+ SSDFS_DBG("type %#x, flags %#x, "
+ "start_hash %llx, end_hash %llx, "
+ "lookup_table %p, table_capacity %d\n",
+ search->request.type, search->request.flags,
+ search->request.start.hash, search->request.end.hash,
+ lookup_table, table_capacity);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ *lookup_index = U16_MAX;
+ hash = search->request.start.hash;
+
+ if (hash >= U64_MAX) {
+ SSDFS_ERR("invalid hash for search\n");
+ return -ERANGE;
+ }
+
+ lower_index = 0;
+ lower_bound = le64_to_cpu(lookup_table[lower_index]);
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("lower_index %d, lower_bound %llu\n",
+ lower_index, lower_bound);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ if (lower_bound >= U64_MAX) {
+ err = -ENODATA;
+ *lookup_index = lower_index;
+ goto finish_index_search;
+ } else if (hash < lower_bound) {
+ err = -ENODATA;
+ *lookup_index = lower_index;
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("hash %llx < lower_bound %llx\n",
+ hash, lower_bound);
+#endif /* CONFIG_SSDFS_DEBUG */
+ goto finish_index_search;
+ } else if (hash == lower_bound) {
+ err = -EEXIST;
+ *lookup_index = lower_index;
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("hash %llx == lower_bound %llx\n",
+ hash, lower_bound);
+#endif /* CONFIG_SSDFS_DEBUG */
+ goto finish_index_search;
+ }
+
+ upper_index = table_capacity - 1;
+ upper_bound = le64_to_cpu(lookup_table[upper_index]);
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("upper_index %d, upper_bound %llu\n",
+ upper_index, upper_bound);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ if (upper_bound >= U64_MAX) {
+ /*
+ * continue to search
+ */
+ } else if (hash == upper_bound) {
+ err = -EEXIST;
+ *lookup_index = upper_index;
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("hash %llx == upper_bound %llx\n",
+ hash, upper_bound);
+#endif /* CONFIG_SSDFS_DEBUG */
+ goto finish_index_search;
+ } else if (hash > upper_bound) {
+ err = 0;
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("hash %llx > upper_bound %llx\n",
+ hash, upper_bound);
+#endif /* CONFIG_SSDFS_DEBUG */
+ *lookup_index = upper_index;
+ goto finish_index_search;
+ }
+
+ do {
+ int diff = upper_index - lower_index;
+
+ index = lower_index + (diff / 2);
+
+ lower_bound = le64_to_cpu(lookup_table[index]);
+ upper_bound = le64_to_cpu(lookup_table[index + 1]);
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("index %d, lower_index %d, upper_index %d, "
+ "lower_bound %llx, upper_bound %llx\n",
+ index, lower_index, upper_index,
+ lower_bound, upper_bound);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ if (lower_bound >= U64_MAX)
+ upper_index = index;
+ else if (hash < lower_bound)
+ upper_index = index;
+ else if (hash == lower_bound) {
+ err = -EEXIST;
+ *lookup_index = index;
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("hash %llx == lower_bound %llx\n",
+ hash, lower_bound);
+#endif /* CONFIG_SSDFS_DEBUG */
+ goto finish_index_search;
+ }
+
+ if (lower_bound < hash && upper_bound >= U64_MAX) {
+ err = 0;
+ *lookup_index = index;
+ goto finish_index_search;
+ } else if (lower_bound < hash && hash < upper_bound) {
+ err = 0;
+ lower_index = index;
+ } else if (hash == upper_bound) {
+ err = -EEXIST;
+ *lookup_index = index;
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("hash %llx == upper_bound %llx\n",
+ hash, upper_bound);
+#endif /* CONFIG_SSDFS_DEBUG */
+ goto finish_index_search;
+ } else if (hash > upper_bound)
+ lower_index = index;
+ } while ((upper_index - lower_index) > 1);
+
+ if ((upper_index - lower_index) > 1) {
+ err = -ERANGE;
+ SSDFS_ERR("lower_index %d, upper_index %d\n",
+ lower_index, upper_index);
+ goto finish_index_search;
+ }
+
+ *lookup_index = lower_index;
+
+finish_index_search:
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("lookup_index %u\n", *lookup_index);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ if (err == -EEXIST) {
+ /* index found */
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(*lookup_index >= table_capacity);
+#endif /* CONFIG_SSDFS_DEBUG */
+ return 0;
+ }
+
+ return err;
+}
+
+/*
+ * __ssdfs_extract_range_by_lookup_index() - extract a range of items
+ * @node: pointer on node object
+ * @lookup_index: lookup index for requested range
+ * @lookup_table_capacity: maximal number of items in lookup table
+ * @item_size: size of item in bytes
+ * @search: pointer on search request object
+ * @check_item: specialized method of checking item
+ * @prepare_buffer: specialized method of buffer preparing
+ * @get_hash_range: specialized method of getting hash range
+ *
+ * This method tries to extract a range of items from the node.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-ERANGE - internal error.
+ * %-ENODATA - requested range is out of the node.
+ */
+int __ssdfs_extract_range_by_lookup_index(struct ssdfs_btree_node *node,
+ u16 lookup_index,
+ int lookup_table_capacity,
+ size_t item_size,
+ struct ssdfs_btree_search *search,
+ ssdfs_check_found_item check_item,
+ ssdfs_prepare_result_buffer prepare_buffer,
+ ssdfs_extract_found_item extract_item)
+{
+ DEFINE_WAIT(wait);
+ struct ssdfs_fs_info *fsi;
+ struct ssdfs_state_bitmap *bmap;
+ u16 index, found_index;
+ u16 items_count;
+ u32 area_offset;
+ u32 area_size;
+ u32 item_offset;
+ u64 start_hash = U64_MAX;
+ u64 end_hash = U64_MAX;
+ int page_index;
+ struct page *page;
+ void *kaddr;
+ unsigned long start_index;
+ int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!node || !node->tree || !node->tree->fsi || !search);
+ BUG_ON(lookup_index >= lookup_table_capacity);
+
+ SSDFS_DBG("type %#x, flags %#x, "
+ "start_hash %llx, end_hash %llx, "
+ "state %d, node_id %u, height %d, "
+ "lookup_index %u\n",
+ search->request.type, search->request.flags,
+ search->request.start.hash, search->request.end.hash,
+ atomic_read(&node->state), node->node_id,
+ atomic_read(&node->height),
+ lookup_index);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ fsi = node->tree->fsi;
+
+ down_read(&node->header_lock);
+ area_offset = node->items_area.offset;
+ area_size = node->items_area.area_size;
+ items_count = node->items_area.items_count;
+ up_read(&node->header_lock);
+
+ if (items_count == 0) {
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("node_id %u is empty\n",
+ node->node_id);
+#endif /* CONFIG_SSDFS_DEBUG */
+ return -ENODATA;
+ }
+
+ found_index = U16_MAX;
+ index = __ssdfs_convert_lookup2item_index(lookup_index,
+ node->node_size,
+ item_size,
+ lookup_table_capacity);
+ if (index >= items_count) {
+ err = -ERANGE;
+ SSDFS_ERR("index %u >= items_count %u\n",
+ index, items_count);
+ return err;
+ }
+
+ down_read(&node->full_lock);
+
+ bmap = &node->bmap_array.bmap[SSDFS_BTREE_NODE_LOCK_BMAP];
+
+ if (found_index != U16_MAX)
+ goto try_extract_range;
+
+ for (; index < items_count; index++) {
+ item_offset = (u32)index * item_size;
+ if (item_offset >= area_size) {
+ err = -ERANGE;
+ SSDFS_ERR("item_offset %u >= area_size %u\n",
+ item_offset, area_size);
+ goto finish_extract_range;
+ }
+
+ item_offset += area_offset;
+ if (item_offset >= node->node_size) {
+ err = -ERANGE;
+ SSDFS_ERR("item_offset %u >= node_size %u\n",
+ item_offset, node->node_size);
+ goto finish_extract_range;
+ }
+
+ page_index = item_offset >> PAGE_SHIFT;
+
+ if (page_index > 0)
+ item_offset %= page_index * PAGE_SIZE;
+
+ if (page_index >= pagevec_count(&node->content.pvec)) {
+ err = -ERANGE;
+ SSDFS_ERR("invalid page_index: "
+ "index %d, pvec_size %u\n",
+ page_index,
+ pagevec_count(&node->content.pvec));
+ goto finish_extract_range;
+ }
+
+ page = node->content.pvec.pages[page_index];
+
+ down_read(&node->bmap_array.lock);
+
+try_lock_checking_item:
+ spin_lock(&bmap->lock);
+
+ start_index = node->bmap_array.item_start_bit + index;
+ err = bitmap_allocate_region(bmap->ptr,
+ (unsigned int)start_index, 0);
+ if (err == -EBUSY) {
+ err = 0;
+ prepare_to_wait(&node->wait_queue, &wait,
+ TASK_UNINTERRUPTIBLE);
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("waiting unlocked state of item %lu\n",
+ start_index);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ spin_unlock(&bmap->lock);
+
+ schedule();
+ finish_wait(&node->wait_queue, &wait);
+ goto try_lock_checking_item;
+ }
+
+ spin_unlock(&bmap->lock);
+
+ up_read(&node->bmap_array.lock);
+
+ if (err) {
+ SSDFS_ERR("fail to lock: index %lu, err %d\n",
+ start_index, err);
+ goto finish_extract_range;
+ }
+
+ if ((item_offset + item_size) > PAGE_SIZE) {
+ err = -ERANGE;
+ SSDFS_ERR("invalid offset: "
+ "item_offset %u, item_size %zu\n",
+ item_offset, item_size);
+ goto finish_extract_range;
+ }
+
+ kaddr = kmap_local_page(page);
+ err = check_item(fsi, search,
+ (u8 *)kaddr + item_offset,
+ index,
+ &start_hash, &end_hash,
+ &found_index);
+ kunmap_local(kaddr);
+
+ down_read(&node->bmap_array.lock);
+ spin_lock(&bmap->lock);
+ bitmap_clear(bmap->ptr, (unsigned int)start_index, 1);
+ spin_unlock(&bmap->lock);
+ up_read(&node->bmap_array.lock);
+
+ wake_up_all(&node->wait_queue);
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("requested (start_hash %llx, end_hash %llx), "
+ "start_hash %llx, end_hash %llx, "
+ "index %u, found_index %u, err %d\n",
+ search->request.start.hash, search->request.end.hash,
+ start_hash, end_hash, index, found_index, err);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ if (err == -EAGAIN)
+ continue;
+ else if (unlikely(err))
+ goto finish_extract_range;
+ else if (found_index != U16_MAX)
+ break;
+ }
+
+ if (err == -EAGAIN) {
+ if (found_index >= U16_MAX) {
+ SSDFS_ERR("fail to find index\n");
+ goto finish_extract_range;
+ } else if (found_index == items_count) {
+ err = 0;
+ found_index = items_count - 1;
+ } else {
+ err = -ERANGE;
+ SSDFS_ERR("fail to find index\n");
+ goto finish_extract_range;
+ }
+ }
+
+ if (found_index > items_count) {
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("found_index %u, items_count %u\n",
+ found_index, items_count);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ err = -ENODATA;
+ search->result.state =
+ SSDFS_BTREE_SEARCH_POSSIBLE_PLACE_FOUND;
+ search->result.err = -ENODATA;
+ search->result.start_index = items_count;
+ search->result.count = 1;
+ goto finish_extract_range;
+ } else if (is_btree_search_contains_new_item(search)) {
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("found_index %u, items_count %u\n",
+ found_index, items_count);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ err = -ENODATA;
+ search->result.state =
+ SSDFS_BTREE_SEARCH_POSSIBLE_PLACE_FOUND;
+ search->result.err = -ENODATA;
+ search->result.start_index = found_index;
+ search->result.count = 0;
+ goto finish_extract_range;
+ } else {
+ err = prepare_buffer(search, found_index,
+ search->request.start.hash,
+ search->request.end.hash,
+ items_count, item_size);
+ if (unlikely(err)) {
+ SSDFS_ERR("fail to prepare buffers: "
+ "requested (start_hash %llx, end_hash %llx), "
+ "found_index %u, start_hash %llx, "
+ "end_hash %llx, items_count %u, "
+ "item_size %zu, err %d\n",
+ search->request.start.hash,
+ search->request.end.hash,
+ found_index, start_hash, end_hash,
+ items_count, item_size, err);
+ goto finish_extract_range;
+ }
+
+ search->result.start_index = found_index;
+ search->result.count = 0;
+ }
+
+try_extract_range:
+ for (; found_index < items_count; found_index++) {
+ item_offset = (u32)found_index * item_size;
+ if (item_offset >= area_size) {
+ err = -ERANGE;
+ SSDFS_ERR("item_offset %u >= area_size %u\n",
+ item_offset, area_size);
+ goto finish_extract_range;
+ }
+
+ item_offset += area_offset;
+ if (item_offset >= node->node_size) {
+ err = -ERANGE;
+ SSDFS_ERR("item_offset %u >= node_size %u\n",
+ item_offset, node->node_size);
+ goto finish_extract_range;
+ }
+
+ page_index = item_offset >> PAGE_SHIFT;
+
+ if (page_index > 0)
+ item_offset %= page_index * PAGE_SIZE;
+
+ if (page_index >= pagevec_count(&node->content.pvec)) {
+ err = -ERANGE;
+ SSDFS_ERR("invalid page_index: "
+ "index %d, pvec_size %u\n",
+ page_index,
+ pagevec_count(&node->content.pvec));
+ goto finish_extract_range;
+ }
+
+ page = node->content.pvec.pages[page_index];
+
+ down_read(&node->bmap_array.lock);
+
+try_lock_extracting_item:
+ spin_lock(&bmap->lock);
+
+ start_index = node->bmap_array.item_start_bit + found_index;
+ err = bitmap_allocate_region(bmap->ptr,
+ (unsigned int)start_index, 0);
+ if (err == -EBUSY) {
+ err = 0;
+ prepare_to_wait(&node->wait_queue, &wait,
+ TASK_UNINTERRUPTIBLE);
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("waiting unlocked state of item %lu\n",
+ start_index);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ spin_unlock(&bmap->lock);
+
+ schedule();
+ finish_wait(&node->wait_queue, &wait);
+ goto try_lock_extracting_item;
+ }
+
+ spin_unlock(&bmap->lock);
+
+ up_read(&node->bmap_array.lock);
+
+ if (err) {
+ SSDFS_ERR("fail to lock: index %lu, err %d\n",
+ start_index, err);
+ goto finish_extract_range;
+ }
+
+ if ((item_offset + item_size) > PAGE_SIZE) {
+ err = -ERANGE;
+ SSDFS_ERR("invalid offset: "
+ "item_offset %u, item_size %zu\n",
+ item_offset, item_size);
+ goto finish_extract_range;
+ }
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("item_offset %u, item_size %zu\n",
+ item_offset, item_size);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ kaddr = kmap_local_page(page);
+ err = extract_item(fsi, search, item_size,
+ (u8 *)kaddr + item_offset,
+ &start_hash, &end_hash);
+ kunmap_local(kaddr);
+
+ down_read(&node->bmap_array.lock);
+ spin_lock(&bmap->lock);
+ bitmap_clear(bmap->ptr, (unsigned int)start_index, 1);
+ spin_unlock(&bmap->lock);
+ up_read(&node->bmap_array.lock);
+
+ wake_up_all(&node->wait_queue);
+
+ if (err == -ENODATA && search->result.count > 0) {
+ err = 0;
+ search->result.err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("stop search: "
+ "found_index %u, start_hash %llx, "
+ "end_hash %llx, search->request.end.hash %llx\n",
+ found_index, start_hash, end_hash,
+ search->request.end.hash);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ goto finish_extract_range;
+ } else if (unlikely(err)) {
+ SSDFS_ERR("fail to extract item: "
+ "kaddr %p, item_offset %u, err %d\n",
+ kaddr, item_offset, err);
+ goto finish_extract_range;
+ }
+
+#ifdef CONFIG_SSDFS_DEBUG
+ SSDFS_DBG("found_index %u, start_hash %llx, "
+ "end_hash %llx, search->request.end.hash %llx\n",
+ found_index, start_hash, end_hash,
+ search->request.end.hash);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ if (search->request.end.hash <= end_hash)
+ break;
+ }
+
+ if (search->request.end.hash > end_hash)
+ err = -EAGAIN;
+
+finish_extract_range:
+ up_read(&node->full_lock);
+
+ if (err == -ENODATA || err == -EAGAIN) {
+ /*
+ * do nothing
+ */
+ search->result.err = err;
+ } else if (unlikely(err)) {
+ search->result.state = SSDFS_BTREE_SEARCH_FAILURE;
+ search->result.err = err;
+ }
+
+ return err;
+}
+
+/*
+ * ssdfs_calculate_item_offset() - calculate item's offset
+ * @node: pointer on node object
+ * @area_offset: area offset in bytes from the node's beginning
+ * @area_size: area size in bytes
+ * @index: item's index in the node
+ * @item_size: size of item in bytes
+ * @page_index: index of a page in the node [out]
+ * @item_offset: offset in bytes from a page's beginning
+ *
+ * This method tries to calculate item's offset in a page.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-ERANGE - internal error.
+ */
+static
+int ssdfs_calculate_item_offset(struct ssdfs_btree_node *node,
+ u32 area_offset, u32 area_size,
+ int index, size_t item_size,
+ int *page_index,
+ u32 *item_offset)
+{
+#ifdef CONFIG_SSDFS_DEBUG
+ BUG_ON(!node || !page_index || !item_offset);
+ BUG_ON(!rwsem_is_locked(&node->full_lock));
+
+ SSDFS_DBG("node_id %u, area_offset %u, area_size %u, "
+ "item_size %zu, index %d\n",
+ node->node_id, area_offset, area_size,
+ item_size, index);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+ *item_offset = (u32)index * item_size;
+ if (*item_offset >= area_size) {
+ SSDFS_ERR("item_offset %u >= area_size %u\n",
+ *item_offset, area_size);
+ return -ERANGE;
+ }
+
+ *item_offset += area_offset;
+ if (*item_offset >= node->node_size) {
+ SSDFS_ERR("item_offset %u >= node_size %u\n",
+ *item_offset, node->node_size);
+ return -ERANGE;
+ }
+
+ *page_index = *item_offset >> PAGE_SHIFT;
+ if (*page_index >= pagevec_count(&node->content.pvec)) {
+ SSDFS_ERR("invalid page_index: "
+ "index %d, pvec_size %u\n",
+ *page_index,
+ pagevec_count(&node->content.pvec));
+ return -ERANGE;
+ }
+
+ if (*page_index != 0)
+ *item_offset %= PAGE_SIZE;
+
+ return 0;
+}
B-tree node implements change and delete item or range of items in the node: (1) change_item - change item in the b-tre node (2) delete_item - delete item from b-tree node (3) delete_range - delete range of items from b-tree node Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com> CC: Viacheslav Dubeyko <viacheslav.dubeyko@bytedance.com> CC: Luka Perkov <luka.perkov@sartura.hr> CC: Bruno Banelli <bruno.banelli@sartura.hr> --- fs/ssdfs/btree_node.c | 2577 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2577 insertions(+)