HEX
Server: LiteSpeed
System: Linux CentOS-79-64-minimal 3.10.0-1160.119.1.el7.x86_64 #1 SMP Tue Jun 4 14:43:51 UTC 2024 x86_64
User: vishn3436 (5293)
PHP: 8.0.15
Disabled: NONE
Upload Files
File: //usr/src/dattobd-0.11.2/snap_handle.c
// SPDX-License-Identifier: GPL-2.0-only

/*
 * Copyright (C) 2022 Datto Inc.
 */

#include "snap_handle.h"

#include "bio_helper.h"
#include "cow_manager.h"
#include "filesystem.h"
#include "logging.h"
#include "snap_device.h"

// macros for snapshot bio modes of operation.
#define READ_MODE_COW_FILE 1
#define READ_MODE_BASE_DEVICE 2
#define READ_MODE_MIXED 3

#ifndef READ_SYNC
#define READ_SYNC 0
#endif

/**
 * snap_read_bio_get_mode() - Determine how to handle reading this @bio.
 * @dev: The &struct snap_device object pointer.
 * @bio: The &struct bio which describes the I/O.
 * @mode: An output indicating the computed read mode.
 *
 * The BIO is contained in either three cases:
 * * it is entirely in cache, READ_MODE_COW_FILE,
 * * it is entirely on the block device, READ_MODE_BASE_DEVICE,
 * * or it is a mixture of the two location, READ_MODE_MIXED.
 *
 * Return:
 * * 0 - success.
 * * !0 - errno indicating the error.
 */
static int snap_read_bio_get_mode(const struct snap_device *dev,
                                  struct bio *bio, int *mode)
{
        int ret, start_mode = 0;
        bio_iter_t iter;
        bio_iter_bvec_t bvec;
        unsigned int bytes;
        uint64_t block_mapping, curr_byte,
                curr_end_byte = bio_sector(bio) * SECTOR_SIZE;

        bio_for_each_segment (bvec, bio, iter) {
                // reset the number of bytes we have traversed for this bio_vec
                bytes = 0;

                // while we still have data left to be written into the page
                while (bytes < bio_iter_len(bio, iter)) {
                        // find the start and stop byte for our next write
                        curr_byte = curr_end_byte;
                        curr_end_byte += min(
                                COW_BLOCK_SIZE - (curr_byte % COW_BLOCK_SIZE),
                                ((uint64_t)bio_iter_len(bio, iter) - bytes));

                        // check if the mapping exists
                        ret = cow_read_mapping(dev->sd_cow,
                                               curr_byte / COW_BLOCK_SIZE,
                                               &block_mapping);
                        if (ret)
                                goto error;

                        if (!start_mode && block_mapping)
                                start_mode = READ_MODE_COW_FILE;
                        else if (!start_mode && !block_mapping)
                                start_mode = READ_MODE_BASE_DEVICE;
                        else if ((start_mode == READ_MODE_COW_FILE &&
                                  !block_mapping) ||
                                 (start_mode == READ_MODE_BASE_DEVICE &&
                                  block_mapping)) {
                                *mode = READ_MODE_MIXED;
                                return 0;
                        }

                        // increment the number of bytes we have written
                        bytes += curr_end_byte - curr_byte;
                }
        }

        *mode = start_mode;
        return 0;

error:
        LOG_ERROR(ret, "error finding read mode");
        return ret;
}

/**
 * snap_handle_read_bio() - Reads all data contained in the @bio.  The data
 *                          is either all in cache, on the block device or
 *                          a mixture of the two locations.
 * @dev: The &struct snap_device containing snap device state.
 * @bio: The &struct bio which describes the I/O.
 *
 * Return:
 * * 0 - success.
 * * !0 - errno indicating the error.
 */
int snap_handle_read_bio(const struct snap_device *dev, struct bio *bio)
{
        int ret, mode;
        bio_iter_t iter;
        bio_iter_bvec_t bvec;
        void *orig_private;
        bio_end_io_t *orig_end_io;
        char *data;
        sector_t bio_orig_sect, cur_block, cur_sect;
        unsigned int bio_orig_idx, bio_orig_size;
        uint64_t block_mapping, bytes_to_copy, block_off, bvec_off;

        // save the original state of the bio
        orig_private = bio->bi_private;
        orig_end_io = bio->bi_end_io;
        bio_orig_idx = bio_idx(bio);
        bio_orig_size = bio_size(bio);
        bio_orig_sect = bio_sector(bio);

        dattobd_bio_set_dev(bio, dev->sd_base_dev);
        dattobd_set_bio_ops(bio, REQ_OP_READ, READ_SYNC);

        // detect fastpath for bios completely contained within either the cow
        // file or the base device
        ret = snap_read_bio_get_mode(dev, bio, &mode);
        if (ret)
                goto out;

        // submit the bio to the base device and wait for completion
        if (mode != READ_MODE_COW_FILE) {
                ret = dattobd_submit_bio_wait(bio);
                if (ret) {
                        LOG_ERROR(ret,
                                  "error reading from base device for read");
                        goto out;
                }

#ifdef HAVE_BIO_BI_REMAINING
                //#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0)
                atomic_inc(&bio->bi_remaining);
#endif
        }

        // read the bio from the cow file
        if (mode != READ_MODE_BASE_DEVICE) {
                // reset the bio
                bio_idx(bio) = bio_orig_idx;
                bio_size(bio) = bio_orig_size;
                bio_sector(bio) = bio_orig_sect;
                cur_sect = bio_sector(bio);

                // iterate over all the segments and fill the bio. this more
                // complex than writing since we don't have the block aligned
                // guarantee
                bio_for_each_segment (bvec, bio, iter) {
                        // map the page into kernel space
                        data = kmap(bio_iter_page(bio, iter));

                        cur_block = (cur_sect * SECTOR_SIZE) / COW_BLOCK_SIZE;
                        block_off = (cur_sect * SECTOR_SIZE) % COW_BLOCK_SIZE;
                        bvec_off = bio_iter_offset(bio, iter);

                        while (bvec_off < bio_iter_offset(bio, iter) +
                                                  bio_iter_len(bio, iter)) {
                                bytes_to_copy =
                                        min(bio_iter_offset(bio, iter) +
                                                    bio_iter_len(bio, iter) -
                                                    bvec_off,
                                            COW_BLOCK_SIZE - block_off);
                                // check if the mapping exists
                                ret = cow_read_mapping(dev->sd_cow, cur_block,
                                                       &block_mapping);
                                if (ret) {
                                        kunmap(bio_iter_page(bio, iter));
                                        goto out;
                                }

                                // if the mapping exists, read it into the page,
                                // overwriting the live data
                                if (block_mapping) {
                                        ret = cow_read_data(dev->sd_cow,
                                                            data + bvec_off,
                                                            block_mapping,
                                                            block_off,
                                                            bytes_to_copy);
                                        if (ret) {
                                                kunmap(bio_iter_page(bio,
                                                                     iter));
                                                goto out;
                                        }
                                }

                                cur_sect += bytes_to_copy / SECTOR_SIZE;
                                cur_block = (cur_sect * SECTOR_SIZE) /
                                            COW_BLOCK_SIZE;
                                block_off = (cur_sect * SECTOR_SIZE) %
                                            COW_BLOCK_SIZE;
                                bvec_off += bytes_to_copy;
                        }

                        // unmap the page from kernel space
                        kunmap(bio_iter_page(bio, iter));
                }
        }

out:
        if (ret) {
                LOG_ERROR(ret, "error handling read bio");
                bio_idx(bio) = bio_orig_idx;
                bio_size(bio) = bio_orig_size;
                bio_sector(bio) = bio_orig_sect;
        }

        // revert bio's original data
        bio->bi_private = orig_private;
        bio->bi_end_io = orig_end_io;

        return ret;
}

/**
 * snap_handle_write_bio() - This writes all data in the BIO.
 * @dev: The &struct snap_device containing snap device state.
 * @bio: The &struct bio which describes the I/O.
 *
 * It will ultimately either cache/store what's already on the block device
 * before allowing new data to be written or it will just transfer the new
 * data to the block device in the event that the original data has already
 * been stored.
 *
 * Return:
 * * 0 - successful.
 * * !0 - errno indicating the error.
 */
int snap_handle_write_bio(const struct snap_device *dev, struct bio *bio)
{
        int ret;
        bio_iter_t iter;
        bio_iter_bvec_t bvec;
        char *data;
        sector_t start_block, end_block = SECTOR_TO_BLOCK(bio_sector(bio));

        // iterate through the bio and handle each segment (which is guaranteed
        // to be block aligned)
        bio_for_each_segment (bvec, bio, iter) {
                // find the start and end block
                start_block = end_block;
                end_block = start_block +
                            (bio_iter_len(bio, iter) / COW_BLOCK_SIZE);

                // map the page into kernel space
                data = kmap(bio_iter_page(bio, iter));

                // loop through the blocks in the page
                for (; start_block < end_block; start_block++) {
                        // pass the block to the cow manager to be handled
                        ret = cow_write_current(dev->sd_cow, start_block, data);
                        if (ret) {
                                kunmap(bio_iter_page(bio, iter));
                                goto error;
                        }
                }

                // unmap the page
                kunmap(bio_iter_page(bio, iter));
        }

        return 0;

error:
        LOG_ERROR(ret, "error handling write bio");
        return ret;
}

/**
 * inc_handle_sset() - Adds a placeholder to the mapping state maintained for
 *                     each COW block spanning the range of sectors.
 * @dev: The &struct snap_device containing snap device state.
 * @sset: The &struct sector_set which describes the I/O spaning sectors.
 *
 * Return:
 * * 0 - successful
 * * !0 - errno indicating the error
 */
int inc_handle_sset(const struct snap_device *dev, struct sector_set *sset)
{
        int ret;
        sector_t start_block = SECTOR_TO_BLOCK(sset->sect);
        sector_t end_block = NUM_SEGMENTS(sset->sect + sset->len,
                                          COW_BLOCK_LOG_SIZE - SECTOR_SHIFT);

        for (; start_block < end_block; start_block++) {
                ret = cow_write_filler_mapping(dev->sd_cow, start_block);
                if (ret)
                        goto error;
        }

        return 0;

error:
        LOG_ERROR(ret, "error handling sset");
        return ret;
}