diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..9d84d7ec --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,77 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/2.0/configuration-reference +version: 2.1 + +shared: &shared + steps: + - checkout + - run: + name: Prepare environment + command: | + echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections + apt-get update + apt-get dist-upgrade -y + - run: + name: Install dependencies + command: | + apt-get install -y g++ cmake uuid-dev libboost-program-options-dev libboost-filesystem-dev libssl-dev debhelper + - run: + name: Build dev, tools and tests packages + working_directory: pkg/deb + command: ./build-blksnap.sh + - run: + name: Save generate packages as artifacts + working_directory: .. + command: | + mkdir /tmp/artifacts + mv *.deb /tmp/artifacts + - store_artifacts: + path: /tmp/artifacts + +jobs: + debian10: + <<: *shared + docker: + - image: library/debian:buster + debian11: + <<: *shared + docker: + - image: library/debian:bullseye + debian12: + <<: *shared + docker: + - image: library/debian:bookworm + ubuntu1404: + <<: *shared + docker: + - image: library/ubuntu:trusty + ubuntu1604: + <<: *shared + docker: + - image: library/ubuntu:xenial + ubuntu1804: + <<: *shared + docker: + - image: library/ubuntu:bionic + ubuntu2004: + <<: *shared + docker: + - image: library/ubuntu:focal + ubuntu2204: + <<: *shared + docker: + - image: library/ubuntu:jammy + ubuntu2310: + <<: *shared + docker: + - image: library/ubuntu:mantic + +workflows: + build-deb: + jobs: + - debian10 + - debian11 + - debian12 + - ubuntu2004 + - ubuntu2204 + - ubuntu2310 diff --git a/.editorconfig b/.editorconfig index 65208e13..65cf9441 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,11 +10,5 @@ end_of_line = lf charset = utf-8 trim_trailing_whitespace = true -[module/**.{c,h}] -indent_style = tab -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - [Makefile] -indent_style = tab \ No newline at end of file +indent_style = tab diff --git a/.github/workflows/build-other-archs.yml b/.github/workflows/build-other-archs.yml new file mode 100644 index 00000000..d82ced6d --- /dev/null +++ b/.github/workflows/build-other-archs.yml @@ -0,0 +1,65 @@ +name: Test build on other archs + +on: + push: + branches: + - "*" + paths: + - "include/**" + - "lib/**" + - "tests/**" + - "tools/**" + pull_request: + branches: + - "*" + paths: + - "include/**" + - "lib/**" + - "tests/**" + - "tools/**" + workflow_dispatch: + +jobs: + build_job: + # The host should always be linux + runs-on: ubuntu-22.04 + name: ${{ matrix.arch }} + + strategy: + fail-fast: false + matrix: + include: + - arch: aarch64 + - arch: ppc64le + - arch: s390x + - arch: armv7 + steps: + - uses: actions/checkout@v4 + - uses: uraimo/run-on-arch-action@v2 + name: Build + id: build + with: + arch: ${{ matrix.arch }} + distro: bullseye + + # Not required, but speeds up builds + githubToken: ${{ github.token }} + + # The shell to run commands with in the container + shell: /bin/sh + + # Install some dependencies in the container. This speeds up builds if + # you are also using githubToken. Any dependencies installed here will + # be part of the container image that gets cached, so subsequent + # builds don't have to re-install them. The image layer is cached + # publicly in your project's package repository, so it is vital that + # no secrets are present in the container state or logs. + install: | + apt-get update -q -y + apt-get install -q -y g++ cmake uuid-dev libboost-program-options-dev libboost-filesystem-dev libssl-dev + + # Build blksnap-dev, blksnap-tools and blksnap-tests + run: | + cmake . + make + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..61b093ef --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,92 @@ +name: Build + +on: + push: + branches: + - "*" + paths-ignore: + - "README.md" + - "doc/**" + pull_request: + branches: + - "*" + paths-ignore: + - "README.md" + - "doc/**" + workflow_dispatch: + +jobs: + amd64: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + name: [Ubuntu-20, Ubuntu-22, Debian-10, Debian-11, Debian-12, Debian-Testing, Debian-Experimental] + cpp_compiler: [g++] + include: + - name: Ubuntu-20 + # Uses gcc 9.3.0, clang 10.0.0, cmake 3.16.3 + image: "ubuntu:20.04" + ubuntu: 20 + - name: Ubuntu-22 + # Uses gcc 12.2.0, clang 15.0.7, cmake 3.24.2 + image: "ubuntu:22.04" + ubuntu: 22 + - name: Debian-10 + # Uses gcc 8.3.0, clang 7.0.1, cmake 3.13.4 + image: "debian:buster" + - name: Debian-11 + # Uses gcc 10.2.1, clang 11.0.1, cmake 3.18.4 + image: "debian:bullseye" + - name: Debian-11 + image: "debian:bullseye" + c_compiler: clang + cpp_compiler: clang++ + - name: Debian-12 + # Uses gcc 12.2.0, clang 15.0.6, cmake 3.25.1 + image: "debian:bookworm" + - name: Debian-12 + image: "debian:bookworm" + c_compiler: clang + cpp_compiler: clang++ + - name: Debian-Testing + image: "debian:testing" + - name: Debian-Testing + image: "debian:testing" + c_compiler: clang + cpp_compiler: clang++ + - name: Debian-Experimental + image: "debian:experimental" + - name: Debian-Experimental + image: "debian:experimental" + c_compiler: clang + cpp_compiler: clang++ + container: + image: ${{ matrix.image }} + env: + LANG: en_US.UTF-8 + BUILD_TYPE: ${{ matrix.build_type }} + CC: ${{ matrix.c_compiler }} + CXX: ${{ matrix.cpp_compiler }} + WITH_PROJ: ON + APT_LISTCHANGES_FRONTEND: none + DEBIAN_FRONTEND: noninteractive + steps: + - name: Install packages required + shell: bash + run: | + apt-get update -qq + apt-get install -yq \ + clang \ + cmake \ + g++ \ + uuid-dev \ + libboost-program-options-dev \ + libboost-filesystem-dev \ + libssl-dev + - uses: actions/checkout@v4 + - name: Build blksnap-dev, blksnap-tools and blksnap-tests + working-directory: . + run: | + cmake . + make diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index f23f3f2b..cc163505 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -2,7 +2,7 @@ name: codespell on: [pull_request] jobs: - check: + codespell: name: codespell runs-on: ubuntu-latest steps: diff --git a/CMakeLists.txt b/CMakeLists.txt index 87b6561f..7cd9d114 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,7 @@ add_subdirectory(${CMAKE_SOURCE_DIR}/tests/cpp) if(EXISTS ${CMAKE_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in) configure_file(${CMAKE_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in - ${CMAKE_SOURCE_DIR}/cmake/cmake_uninstall.cmake @ONLY + ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake @ONLY ) - add_custom_target(uninstall "${CMAKE_COMMAND}" -P "${CMAKE_SOURCE_DIR}/cmake/cmake_uninstall.cmake") -endif() \ No newline at end of file + add_custom_target(uninstall "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") +endif() diff --git a/README.md b/README.md index a6f54fd3..377f6754 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,9 @@ -| :warning: Important note | -|:---------------------------| -| Master branch was recently updated only about readme and patches posted for upstream kernel | -| Latest work for upstream is in [stable-2.0](https://github.com/veeam/blksnap/tree/stable-v2.0) branch, for more details check [Upstream kernel integration](https://github.com/veeam/blksnap/blob/master/doc/README-upstream-kernel.md#work-in-progress-and-news) | -| For older blksnap version based on external module (actually used in production) see these branches: [VAL-6.1](https://github.com/veeam/blksnap/tree/VAL-6.1), [VAL-6.0](https://github.com/veeam/blksnap/tree/VAL-6.0), [stable-1.0](https://github.com/veeam/blksnap/tree/stable-v1.0) | -| :information_source: To Veeam agent for linux users: | -| If you need only kernel module updated with latest kernel versions support and latest fixes for it [build kernel module](#how-to-build) from [VAL-6.0](https://github.com/veeam/blksnap/tree/VAL-6.0) or [VAL-6.1](https://github.com/veeam/blksnap/tree/VAL-6.1) based on your Veeam agent for linux version | - -# BLKSNAP - Block Devices Snapshots Module +# BLKSNAP - Block Devices Snapshots * [Extended description and features](doc/blksnap.md) * [Repository structure](#repository-structure) * [Licensing](#licensing) -* [Kernel module](#kernel-module) -* [Upstream kernel integration](https://github.com/veeam/blksnap/blob/master/doc/README-upstream-kernel.md) +* [Upstream kernel integration](#kernel-integration) * [Tools](#tools) * [Library](#library) * [Tests](#tests) @@ -28,8 +19,6 @@ * doc/ - Documentation * include/ - Libraries public headers * lib/ - Libraries sources -* module/ - Sources of kernel module -* patches/ - Patches for the upstream linux kernel * pkg/ - Scripts for building deb and rpm packages * tests/ - Test scripts and tests source code * tools/ - Source files of tools for working with blksnap @@ -42,48 +31,9 @@ Copyright (C) 2022 Veeam Software Group GmbH This project use [SPDX License Identifier](https://spdx.dev/ids/) in source files header. - -## Kernel module -This kernel module implements snapshot and changed block tracking functionality. -The module is developed with the condition of simply adding it to the upstream. -Therefore, the module is divided into two parts: bdevfilter and blksnap. -bdevfilter provides the ability to intercept I/O units (bio). The main logic -is concentrated in blksnap. The upstream variant does not contain a bdevfilter, -but accesses the kernel to attach and detach the block device filter. - -Relating the work in progress for integration in upstream kernel see the specific [README](https://github.com/veeam/blksnap/blob/master/doc/README-upstream-kernel.md) - -### How to build -Installing the necessary deb packages. -``` bash -sudo apt install gcc linux-headers-$(uname -r) -``` -Or installing the necessary rpm packages. -``` bash -sudo yum install gcc kernel-devel -``` -``` bash -cd ./module -mk.sh build -``` -In directory current directory you can found bdevfilter.ko and blksnap.ko. - -### How to install -``` bash -cd ./module -mk.sh install-flt -mk.sh install -``` -### How to create deb package -``` bash -sudo apt install debhelper dkms -# on debian >=12 and ubuntu >= 23.04 is needed dh-dkms, not installed anymore as dkms dep. -sudo apt install dh-dkms -cd ./pkg/deb -./build-blksnap-dkms.sh ${VERSION} -``` -### How to create rpm package -There are several variants, look in the ./pkg/rpm directory. +## Kernel integration +Relating the work in progress for integration in upstream kernel see the +specific [README](https://github.com/veeam/blksnap/blob/master/doc/README-upstream-kernel.md) ## Tools The blksnap tools allows you to manage the module from the command line. @@ -152,7 +102,6 @@ cd ./pkg/deb ``` ## Compatibility notes -- blksnap kernel module support kernel versions >= 5.10, support only X86 archs, blksnap for upstream instead can support any arch (other archs need to be tested) +- blksnap kernel module for upstream can support any arch (other archs beyond X86 archs needs more testing) - all supported debian and ubuntu supported versions are supported but with some notes: - - not all have debian/ubuntu versions have official packages of kernel >= 5.10, so an unofficial or custom ones more updated are needed, with blksnap-dkms should be still possible easy/fast build/install blksnap module on them (is also possible build/install it manually without dkms) - debian 8 and ubuntu 14.04 needs to install cmake 3 from backports to build diff --git a/doc/bdev_filter.md b/doc/bdev_filter.md deleted file mode 100644 index dd49a80e..00000000 --- a/doc/bdev_filter.md +++ /dev/null @@ -1,77 +0,0 @@ -# Block device filters - -## Introduction -A block device filter is a kernel module that handles requests to a block device and performs preprocessing of these requests. Block device filters allow to expand the capabilities of the block layer. The blksnap module is the first filter module that is offered for the Linux kernel upstream. - -The idea of intercepting requests to block devices is not new. Even in the 2.6 kernel, there was the possibility of handling requests by substituting the make_request_fn() function that belonged to the request_queue structure. There are several modules that have used this feature. But none of them were in the kernel tree. Therefore, in the process of refactoring, this possibility was eliminated. - -Support for block device filters in the kernel will allow to return the ability to handle requests, and allow to do it more securely. Moreover, the possibility of simultaneous operation of several filters by the number of available altitudes is provided. The number of available altitudes is limited by the number of filters in the kernel tree. This restriction should encourage the provision of new block device filters to the kernel. - -## How it works -The block device filter is added at the top of the block layer. -``` - +-------------+ +-------------+ - | File System | | Direct I/O | - +-------------+ +-------------+ - || || - \/ \/ - +-----------------------------+ - | | Block Device Filter| | - | +--------------------+ | - | Block Layer | - +-----------------------------+ - || || ... || - \/ \/ \/ - +------+ +------+ - | Disk | | Disk | - +------+ +------+ -``` -Requests sent to the block layer are handled by filters and processed. - -The filter can pass the request for further execution, skip processing the request, or redirect the request to another device. - -In some cases, the filter cannot immediately process the request. In such a case it requires repeated processing. - -For more information about the filter processing cycle, see the "Filtering algorithm" section. - -Theoretically, several filters can be added for one block device at the same time. Filters can be compatible with each other. In this case, they can be placed on their altitudes and process requests in turn. But filters may be incompatible because of their purpose. In this case, they should use one altitude. This will protect the system from an inoperable combination of filters. Currently, only one altitude is reserved for the blksnap module. - -## Filtering algorithm -In the system, the filter is a structure with a reference counter and a set of callback functions. An array of pointers to filters by the number of reserved altitudes is added to the block_device structure. The array is protected from simultaneous access by a spin lock. - -The submit_bio_noaсct() function has added a call to the filter_bio() filter processing function that implements the filtering algorithm. - -For a block device, all altitudes are checked. If there was a filter on the altitude (not NULL in the cell), then the corresponding callback function is called. Depending on the result of the callback function, the following is performed: - - Go to the next altitude (pass) - - Completion of request processing (skip) - - Re-processing filters from the first altitude (redirect) - - Re-processing the request (repeat) - -In order to exclude a recursive call to the submit_bio() function, a pointer to the list of I/O blocks current->bio_list is initialized before calling the callback function. If new I/O requests are creaded when processing a request, they are added to this list. This mechanism allows to protect the stack from overflow. - -After the I/O request is processed by the filter, new requests are extracted from current->bio_list and executed. Therefore, synchronous execution of I/O requests in the filter is not possible. - -However, if it is required to wait for the completion of new requests from the filter, the callback returns the "repeat" code. In this case, after processing requests from current->bio_list, the filter handler will call the filter callback function again so that the filter can take a "quiet nap" while waiting for I/O. - -If the filter redirects the processing of the I/O request to another block device by changing the pointer to the block device in bio, then the filter processing must be repeated from the beginning, but for another block device. In this case, the filter callback function should return the "redirect" code. - -If the filter decides that the original I/O request does not need to be executed, then the return code of the callback function should be "skip". - -To prevent a new I/O request from the filter from being processed by filters, the BIO_FILTERED flag can be set. Such bios are immediately skipped by the filter handler. - -## Algorithm for freeing filter resources -The block device can be extracted. In this case, the block_device structure is released. The filter in this case should also be released. To properly release the filter, it has a reference counter and a callback to release its resources. If, at the time of deleting the block device and disabling the filter, the filter is processing a request, then, thanks to the reference counter, the release will be performed only when the counter is reduced to zero. - -## How to use it -To attach its filter, the module must initiate the bdev_filter structure using the bdev_filter_init() function and call the bdev_filter_attach() function. It would be a good practice to freeze the file system on the block device using the freeze_bdev() function, but it is not obligatory. Thanks to the spin block, the new filter will be added safely. - -Once the filter is attached, during processing of I/O reruests, the submit_bio_cb() callback function from the bdev_filter_operations structure will be called. - -Detaching the filter can be initiated by calling the bdev_filter_detach() function or automatically when deleting a block device. In this case, the filter will be removed from the block_device structure and the detach_cb() callback function will be called. When executing detach_cb(), the process cannot be put into a waiting state. If the filter needs to wait for the completion of any processes, it is recommended to schedule the execution in the worker. It is important to remember that after completing the execution of the bdev_filter_detach() function, the filter will no longer receive I/O requests for processing. - -The kernel module does not need to store a pointer to the bdev_filter structure. It is already stored in the block_device structure. To get a filter, just call bdev_filter_get_by_altitude(). At the same time, the reference count will be increased in order to use the structure safely. After use, the reference count must be reduced using bdev_filter_put(). - -## What's next -In the current implementation, only the submit_bio_noacct() and dev_free_inode() calls are handled. In the future, I would like to add a handle for the bdev_read_page() and dev_write_page() functions. This will allow to work correctly with disks that have the rw_page() callback function in the block_device_operations structure. - -In the future, the number of altitudes will increase. When this number reaches 8, a simple array of pointers to filters can be replaced with a more complex structure, such as xarray. diff --git a/doc/bdev_filter_ru.md b/doc/bdev_filter_ru.md deleted file mode 100644 index 02f3f7a3..00000000 --- a/doc/bdev_filter_ru.md +++ /dev/null @@ -1,77 +0,0 @@ -# Фильтры блочных устройств - -## Введение -Фильтр блочного устройства — это модуль ядра, который перехватывает запросы к блочному устройству и выполняет предварительную обработку этих запросов. Фильтры блочных устройств позволяют расширить возможности блочного уровня. Модуль blksnap является первым фильтр-модулем, который предлагается для восходящего потока ядра Linux. - -Идея перехвата запросов к блочным устройствам не новая. Ещё в ядре 2.6 существовала возможность перехвата запросов с помощью подмены функции make_request_fn, которая пренадлежала структуре request_queue. Существует несколько модулей, которые использовали эту возможность. Но ни один из них не был в дереве ядра. Потому в процессе рефакторинга эта возможность была устранена. - -Поддержка ядром фильтров блочных устройств позволит вернуть возможность перехвата запросов, а также позволит делать это более безопасно. Более того, предуcмотрена возможность одновременной работы нескольких фильтров по количеству доступных альтитуд. Количество доступных альтитуд ограничено количеством фильтров в дереве ядра. Это ограничение должно стимулировать предоставление новых фильтров блочных устройств в ядро. - -## Как это работает -Фильтр блочного устройства добавляется в верхней части блочного слоя. -``` - +-------------+ +-------------+ - | File System | | Direct I/O | - +-------------+ +-------------+ - || || - \/ \/ - +-----------------------------+ - | | Block Device Filter| | - | +--------------------+ | - | Block Layer | - +-----------------------------+ - || || ... || - \/ \/ \/ - +------+ +------+ - | Disk | | Disk | - +------+ +------+ -``` -Направляемые блочному слою запросы перехватываются фильтрами и обрабатываются. - -Фильтр может пропустить запрос на дальнейшее выполнение, завершить обработку запроса или перенаправить запрос на другое устройство. - -В некоторых случаях фильтр не может сразу обработать запрос, в этом случае он требует повторить обработку. - -Подробнее о цикле обработки фильтров в разделе "Алгоритм фильтрации". - -Теоретически для кажого блочного устройства может быть добавлено одновременно несколько фильтров. Фильтры могут быть совместимы друг с другом. В этом случае они могут разместиться на своих альтитудах и обрабатывать запросы поочерёдно. Но фильтры могут быть несовместимы из-за своего назначения. В этом случае они должны использовать одну альтитуду. Это защитит систему от неработоспособной комбинации фильтров. На текущий момент зарезервирована только одна альтитуда для модуля blksnap. - -## Алгоритм фильтрации -В системе фильтр представляет собой структуру со счётчиком ссылок и набором функций обратного вызова. В структуру block_device добавляется массив указателей на фильтры по количеству зарезервированных альтитуд. Массив защищается от одновременного доступа спин-блокировкой. - -В функции submit_bio_noacct() добавлен вызов функции обработки фильтров filter_bio(), в которой реализован алгоритм фильтрации. - -Для блочного устройства проверяются все альтитуды. Если на альтитуде был фильтр (в ячейке не NULL), то вызывается соответствующая функция обратного вызова. В зависимости от результата выполнения функции обратного вызова выполняется: - - переход к следующей альтитуде (pass) - - завершение обработки запроса (skip) - - повторная обработка фильтров с первой альтитуды (redirect) - - повторная обработка запроса (repeat) - -Для того чтобы исключить рекурсивный вызов функции submit_bio(), перед вызовом функции обратного вызова инициализируется указатель на список блоков ввода/вывода current->bio_list. Если при обработке запроса инициируются запросы ввода/вывода, то они добавляются в этот список. Этот механизм позволяет защитить стек от переполнения. - -После обработки запроса ввода/вывода фильтром новые запросы извлекаются из current->bio_list и выполняются. Поэтому синхронное выполнение запросов ввода/вывода в фильтре невозможно. - -Тем не менее, если требуется дождаться завершения выполнения новых запросов от фильтра, то обратный вызов завершается с кодом repeat. В этом случае после обработки запросов из current->bio_list обработчик фильтра снова вызовет функцию обратного вызова фильтра, чтобы фильтр мог спокойно "вздремнуть" в ожидании выполнения ввода/вывода. - -Если фильтр переводит обработку запроса ввода/вывода на другое блочное устройство, изменяя указатель на блочное устройство в bio, то обработку фильтров нужно повторить с начала, но уже для другого блочного устройства. В этом случае функция обратного вызова фильтра должна завершаться с кодом redirect. - -Если фильтр решает, что оригинальный запрос ввода/вывода выполнять не требуется, то код возврата функции обратного вызова должен быть skip. - -Чтобы новый запрос ввода/вывода от фильтра не попадал на обработку фильтрами, запрос может быть помечен флагом BIO_FILTERED. Такие запросы сразу пропускаются обработчиком фильтров. - -## Алгоритм освобождения ресурсов фильтра -Блочное устройство может быть извлечено. В этом случае структура block_device освобождается. Фильтр в этом случае тоже должен быть освобождён. Чтобы корректно освободить фильтр, он имеет счётчик ссылок и обратный вызов для освобождения его ресурсов. Если в момент удаления блочного устройства и отключения фильтра он выполняет обработку запроса, то благодаря счётчику ссылок освобождение выполниться только тогда, когда счётчик уменьшится до нуля. - -## Как этим пользоваться -Чтобы подключить свой фильтр, модуль должен инициализировать структуру bdev_filter с помощью функции bdev_filter_init() и вызвать функцию bdev_filter_attach(). Хорошей практикой будет заморозить файловую систему на блочном устройстве с помощью freeze_bdev(), но это не обязательно. Благодаря спин-блокировке новый фильтр будет добавлен безопасно. - -Сразу после подключения при обработке запросов ввода/вывода будет вызываться обратный вызов submit_bio_cb() из структуры bdev_filter_operations. - -Отключение фильтра может быть инициировано вызовом функции bdev_filter_detach() или автоматически при удалении блочного устройства. При этом фильтр будет удалён из структуры block_device, и будет вызвана функция обратного вызова detach_cb(). При выполнении detach_cb() нельзя переводить процесс в состояние ожидания. Если фильтру требуется дождаться завершения выполнения каких-либо процессов, рекомендуется запланировать выполнение рабочего процесса queue_work(). Важно помнить, что после завершения выполнения функции bdev_filter_detach() фильтр уже не будет получать запросы ввода/вывода на обработку. - -Модулю ядра не нужно хранить указатель на структуру bdev_filter. Она уже хранится в структуре block_device. Чтобы получить фильтр, достаточно вызвать bdev_filter_get_by_altitude(). При этом счётчик ссылок будет увеличен, чтобы безопасно использовать структуру. После использования счётчик ссылок необходимо уменьшить с помощью bdev_filter_put(). - -## Что дальше -В текущей реализации перехватываются только вызовы submit_bio_noacct() и bdev_free_inode(). В будущем хотелось бы добавить перехват для функций bdev_read_page() и bdev_write_page(). Это позволит корректно работать с дисками, которые имеют функцию обратного вызова rw_page() в структуре block_device_operations. - -В будущем количество альтитуд будет увеличиваться. Когда это количество доберётся до 8, простой массив указателей на фильтры можно будет заменить на более сложную структуру, например на xarray. diff --git a/doc/blkfilter.rst b/doc/blkfilter.rst deleted file mode 100644 index 2b1cd763..00000000 --- a/doc/blkfilter.rst +++ /dev/null @@ -1,41 +0,0 @@ -.. SPDX-License-Identifier: GPL-2.0 - -================================ -Block Device Filtering Mechanism -================================ - -The block device filtering mechanism is an API that allows to attach block device filters. -Block device filters allow perform additional processing for I/O units. - -Introduction -============ - -The idea of handling I/O units on block devices is not new. -Back in the 2.6 kernel, there was an undocumented possibility of handling I/O units by substituting the make_request_fn() function, which belonged to the request_queue structure. -But no kernel module used this feature, and it was eliminated in the 5.10 kernel. - -The block device filtering mechanism returns the ability to handle I/O units. -It is possible to safely attach filter to a block device "on the fly" without changing the structure of block devices. - -It supports attaching one filter to one block device, because there is only one filter implementation in the kernel. -See Documentation/block/blksnap.rst. - -Design -====== - -The block device filtering mechanism provides functions for attaching and detaching the filter. -The filter is a structure with a reference counter and callback functions. - -The submit_bio_cb() callback function is called for each I/O unit for a block device, providing I/O unit filtering. -Depending on the result of filtering the I/O unit, it can either be passed for subsequent processing by the block layer, or skipped. - -The reference counter allows to control the filter lifetime. -When the reference count is reduced to zero, the release_cb() callback function is called to release the filter. -This allows the filter to be released when the block device is disconnected. - -Interface description -===================== -.. kernel-doc:: include/linux/blkdev.h - :functions: bdev_filter_operations bdev_filter bdev_filter_init bdev_filter_get bdev_filter_put -.. kernel-doc:: block/bdev.c - :functions: bdev_filter_attach bdev_filter_detach diff --git a/doc/blkfilter_ru.rst b/doc/blkfilter_ru.rst deleted file mode 100644 index 37fa6184..00000000 --- a/doc/blkfilter_ru.rst +++ /dev/null @@ -1,31 +0,0 @@ -.. SPDX-License-Identifier: GPL-2.0 - -===================================== -Механизм фильтрации блочных устройств -===================================== - -Механизм фильтрации блочных устройств — это API, которое позволяет подключать фильтры блочных устройств. -Фильтры блочных устройств позволяют выполнить дополнительную обработку запросов ввода/вывода. - -Introduction -============ - -Идея перехвата запросов к блочным устройствам не новая. -Ещё в ядре 2.6 существовала недокументированная возможность перехвата запросов с помощью подмены функции make_request_fn, которая принадлежала структуре request_queue. -Ни один модуль ядра не использовал эту возможность, и в ядре 5.10 она была устранена. - -Механизм фильтрации блочных устройств возвращает возможность перехвата запросов ввода/вывода. -Предусматривается возможность безопасно подключить один фильтр к блочному устройству "на лету" без изменения структуры блочных устройств. - -Design -====== - -Механизм фильтрации блочных устройств предоставляет функции для подключения и отключения фильтра. -Фильтр представляет собой структуру со счётчиком ссылок и функциями для обратного вызова. - -Функция обратного вызова submit_bio_cb() вызывается для каждого запроса ввода/вывода для блочного устройства, обеспечивая фильтрацию запросов. -В зависимости от результата обработки запроса ввода/вывода фильтром запрос может быть либо пропущен для последующей обработки блочным слоем, либо нет. - -Счётчик ссылок позволяет контролировать время жизни фильтра. -Когда счётчик ссылок уменьшается до нуля, вызывается функция обратного вызова detach_cb() для освобождения фильтра. -Это позволяет освобождать фильтр при отключении блочного устройства. diff --git a/doc/blksnap.8 b/doc/blksnap.8 new file mode 100644 index 00000000..25928062 --- /dev/null +++ b/doc/blksnap.8 @@ -0,0 +1,184 @@ +.TH BLKSNAP 8 "9 November 2023" + +.SH NAME +blksnap \- Snapshots of block devices. + +.SH SYNOPSIS + +.I COMMAND +{ +.I ARGUMENTS +| +.B help +} + +.SH DESCRIPTION +.PP +The tool for creating snapshots of block devices using the blksnap kernel module. + +.SH COMMANDS, ARGUMENTS + +.SS HELP +Print usage. +.TP +.B blksnap { help | \-\-help | \-h } +Prints a list of all available commands and information about them. +.TP +The \-\-help parameter is available for each command. Enter 'blksnap \fICOMMAND\fR --help' and get help about the parameters of this command. + +.SS ATTACH +Attach blksnap tracker to block device. +.TP +.B blksnap attach \-\-device \fIDEVICE\fR +.TP +.BR \-d ", " \-\-device " " \fIDEVICE\fR +Block device name. +.TP +The blksnap block device filter is attached and the change tracker tables are initiated. + +.SS CBTINFO +Get change tracker information. +.TP +.B blksnap cbtinfo \-\-device \fIDEVICE\fR +.TP +.BR \-d ", " \-\-device " " \fIDEVICE\fR +Block device name. +.TP +Prints the block size and their count in the table, the generation ID and the current change number. The change number increases every time a snapshot is taken. It's a byte, and its value cannot exceed 255. When the change number reaches its maximum value, the change table is reset and a new generation ID is generated. + +.SS DETACH +Detach blksnap tracker from block device. +.TP +.B blksnap detach --device \fIDEVICE\fR +.TP +.BR \-d ", " \-\-device " " \fIDEVICE\fR +Block device name. +.TP +The blksnap block device filter is detached, and the change tracker tables are being released. + +.SS MARKDIRTYBLOCK +Mark blocks as changed in change tracking map. +.TP +.B blksnap markdirtyblock {--file \fIFILE\fR | --device \fIDEVICE\fR --range \fIRANGE\fR +.TP +.BR \-f ", " \-\-file " " \fIFILE\fR +File name. Specifies that the contents of this file should be considered changed for the next snapshot. The tool recognizes the block device on which this file is located and the ranges of sectors that this file occupies. +.TP +.BR \-d ", " \-\-device " " \fIDEVICE\fR +The name of the block device for which the change tracker table is being modified. +.TP +.BR \-r ", " \-\-range " " \fIRANGE\fR +Sectors range in format 'sector:count' is multitoken argument. +.TP +The command allows to mark the regions of the block device that must be read in the next incremental or differential backup. + +.SS READCBTMAP +Read change tracking map. +.TP +.B blksnap readcbtmap --device \fIDEVICE\fR --file \fIFILE\fR +.TP +.BR \-d ", " \-\-device " " \fIDEVICE\fR +Block device name. +.TP +.BR \-f ", " \-\-file " " \fIFILE\fR +The name of the file to which the change tracker table will be written. +.TP +The table is an array, each byte of which is the change number of each block. A block is considered to have changed since the previous backup if it contains a number greater than the number of changes in the previous backup. + +.SS SNAPSHOT_ADD +Add device to snapshot. +.TP +.B blksnap snapshot_add \-\-id \fIUUID\fR \-\-device \fIDEVICE\fR +.TP +.BR \-i ", " \-\-id " " \fIUUID\fR +Snapshot unique identifier. +.TP +.BR \-d ", " \-\-device " " \fIDEVICE\fR +Block device name. +.TP +The command can be called after the \fISNAPSHOT_CREATE\fR command. + +.SS SNAPSHOT_COLLECT +Get collection of snapshots. +.TP +.B blksnap snapshot_collect +.TP +Prints the UUIDs of all available snapshots. + +.SS SNAPSHOT_CREATE +Create snapshot. +.TP +.B blksnap snapshot_create --device \fIDEVICE\fR --file \fIFILE\fR --limit \fIBYTES_COUNT\fR +.TP +.BR \-d ", " \-\-device " " \fIDEVICE\fR +Block device name. It's a multitoken optional argument. Allows to set a list of block devices for which a snapshot will be created. If no block device is specified, then should be used \fISNAPSHOT_ADD\fR command. +.TP +.BR \-f ", " \-\-file " " \fIFILE\fR +The name of file or directory. The file name defines the file that will be used as a difference storage for snapshot. If a directory name is specified, an unnamed file with the O_TMPFILE flag is created in this directory. If an unnamed file is used, the kernel module releases it when the snapshot is destroyed. +.TP +.BR \-l ", " \-\-limit " " \fIBYTES_COUNT\fR +The allowable limit for the size of the difference storage file. The suffixes M, K and G is allowed. + +.SS SNAPSHOT_DESTROY +Release snapshot. +.TP +.B blksnap snapshot_destroy --id \fIUUID\fR +.TP +.BR \-i ", " \-\-id " " \fIUUID\fR +Snapshot unique identifier. + +.SS SNAPSHOT_INFO +Get information about block device snapshot image. +.TP +.B blksnap snapshot_info --device \fIDEVICE\fR --field \fIFIELD_NAME\fR +.TP +.BR \-d ", " \-\-device " " \fIDEVICE\fR +Block device name. +.TP +.BR \-f ", " --field " " \fIFIELD_NAME\fR +Optional argument. Allow print only selected field 'image' or 'error_code'. + +.SS SNAPSHOT_TAKE +Take snapshot. +.TP +.B blksnap snapshot_take --id \fIUUID\fR +.TP +.BR \-i ", " \-\-id " " \fIUUID\fR +Snapshot unique identifier. +.TP +Before taking a snapshot, it must be created using the \fISNAPSHOT_CREATE\fR command and the necessary block devices are added to it using the \fISNAPSHOT_ADD\fR command. + +.SS SNAPSHOT_WAITEVENT +Wait and read event from snapshot. +.TP +.B blksnap snapshot_waitevent --id \fIUUID\fR --timeout \fIMILLISECONDS\fR +.TP +.BR \-i ", " \-\-id " " \fIUUID\fR +Snapshot unique identifier. +.TP +.BR -t ", " \-\-timeout " " \fIMILLISECONDS\fR +The allowed waiting time for the event in milliseconds. +.TP +Allow wait and read only one event. + +.SS SNAPSHOT_WATCHER +Start snapshot watcher service. +.TP +.B blksnap snapshot_watcher --id \fIUUID\fR +.TP +.BR \-i ", " \-\-id " " \fIUUID\fR +Snapshot unique identifier. +.TP +Start the process that is waiting for the events from the snapshot and prints snapshots state when the it's damaged or destroyed. + +.SS VERSION +Show module version. +.B blksnap version + +.SH REPORTING BUGS +Report bugs to sergei.shtepa@veeam.com + +.SH COPYRIGHT +Copyright (C) 2022 Veeam Software Group GmbH + +GPL-2.0+ diff --git a/doc/blksnap.md b/doc/blksnap.md index c31db448..e69726df 100644 --- a/doc/blksnap.md +++ b/doc/blksnap.md @@ -1,8 +1,8 @@ -# BLKSNAP - module for snapshots of block devices +# Snapshots of block devices using the blksnap kernel module * [Introduction](#introduction) -* [How it works](#how-it-works) * [Features](#features) +* [How it works](#how-it-works) - [Change tracking](#change-tracking) - [Copy-on-write](#copy-on-write) - [Difference storage](#difference-storage) @@ -10,100 +10,169 @@ - [Using ioctl](#using-ioctl) - [Static C++ library](#static-c-library) - [Blksnap console tool](#blksnap-console-tool) -* [What's next](#whats-next) + - [Regression tests](#regression-tests) +* [License](#license) ## Introduction -There is no novelty in the idea of snapshots for block devices. Even the Linux kernel already has mechanisms for creating snapshots of block devices. First of all, this is Device Mapper which allows you to create persistent and non-persistent snapshots of block devices. There are file systems that support snapshots, such as BTRFS. There are others, but they all have their own features. These features do not allow them to be used as a universal tool for creating backups. That is why different backup vendors offer their own kernel modules for creating snapshots. Unfortunately, none of these modules meet the upstream requirements of the Linux kernel. -The blksnap module was created precisely for the purpose of offering it to the upstream. It provides the ability to create non-persistent snapshots on most modern systems without the requirement to change their configuration. +There is no novelty in the idea of creating snapshots of block devices. The Linux kernel already has mechanisms for creating snapshots. Firstly, this is Device Mapper, which allows you to create persistent and non-persistent snapshots of block devices. There are file systems that support snapshots, such as BTRFS. There are other mechanisms, but they all have their own features. These features do not allow them to be used as a universal tool for creating backups. That is why different backup vendors offer their own kernel modules for creating snapshots. + +The blksnap module has been part of the Linux kernel since version 6.?(the patch is under consideration at the time of editing this document). It provides the ability to create non-persistent snapshots on most modern systems without the requirement to change their configuration. ## Features -A change tracker is implemented in the module. It allows to determine which blocks were changed during the time between the last snapshot created and any of the previous snapshots of the same generation. This allows to implement the logic of both incremental and differential backups. -An arbitrary range of sectors on any block device can be used to store snapshot changes. The size of the change store can be increased after the snapshot is created by adding new sector ranges. This allows to create storage of differences in individual files on a file system that can occupy the entire space of a block device and increase the storage of differences as needed. +A change tracker is implemented in the module. It allows determining which blocks were changed during the time between the last snapshot created and any of the previous snapshots of the same generation. This allows to implement the logic of both incremental and differential backups. -To create images of snapshots of block devices, the module stores blocks of the original block device that have been changed since the snapshot was taken. To do this, the module intercepts write requests and reads blocks that need to be overwritten. This algorithm guarantees the safety of the data of the original block device in case of overflow of the snapshot and even in case of unpredictable critical errors. +To create images of snapshots of block devices, the module stores blocks of the original block device that have been changed since the snapshot was taken. The blksnap module guarantees the safety of the data of the original block device in case of overflow of the snapshot and even in case of unpredictable critical errors. A snapshot is created simultaneously for several block devices, ensuring their consistent state in the backup. The snapshot is created specifically for the block device, and not for the state of the file system. This allows it to be used for cases when a block device is used without a file system, the file system is not supported or is not fully supported. -The listed set of features allows to use it for the purposes of backup and replication of the entire contents of the disk subsystem as a whole. As a result, restoring the system from a backup is much easier. - -In addition to the kernel module itself, a set of related software has been developed under the GPL and LGPL licenses. The console tool and the C++ library for module management can be used for integration with other projects. The test suite will allow regression testing after module changes, after bug fixes, or after adding new features. The developed documentation is designed to make the study of the module more comfortable. +The listed set of features allows using it for the purposes of backup and replication of the entire contents of the disk subsystem as a whole. As a result, restoring the system from a backup is much easier. ## How it works -The blksnap module is a block layer filter. It handles all write I/O requests. -The filter is attached to the block device when the snapshot is created for the first time. - -The change tracker marks all overwritten blocks. When creating a snapshot, information about the history of changes on the block device is available. - -The module reads the blocks that need to be overwritten and stores them in the change store. When reading from a snapshot image, reading is performed either from the original device or from the change store. +The blksnap module is a block layer filter. It handles all write I/O units. The filter is attached to the block device when the snapshot is created for the first time. +The change tracker marks all overwritten blocks. When creating a snapshot from the change tracker, information about the history of changes on the block device is available. +When handling a request to record the original device, the module reads the blocks that need to be overwritten and stores them in the difference storage. When reading from a snapshot image, reading is performed either from the original device or from the difference storage. ### Change tracking -A change tracker map is created for each block device. One byte of this map corresponds to one block. The block size is set by the module configuration parameters: tracking_block_minimum_shift and tracking_block_maximum_count. The tracking_block_minimum_shift parameter limits the minimum block size for tracking, while tracking_block_maximum_count defines the maximum allowed number of blocks. The default values for these parameters are determined by the module configuration declarations: CONFIG_BLK_SNAP_TRACKING_BLOCK_MINIMUM_SHIFT and CONFIG_BLK_SNAP_TRACKING_BLOCK_MAXIMUM_COUNT. The size of the change tracker block is determined depending on the size of the block device when adding a tracking device, that is, when the snapshot is taken for the first time. The block size must be a power of two. -The byte of the change tracking map stores a number from 0 to 255. This is the sequence number of the snapshot for which there have been changes in the block since the snapshot was taken. Each time a snapshot is taken, the number of the current snapshot increases by one. This number is written to the cell of the change tracking map when writing to the block. Thus, knowing the number of one of the previous snapshots and the number of the last one, we can determine from the change tracking map which blocks have been changed. When the number of the current change has reached the maximum allowed value for the map of 255, when creating the next snapshot, the change tracking map is reset to zero, and the number of the current snapshot is assigned the value 1. The tracker of changes is reset and a new UUID — a unique identifier of the generation of snapshots — is generated. The snapshot generation identifier allows to identify that a change tracking reset has been performed. +A change tracker map is created for each block device. One byte of this map corresponds to one block. The block size is set by the module parameters: tracking_block_minimum_shift, tracking_block_maximum_shift and tracking_block_maximum_count. The tracking_block_minimum_shift parameter limits the minimum block size for tracking, while tracking_block_maximum_count defines the maximum allowed number of blocks. The size of the change tracker block is determined depending on the size of the block device when adding a tracking device, that is, when the snapshot is taken for the first time. The block size must be a power of two. However, the block size should not exceed tracking_blocker_maximum_shift. If the block size reaches this value, then their number may exceed tracking_block_maximum_count. -The change map has two copies. One is active, and it tracks the current changes on the block device. The second one is available for reading while the snapshot is being held, and it contains the history of changes that occurred before the snapshot was taken. Copies are synchronized at the moment of taking a snapshot. After the snapshot is released, a second copy of the map is not needed, but it is not released, so as not to allocate memory for it again the next time the snapshot is created. +The byte of the change tracking map stores a number from 0 to 255. This is the sequence number of the snapshot for which there have been changes in the block since the snapshot was taken. Each time a snapshot is taken, the number of the current snapshot increases by one. This number is written to the cell of the change tracking map when writing to the block. Thus, knowing the number of one of the previous snapshots and the number of the last one, we can determine from the change tracking map which blocks have been changed. When the number of the current change has reached the maximum allowed value for the map of 255, when creating the next snapshot, the change tracking map is reset to zero, and the number of the current snapshot is assigned the value 1. The tracker of changes is reset and a new UUID — a unique identifier of the generation of snapshots — is generated. The snapshot generation identifier allows identifying that a change tracking reset has been performed. + +There are two instances of the change tracker map. One is active, and it tracks the current changes on the block device. The second map is available for reading while the snapshot is being held, and it contains the history of changes that occurred before the snapshot was taken. Maps are synchronized at the moment of taking a snapshot. After the snapshot is released, the second card is not used. ### Copy-on-write + Data is copied in blocks, or rather in chunks. The term "chunk" is used not to confuse it with change tracker blocks and I/O blocks. In addition, the "chunk" in the blksnap module means about the same as the "chunk" in the dm-snap module. -The size of the chunk is determined by the parameters of the module: chunk_minimum_shift and chunk_maximum_count. The chunk_minimum_shift parameter limits the minimum chunk size, while chunk_maximum_count defines the maximum allowed number of chunks. The default values for these parameters are determined by the module configuration declarations: CONFIG_BLK_SNAP_CHUNK_MINIMUM_SHIFT and CONFIG_BLK_SNAP_CHUNK_MAXIMUM_COUNT. The size of the chunks is determined depending on the size of the block device at the time of taking the snapshot. The size of the chunk must be a power of two. +The size of the chunk is determined by the parameters of the module: chunk_minimum_shift, chunk_maximum_shift and chunk_maximum_count. The chunk_minimum_shift parameter limits the minimum chunk size, while chunk_maximum_count defines the maximum allowed number of chunks. The size of the chunks is determined depending on the size of the block device at the time of taking the snapshot. The size of the chunk must be a power of two. However, the chunk size should not exceed chunk_maximum_shift. If the chunk size reaches this value, then their number may exceed chunk_maximum_count. -One chunk is described by the &struct chunk structure. An array of structures is created for each block device. The structure contains all the necessary information to copy the chunks data from the original block device to the difference storage. The same information allows to create the snapshot image. A semaphore is located in the structure, which allows synchronization of threads accessing to the chunk. While the chunk data is being read from the original block device, the thread that initiated the write request is put into the sleeping state. +One chunk is described by the "struct chunk". An array of structures is created for each block device. The structure contains all the necessary information to copy the chunks of data from the original block device to the difference storage. The same information allows creating the snapshot image. A semaphore is located in the structure, which allows synchronization of threads accessing to the chunk. While the chunk data is being read from the original block device, the thread that initiated the write request is put into the sleeping state. -The feature of the block layer filter is that when handling an I/O request, synchronous I/O requests cannot be executed. Synchronous requests can cause stack overflow in the case of a recursive call to submit_bio_noacct(), and also increase the chance of a mutual blocking situation. Therefore, before calling the filter callback function, the variable current->bio_list is initialized, and the submit_bio_noacct() function in this case adds an I/O request to this linked list. After executing the request processing callback function, all requests are extracted from the current->bio_list list and submit_bio_noacct() is called for them. Therefore, the copy-on-write algorithm is performed asynchronously. +There is also a drawback. Since an entire chunk is copied when overwriting even one sector, a situation of rapid filling of the difference storage when writing data to a block device in small portions in random order is possible. This situation is possible in case of great file system fragmentation. At the same time, performance of the machine in this case is severely degraded even without the blksnap module. Therefore, this problem does not occur on real servers, although it can easily be created by artificial tests. -The block layer has another feature. If a read request is sent, and then a write request is sent, then write can be performed first, and only then a read. Even using the REQ_SYNC and REQ_PREFLUSH flags does not provide correct behavior. Maybe it's a mistake, I'm not sure. +### Difference storage -Due to these features, the current implementation assumes a repeated entry into the handler. When the callback function is called for the first time, the semaphore of the chunk is locked and a linked list of requests reading the rewritable chunk is formed, after which the block layer passes them to execution and calls the filter callback function again. When the callback function is called for the second time, when trying to lock the chunk semaphore, the process is locked until the chunk data is read from the original block device. If, when executing the callback function, it becomes clear that the data of the chunk has already been copied, then the write request is simply skipped without any delay. +For one snapshot, a single difference storage is created that is common to all snapshot block devices. +The snapshot difference storage can use: +- a file on a regular file system +- block device +- file on tmpfs. -This algorithm allows to efficiently perform backups of systems that run Round Robin Database [RRD](https://www.loriotpro.com/Products/On-line_Documentation_V5/LoriotProDoc_EN/V22-RRD_Collector_RRD_Manager/V22-A1_Introduction_RRD_EN.htm). Such databases can be overwritten several times during backup of the machine. Of course, the value of the RRD data backup of the monitoring system can be questioned. However, it is often a task to make a backup copy of the entire enterprise infrastructure in full, so that to restore or replicate it entirely in case of problems. +The file or block device must be opened with the O_EXCL flag to ensure exclusive access of the module to storage. -But there is also a drawback. Since an entire chunk is copied when overwriting even one sector, a situation of rapid filling of the difference storage when writing data to a block device in small portions in random order is possible. This situation is possible in case of great file system fragmentation. At the same time, performance of the machine in this case is severely degraded even without the blksnap module. Therefore, this problem does not occur on real servers, although it can easily be created by artificial tests. +#### A file on a regular file system -### Difference storage -Before considering how the blksnap module organizes the difference storage, let's look at other similar solutions. +A file on a regular file system can be applied to most systems. For file systems that support fallocate(), a dynamic increase in the difference storage is available. There is no need to allocate a large file before creating a snapshot. The kernel module itself increased its size as needed, but within the specified limit. +However, a file on a regular file system cannot be applied if it is located on a block device for which a snapshot is being created. This means that there must be a file system on the system that is not involved in the backup. -BTRFS implements snapshots at the file system level. If a file is overwritten after the snapshot is taken, then the new data is stored in new blocks. The old blocks remain and are used for the snapshot image. Therefore, if the snapshot overflows, there is no space to store the new up-to-date data, and the system loses its operability. +#### Block device -Device Mapper implements snapshot support using dm-snap. It implements the logic of a snapshot of a block device. The copy-on-write algorithm is about the same as that of the blksnap module. Before overwriting the data on the original device, it is read and stored in a block device specially allocated for this purpose. In practice, this means that when taking a snapshot from several block devices, you need to have one or several empty block devices, and need to allocate areas on them for each device from which the snapshot is taken. The first problem is that the system may not have free disk space for storing differences. If there is free space on the disk, then the question arises: "Is there enough free disk space, and how to divide it between block devices?". You can divide this space equally or proportionally to the size. But the load on different disks is usually unevenly distributed. As a result, snapshot overflow occurs for one of the block devices, while for the others all the reserved space may remain free. It turns out that the reserved space is used suboptimally. +This version of the difference storage allows to get the maximum possible performance, but requires reserved disk space. +Exclusive access to the block device ensures that there are no mounted file systems on it. Dynamic increase of the difference storage does not function in this case. The storage is limited to one block device. -The difference storage of the blksnap module does not have the listed disadvantages. -1. Copying when writing differences to the storage saves the old data needed for snapshot images. Therefore, when the snapshot is overflowing, the snapshot images become inconsistent, the backup process fails, but the system remains operational without data loss. -2. The difference storage is common to all block devices in the snapshot. There is no need to distribute the difference storage area between block devices. -3. The difference storage is a pool of disk space areas on different block devices. That is, the load of storing changes can be distributed. -4. There is no need to allocate large disk space immediately before taking a snapshot. Even while the snapshot is being held, the difference storage can be expanded. +#### File on tmpfs -Thanks to the listed features of the blksnap module difference storage, we do not need to allocate free disk space in advance. It is enough to have free space on the file system. Areas of disk space can be allocated using fallocate(). Unfortunately, not all file systems support this system call, but the most common XFS and EXT4 support it (as well as BTRFS, but conversion of virtual offsets to physical ones is required). When holding a snapshot, user-space process can poll its status using a special ioctl. When free space in the difference storage runs out, the module notifies the user process about this, and the user process can prepare a new area and transfer it to the module to expand the difference storage. +A file on tmpfs can be applied if there is no free disk space and all file systems participate in the backup. In this case, the difference storage is located in virtual memory, that is, in RAM and the swap file (partition). For low-load systems, this variant may be acceptable. High-load servers may require a swap of a fairly large size, otherwise there may not be enough virtual memory to difference storage, which may lead to an overflow of the snapshot. ## How to use it + +In addition to the kernel module, a set of related software was developed under the GPL and LGPL licenses. +The console tool and the C++ library for module management can be used for integration with other projects. + Depending on the needs and the selected license, you can choose different options for managing the module: -1. Directly via ioctl -2. Using a static C++ library -3. Using the blksnap console tool +1. directly via ioctl +2. using a static C++ library +3. using the blksnap console tool. ### Using ioctl -The kernel module provides the blksnap.h header file. It describes all available ioctls and structures for interacting with the module. Each ioctl and structure is documented in detail. This should be enough to understand the interface. If you think that something is missing, I suggest expanding the description in the header file. -In addition, the libblksnap library and the blksnap tool use this interface, so in case of difficulty, you can use their source code as an example. + +The kernel includes the header files uapi/linux/blk-filter.h and uapi/linux/blksnap.h. The filter is controlled using ioctl: *BLKFILTER_ATTACH*, *BLKFILTER_DETACH*, *BLKFILTER_CTL*. They allow attaching the filter to a block device, detach it and send control commands to the filter. In addition, the blksnap module creates a /dev/blksnap-control file. With its help, commands for managing snapshots are transmitted to the module. A detailed description of the block device filter interface can be found in the kernel documentation Documentation/block/blkfilter.rst or [online](https://www.kernel.org/doc/html/latest/block/blkfilter.html). Description of the blksnap module interface in the kernel documentation Documentation/block/blksnap.rst or [online](https://www.kernel.org/doc/html/latest/block/blksnap.html). ### Static C++ library -The library was created primarily to simplify creation of tests in C++. This is also a good example of using the ioctl interface of the module. You can use it directly in the GPL-2+ application or make an LGPL-2+ library, with which a proprietary application will be dynamically linked. -The library interface is quite simple and intuitive. There are tests that apply it. They can be an example of using the library. Therefore, I will briefly describe only the purpose of the key classes. -* The blksnap::CBlksnap (include/blksnap/Blksnap.h) class is a thin C++ wrapper for the ioctl interface of the kernel module. Abstraction at this level may be enough for you. -* The blksnap::Session (include/blksnap/Session.h) interface. Its static Create() method creates a snapshot object and holds it. The snapshot is released when this object is released. The object itself contains implementation of the algorithm for allocating new portions for the repository of changes and processing events from the module. For the method to work, it is enough to create a session and use the GetImageDevice() call to get the name of the snapshot image block device. It is very suitable for quick prototyping of an application. -* The blksnap::ICbt (include/blksnap/Cbt.h) interface allows to access the change tracker data. -* The include/blksnap/Service.h file contains the function of getting the kernel module version and may contain other functionality for debugging the module. +The library was created to simplify the creation of tests in C++. The library is an example of using the module's ioctl interface. + +#### class blksnap::CTracker + +The сlass *blksnap::CTracker* from ([include/blksnap/Tracker.h](../include/blksnap/Tracker.h)) is a thin C++ wrapper for the block device filter interface, which is implemented in the kernel as an ioctl: +- *BLKFILTER_ATTACH* +- *BLKFILTER_DETACH* +- *BLKFILTER_CTL*. + +Methods of the class: +- *Attach* - attachs the block layer filter 'blksnap' +- *Detach* - detach filter +- *CbtInfo* - provides the status of the change tracker for the block device +- *ReadCbtMap* - reads the block device change tracker table +- *MarkDirtyBlock* - sets the 'dirty blocks' of the change tracker +- *SnapshotAdd* - adds a block device to the snapshot +- *SnapshotInfo* - allows getting the snapshot status of a block device. + +#### class blksnap::CSnapshot + +The class *blksnap::CSnapshot* from ([include/blksnap/Snapshot.h](../include/blksnap/Snapshot.h)) is a thin C++ wrapper for the blksnap module management interface. +Implements ioctl calls: +- *IOCTL_BLKSNAP_VERSION* +- *IOCTL_BLKSNAP_SNAPSHOT_CREATE* +- *IOCTL_BLKSNAP_SNAPSHOT_COLLECT* +- *IOCTL_BLKSNAP_SNAPSHOT_TAKE* +- *IOCTL_BLKSNAP_SNAPSHOT_DESTROY* +- *IOCTL_BLKSNAP_SNAPSHOT_WAIT_EVENT*. + +Static methods of the class: +- *Collect* - allows getting a list of UUIDs of all snapshots of the blksnap module +- *Version* - get the module version +- *Create* - creates an instance of the *blksnap::C Snapshot* class, while the module creates a snapshot to which devices can be added +- *Open* - creates an instance of the *blksnap::CSnapshot* class for an existing snapshot by its UUID. + +Methods of the class: +- *Take* - take snapshot +- *Destroy* - destroy snapshot +- *WaitEvent* - allows receiving events about changes in the state of snapshot +- *Id* - requests a snapshot UUID. + +#### class blksnap::ISession + +The class *blksnap::ISession* from ([include/blksnap/Session.h](../include/blksnap/Session.h)) creates a snapshot session. +The static method *Create* creates an instance of the class that creates, takes and holds the snapshot. The class contains a worker thread that checks the snapshot status and stores them in a queue when events are received. The *GetError* method allows reading a message from this queue. The class destructor destroys the snapshot. + +#### class blksnap::ICbt + +The class *blksnap::ICbt* from ([include/blksnap/Cbt.h](../include/blksnap/Cbt.h)) allows accessing the data of the change tracker. +The static method *Create* creates an object to interact with the block device change tracker. + +Methods of the class: +- *GetCbtInfo* - provides information about the current state of the change tracker for a block device +- *GetCbtData* - allow reading the table of changes +- *GetImage* - provide the name of the block device for the snapshot image +- *GetError* - allows checking the snapshot status of a block device. + +#### struct blksnap::SRange + +The struct *blksnap::SRange* from ([include/blksnap/Sector.h](../include/blksnap/Sector.h)) describes the area of the block device, combines the offset from the beginning of the block device and the size of the area in the form of the number of sectors. + +#### blksnap::Version + +The function *blksnap::Version* from ([include/blksnap/Service.h](include/blksnap/Service.h)) allows getting the version of the kernel module. ### Blksnap console tool -In accordance with the "the best documentation is code" paradigm, the tool contains a detailed built-in help. Calling "blksnap --help" allows to get a list of commands. When requesting "blksnap \ --help", a description of the command is output. If you think that this documentation is not enough, I suggest adding the necessary information to the built-in help system of the tool. -If you still have questions about how to use the tool, then you can use tests written in bash as an example. +The tool contains detailed built-in help. Calling "blksnap --help" allows you to get a list of commands. When requesting "blksnap \ --help", a description of the command is output. Page [man](./blksnap.8) may also be useful. Use the built-in documentation. + +### Regression tests + +The test suite allows regression testing of the blksnap module. The tests are created using bash scripts and C++. +Tests on bash scripts are quite simple. They check the basic functionality. Interaction with the kernel module is carried out using the blksnap tool. +C++ tests implement more complex verification algorithms. Documentation for C++ tests is available: +- [boundary](./tests/boundary.md) +- [corrupt](./tests/corrupt.md) + +## License -## What's next -* Ensure the operability of the module and related software on different architectures. At the moment, it is tested to work on amd64. Testing on the arm64le architecture was quite successful. A system crash was found on the ppc64le architecture. Testing on other architectures has not been performed yet. -* For the blksnap console tool, add the ability to return data in the form of a json structure. This should facilitate integration with other programs, such as written on pythons. -* For the blksnap console tool, it would be great to make a man page. +The kernel module, like the Linux kernel, has a GPL-2 license. +The blksnap console tool has a GPL-2+ license. +The libraries are licensed LGPL-3+. diff --git a/doc/blksnap.rst b/doc/blksnap.rst deleted file mode 100644 index d3f162da..00000000 --- a/doc/blksnap.rst +++ /dev/null @@ -1,254 +0,0 @@ -.. SPDX-License-Identifier: GPL-2.0 - -======================================== -Block Devices Snapshots Module (blksnap) -======================================== - -Introduction -============ - -At first glance, there is no novelty in the idea of creating snapshots for block devices. -The Linux kernel already has mechanisms for creating snapshots. -Device Mapper includes dm-snap, which allows to create snapshots of block devices. -BTRFS supports snapshots at the file system level. -However, both of these options have flaws that do not allow to use them as a universal tool for creating backups. - -Device Mapper flaws: - -- Block devices must have LVM markup. - If no logical volumes were created during system installation, then dm-snap cannot be applied. -- To store snapshot differences of one logical volume, it is necessary to reserve a fixed range of sectors on a reserved empty logical volume. - Firstly, it is required that the system has enough space unoccupied by the file system, which rarely occurs on real servers. - Secondly, as a rule, it is necessary to create snapshots for all logical volumes at once, which requires dividing this reserved space between several logical volumes. - This space can be divided equally or proportionally to the size. But the load on different disks is usually uneven. - As a result, a snapshot overflow may occur for one of the block devices, while for others all the reserved space may remain free. - This complicates management of the difference storage and makes it almost impossible to create a coherent snapshot of multiple logical volumes. - -BTRFS flaws: - -- Snapshots create a persistent image of the file system, not a block device. Such a snapshot is only applicable for a file backup. -- When synchronizing the snapshot subvolume with the backup subvolume, reading the differences leads to random access to the block device, which leads to decrease in efficiency compared to direct copying of the block device. -- BTRFS allows to get an incremental backup [#btrfs_increment]_, but it is necessary to keep a snapshot of the previous backup cycle on the system, which leads to excessive consumption of disk space. -- If there is not enough free space on the file system while holding the snapshot, new data cannot be saved, which leads to a server malfunction. - -Features of the blksnap module: - -- Change tracker -- Snapshots at the block device level -- Dynamic allocation of space for storing differences -- Snapshot overflow resistance -- Coherent snapshot of multiple block devices - - -For a more detailed description of the features, see the `Features`_ section. - -The listed set of features allows to achieve the key goals of the backup tool: - -- Simplicity and versatility of use -- Reliability -- Minimal consumption of system resources during backup -- Minimal time required for recovery or replication of the entire system - -Features -======== - -Change tracker --------------- - -The change tracker allows to determine which blocks were changed during the time between the last snapshot created and any of the previous snapshots. -Having a map of changes, it is enough to copy only the changed blocks, and no need to reread the entire block device completely. -The change tracker allows to implement the logic of both incremental and differential backups. -Incremental backup is critical for large file repositories whose size can be hundreds of terabytes and whose full backup time can take more than a day. -On such servers, the use of backup tools without a change tracker becomes practically impossible. - -Snapshot at the block device level ----------------------------------- - -A snapshot at the block device level allows to simplify the backup algorithm and reduce consumption of system resources. -It also allows to perform linear reading of disk space directly, which allows to achieve maximum reading speed with minimal use of processor time. -At the same time, the versatility of creating snapshots for any block device is achieved, regardless of the file system located on it. -The exceptions are BTRFS, ZFS and cluster file systems. - -Dynamic allocation of storage space for differences ---------------------------------------------------- - -To store differences, the module does not require a pre-reserved block device range. -A range of sectors can be allocated on any block device immediately before creating a snapshot in individual files on the file system. -In addition, the size of the difference storage can be increased after the snapshot is created by adding new sector ranges on block devices. -Sector ranges can be allocated on any block devices of the system, including those on which the snapshot was created. -A shared difference storage for all images of snapshot block devices allows to optimize the use of disk space. - -Snapshot overflow resistance ----------------------------- - -To create images of snapshots of block devices, the module stores blocks of the original block device that have been changed since the snapshot was taken. -To do this, the module handles write requests and reads blocks that need to be overwritten. -This algorithm guarantees safety of the data of the original block device in the event of an overflow of the snapshot, and even in the case of unpredictable critical errors. -If a problem occurs during backup, the difference storage is released, the snapshot is closed, no backup is created, but the server continues to work. - -Coherent snapshot of multiple block devices -------------------------------------------- - -A snapshot is created simultaneously for all block devices for which a backup is being created, ensuring their coherent state. - - -Algorithms -========== - -Overview --------- - -The blksnap module is a block-level filter. It handles all write I/O units. -The filter is attached to the block device when the snapshot is created for the first time. -The change tracker marks all overwritten blocks. -Information about the history of changes on the block device is available while holding the snapshot. -The module reads the blocks that need to be overwritten and stores them in the difference storage. -When reading from a snapshot image, reading is performed either from the original device or from the difference storage. - -Change tracking ---------------- - -A change tracker map is created for each block device. -One byte of this map corresponds to one block. -The block size is set by the ``tracking_block_minimum_shift`` and ``tracking_block_maximum_count`` module parameters. -The ``tracking_block_minimum_shift`` parameter limits the minimum block size for tracking, while ``tracking_block_maximum_count`` defines the maximum allowed number of blocks. -The size of the change tracker block is determined depending on the size of the block device when adding a tracking device, that is, when the snapshot is taken for the first time. -The block size must be a power of two. - -The byte of the change map stores a number from 0 to 255. -This is the snapshot number, since the creation of which there have been changes in the block. -Each time a snapshot is created, the number of the current snapshot is increased by one. -This number is written to the cell of the change map when writing to the block. -Thus, knowing the number of one of the previous snapshots and the number of the last snapshot, one can determine from the change map which blocks have been changed. -When the number of the current change reaches the maximum allowed value for the map of 255, at the time when the next snapshot is created, the map of changes is reset to zero, and the number of the current snapshot is assigned the value 1. -The change tracker is reset, and a new UUID is generated - a unique identifier of the snapshot generation. -The snapshot generation identifier allows to identify that a change tracking reset has been performed. - -The change map has two copies. One copy is active, it tracks the current changes on the block device. -The second copy is available for reading while the snapshot is being held, and contains the history up to the moment the snapshot is taken. -Copies are synchronized at the moment of snapshot creation. -After the snapshot is released, a second copy of the map is not needed, but it is not released, so as not to allocate memory for it again the next time the snapshot is created. - -Copy on write -------------- - -Data is copied in blocks, or rather in chunks. -The term "chunk" is used to avoid confusion with change tracker blocks and I/O blocks. -In addition, the "chunk" in the blksnap module means about the same as the "chunk" in the dm-snap module. - -The size of the chunk is determined by the ``chunk_minimum_shift`` and ``chunk_maximum_count`` module parameters. -The ``chunk_minimum_shift`` parameter limits the minimum size of the chunk, while ``chunk_maximum_count`` defines the maximum allowed number of chunks. -The size of the chunk is determined depending on the size of the block device at the time of taking the snapshot. The size of the chunk must be a power of two. -One chunk is described by the ``struct chunk`` structure. An array of structures is created for each block device. -The structure contains all the necessary information to copy the chunks data from the original block device to the difference storage. -This information allows to describe the snapshot image. A semaphore is located in the structure, which allows synchronization of threads accessing the chunk. - -The block level has a feature. If a read I/O unit was sent, and a write I/O unit was sent after it, then a write can be performed first, and only then a read. -Therefore, the copy-on-write algorithm is executed synchronously. -If a write request is handled, the execution of this I/O unit will be delayed until the overwritten chunks are copied to the difference storage. -But if, when handling a write I/O unit, it turns out that the recorded range of sectors has already been copied to the difference storage, then the I/O unit is simply passed. - -This algorithm allows to efficiently perform backups of systems that run Round Robin Database. -Such databases can be overwritten several times during the system backup. -Of course, the value of a backup of the RRD monitoring system data can be questioned. However, it is often a task to make a backup of the entire enterprise infrastructure in order to restore or replicate it entirely in case of problems. - -There is also a flaw in the algorithm. When overwriting at least one sector, an entire chunk is copied. Thus, a situation of rapid filling of the difference storage when writing data to a block device in small portions in random order is possible. -This situation is possible in case of strong fragmentation of data on the file system. -But it must be borne in mind that with such data fragmentation, performance of systems usually degrades greatly. -So, this problem does not occur on real servers, although it can easily be created by artificial tests. - -Difference storage ------------------- - -The difference storage is a pool of disk space areas, and it is shared with all block devices in the snapshot. -Therefore, there is no need to divide the difference storage area between block devices, and the difference storage itself can be located on different block devices. - -There is no need to allocate a large disk space immediately before creating a snapshot. -Even while the snapshot is being held, the difference storage can be expanded. -It is enough to have free space on the file system. - -Areas of disk space can be allocated on the file system using fallocate(), and the file location can be requested using Fiemap Ioctl or Fibmap Ioctl. -Unfortunately, not all file systems support these mechanisms, but the most common XFS, EXT4 and BTRFS file systems support it. -BTRFS requires additional conversion of virtual offsets to physical ones. - -While holding the snapshot, the user process can poll the status of the module. -When free space in the difference storage is reduced to a threshold value, the module generates an event about it. -The user process can prepare a new area and pass it to the module to expand the difference storage. -The threshold value is determined as half of the value of the ``diff_storage_minimum`` module parameter. - -If free space in the difference storage runs out, an event is generated about the overflow of the snapshot. -Such a snapshot is considered corrupted, and read I/O units to snapshot images will be terminated with an error code. -The difference storage stores outdated data required for snapshot images, so when the snapshot is overflowed, the backup process is interrupted, but the system maintains its operability without data loss. - -How to use -========== - -Depending on the needs and the selected license, you can choose different options for managing the module: - -- Using ioctl directly -- Using a static C++ library -- Using the blksnap console tool - -Using ioctl ------------ - -The module provides the ``include/uapi/blksnap.h`` header file. -It describes all the available ioctl and structures for interacting with the module. -Each ioctl and structure is documented in detail. -The general algorithm for calling control requests is approximately the following: - -1. ``blk_snap_ioctl_snapshot_create`` initiates the snapshot creation process. -2. ``blk_snap_ioctl_snapshot_append_storage`` allows to add the first range of blocks to store changes. -3. ``blk_snap_ioctl_snapshot_take`` creates block devices of block device snapshot images. -4. ``blk_snap_ioctl_snapshot_collect`` and ``blk_snap_ioctl_snapshot_collect_images`` allow to match the original block devices and their corresponding snapshot images. -5. Snapshot images are being read from block devices whose numbers were received when calling ``blk_snap_ioctl_snapshot_collect_images``. Snapshot images also support the write operation. So, the file system on the snapshot image can be mounted before backup, which allows to perform the necessary preprocessing. -6. ``blk_snap_ioctl_tracker_collect`` and ``blk_snap_ioctl_tracker_read_cbt_map`` allow to get data of the change tracker. If a write operation was performed for the snapshot, then the change tracker takes this into account. Therefore, it is necessary to receive tracker data after write operations have been completed. -7. ``blk_snap_ioctl_snapshot_wait_event`` allows to track the status of snapshots and receive events about the requirement to expand the difference storage or about snapshot overflow. -8. The difference storage is expanded using ``blk_snap_ioctl_snapshot_append_storage``. -9. ``blk_snap_ioctl_snapshot_destroy`` releases the snapshot. -10. If, after creating a backup, postprocessing is performed that changes the backup blocks, it is necessary to mark such blocks as dirty in the change tracker table. ``blk_snap_ioctl_tracker_mark_dirty_blocks`` is used for this. -11. It is possible to disable the change tracker from any block device using ``blk_snap_ioctl_tracker_remove``. - -Static C++ library ------------------- - -The [#userspace_libs]_ library was created primarily to simplify creation of tests in C++, and it is also a good example of using the module interface. -When creating applications, direct use of control calls is preferable. -However, the library can be used in an application with a GPL-2+ license, or a library with an LGPL-2+ license can be created, with which even a proprietary application can be dynamically linked. - -blksnap console tool --------------------- - -The blksnap [#userspace_tools]_ console tool allows to control the module from the command line. -The tool contains detailed built-in help. -To get the list of commands, enter the ``blksnap --help`` command. -The ``blksnap --help`` command allows to get detailed information about the parameters of each command call. -This option may be convenient when creating proprietary software, as it allows not to compile with the open source code. -At the same time, the blksnap tool can be used for creating backup scripts. -For example, rsync can be called to synchronize files on the file system of the mounted snapshot image and files in the archive on a file system that supports compression. - -Tests ------ - -A set of tests was created for regression testing [#userspace_tests]_. -Tests with simple algorithms that use the ``blksnap`` console tool to control the module are written in Bash. -More complex testing algorithms are implemented in C++. -Documentation [#userspace_tests_doc]_ about them can be found on the project repository. - -References -========== - -.. [#btrfs_increment] https://btrfs.wiki.kernel.org/index.php/Incremental_Backup - -.. [#userspace_libs] https://github.com/veeam/blksnap/tree/master/lib/blksnap - -.. [#userspace_tools] https://github.com/veeam/blksnap/tree/master/tools/blksnap - -.. [#userspace_tests] https://github.com/veeam/blksnap/tree/master/tests - -.. [#userspace_tests_doc] https://github.com/veeam/blksnap/tree/master/doc - -Module interface description -============================ - -.. kernel-doc:: include/uapi/linux/blksnap.h diff --git a/doc/blksnap_ru.md b/doc/blksnap_ru.md index 1c7ee614..5b452f7f 100644 --- a/doc/blksnap_ru.md +++ b/doc/blksnap_ru.md @@ -1,16 +1,30 @@ -# blksnap - модуль для создания снапшотов блочных устройств +# Снапшоты блочных устройств с помощью модуля ядра blksnap + +* [Введение](#введение) +* [Возможности модуля](#возможности-модуля) +* [Как это работает](#как-это-работает) + - [Трекер изменений](#трекер-изменений) + - [Копирование при записи](#копирование-при-записи) + - [Хранилище изменений](#хранилище-изменений) +* [Как этим пользоваться](#как-этим-пользоваться) + - [Иcпользование ioctl](#иcпользование-ioctl) + - [Статическая С++ библиотека](#статическая-c-библиотека) + - [Консольный инструмент blksnap](#консольный-инструмент-blksnap) + - [Регрессионные тесты](#регрессионные-тесты) +* [Лицензия](#лицензия) ## Введение -В идее создания снапшотов для блочных устройств нет новизны. Даже в ядре Linux уже есть механизмы для создания снапшотов блочных устройств. Прежде всего это Device Mapper, который позволяет создавать персистентные и неперсистентные снапшоты блочных устройств. Есть файловые системы, поддерживающие снапшоты, такие как BTRFS. Есть и другие механизмы, но у всех них есть свои особенности, которые не позволяют использовать их как универсальное средство для создание резервных копий. Именно поэтому разные поставщики средств для резервного копирования предлагают для создания снапшотов свои модули ядра. К сожалению, ни один из этих модулей не соответствует требованиям восходящего потока ядра Linux. -Модуль blksnap создавался именно с целью предложения его в восходящий поток. Он обеспечивает создание неперсистентных снапшотов на большинстве современных систем без требования изменения их конфигурации. +В идее создания снапшотов блочных устройств нет новизны. В ядре Linux уже есть механизмы для создания снапшотов. Прежде всего это Device Mapper, который позволяет создавать персистентные и неперсистентные снапшоты блочных устройств. Есть файловые системы, поддерживающие снапшоты, такие как BTRFS. Есть и другие механизмы, но у всех них есть свои особенности, которые не позволяют использовать их как универсальное средство для создание резервных копий. Именно поэтому разные поставщики средств для резервного копирования предлагают для создания снапшотов свои модули ядра. -## Возможности модуля blksnap -В модуле реализован трекер изменений. Он позволяет определить, какие блоки были изменены за время между последним созданным снапшотом и любым из предыдущих снапшотов одного поколения. Это позволяет реализовывать логику как инкрементального, так и дифференциального бэкапа. +Модуль blksnap является частью ядра Linux начиная с версии 6.?(патч находится на рассмотрении на момент редактировния данного документа). +Он обеспечивает создание неперсистентных снапшотов на большинстве современных систем без требования изменения их конфигурации. + +## Возможности модуля -Для хранения изменений снапшота может быть использован произвольный диапазон секторов на любом блочном устройстве. Размер хранилища изменений может увеличиваться уже после создания снапшота путём добавления новых диапазонов секторов. Это позволяет создавать хранилище изменений в отдельных файлах на файловой системе, которая может занимать всё пространство блочного устройства и увеличивать хранилище изменений по мере необходимости. +В модуле реализован трекер изменений. Он позволяет определить, какие блоки были изменены за время между последним созданным снапшотом и любым из предыдущих снапшотов одного поколения. Это позволяет реализовывать логику как инкрементального, так и дифференциального бэкапа. -Для создания образов снапшотов блочных устройств модуль хранит блоки оригинального блочного устройства, которые были изменены с момента снятия снапшота. Для этого модуль перехватывает запросы на запись и считывает блоки, которые должны быть перезаписаны. Такой алгоритм гарантирует сохранность данных оригинального блочного устройства в случае переполнения снапшота, и даже в случае непредсказуемых критических ошибкок. +Для создания образов снапшотов блочных устройств модуль хранит блоки оригинального блочного устройства, которые были изменены с момента снятия снапшота. Модуль гарантирует сохранность данных оригинального блочного устройства в случае переполнения снапшота, и даже в случае непредсказуемых критических ошибкок. Снапшот создаётся одновременно для нескольких блочных устройств, обеспечивая их согласованное состояние в резервной копии. @@ -18,85 +32,150 @@ Перечисленный набор возможностей позволяет использовать модуль для целей резервного копирования и репликации всего содержимого дисковой подсистемы целиком. В результате значительно упрощается восстановление системы из резервной копии. -Кроме самого модуля ядра, был разработан набор сопутствующего ПО под лицензиями GPL и LGPL. -Консольный инструмент и С++-библиотека для управления модулем могут быть использованы для интеграции с другими проектами. -Набор тестов позволит провести регрессионное тестирование после изменений модуля, связанных с исправлением ошибок или добавлением новых возможностей. - -Разрабатываемая документация призвана сделать изучение модуля более комфортным. - ## Как это работает -Модуль blksnap является фильтром блочного слоя. Он перехватывает все запросы на запись. - -Подключение фильтра к блочному устройству выполняется при первом создании снапшорта. -Трекер изменений отмечает все перезаписанные блоки. При создании снапшота доступна информация об истории изменений на диске. - -Модуль выполняет чтение блоков, которые должны быть перезаписаны, и сохраняет их в хранилище изменений. При чтении из образа снапшота чтение выполняется либо из оригинального устройства, либо из хранилища изменений. +Модуль blksnap является фильтром блочного слоя. Он перехватывает все запросы на запись. Подключение фильтра к блочному устройству выполняется при первом создании снапшорта. +Трекер изменений отмечает все перезаписанные блоки. При создании снапшота от трекера изменений доступна информация об истории изменений на блочном устройстве. +При перехвате запроса на запись оригинального устройтсва, модуль выполняет чтение блоков, которые должны быть перезаписаны, и сохраняет их в хранилище изменений. При чтении из образа снапшота чтение выполняется либо из оригинального устройства, либо из хранилища изменений. ### Трекер изменений -Для каждого блочного устройства создаётся карта трекера изменений. Один байт этой карты соответствует одному блоку. Размер блока задаётся параметрами конфигурации модуля: tracking_block_minimum_shift и tracking_block_maximum_count. Параметр tracking_block_minimum_shift ограничивает минимальный размер блока для трекинга, в то время как tracking_block_maximum_count определяет их максимальное допустимое количество. Значения по умолчанию для этих параметров определяются объявлениями конфигурации модуля: CONFIG_BLK_SNAP_TRACKING_BLOCK_MINIMUM_SHIFT и CONFIG_BLK_SNAP_TRACKING_BLOCK_MAXIMUM_COUNT. Размер блока трекера изменений определяется в зависимости от размера блочного устройства при добавлении устройства под трекинг, то есть при первом снятии снапшота. Размер блока может должен быть кратен степени двойки. + +Для каждого блочного устройства создаётся карта трекера изменений. Один байт этой карты соответствует одному блоку. +Размер блока задаётся параметрами модуля: tracking_block_minimum_shift, tracking_block_maximum_shift и tracking_block_maximum_count. Параметр tracking_block_minimum_shift ограничивает минимальный размер блока для трекинга, в то время как tracking_block_maximum_count определяет их максимальное допустимое количество. Размер блока трекера изменений определяется в зависимости от размера блочного устройства при добавлении устройства под трекинг, то есть при первом снятии снапшота. Размер блока должен быть кратен степени двойки. Однако размер блока не должен превышать tracking_block_maximum_shift. Если размер блока достигает этого значения, то их количесто может превысить tracking_block_maximum_count. Байт карты изменений хранит число от 0 до 255. Это номер снапшота, с момента снятия которого были изменения в блоке. При каждом снятии снапшота номер текущего снапшота увеличивается на единицу. Этот номер записывается в ячейку карты изменений при записи в блок. Таким образом, зная номер одного из предыдущих снапшотов и номер последнего снапшота, можно определить по карте изменений, какие блоки были изменены. Когда номер текущего изменения достигает максимального допустимого значения для карты в 255, при создании следующего снапшота карта изменений обнуляется, а номеру текущего снапшота присваивается значение 1. Трекер изменений сбрасывается и генерируется новый UUID — уникальный идентификатор поколения снапшотов. Идентификатор поколения снапшотов позволяет выявлять, что был выполнен сброс трекинга изменений. -У карты изменений есть две копии. Одна копия активная, она отслеживает текущие изменения на блочном устройстве. Вторая копия доступна для чтения на время, пока удерживается снапшот, и содержит историю до момента снятия снапшота. Копии синхронизируются в момент снятия снапшота. После освобождения снапшота вторая копия карты не нужна, но она не освобождается, чтобы не выделять для неё память снова при следующем создании снапшота. +Есть два экземпляра карты трекера изменений. Одна карта активная, она отслеживает текущие изменения на блочном устройстве. Вторая карта доступна для чтения на время, пока удерживается снапшот и содержит историю до момента снятия снапшота. Карты синхронизируются в момент снятия снапшота. После освобождения снапшота вторая карта не используется. ### Копирование при записи + Копирование данных выполняется блоками, точнее кусками. Термин "кусок" используется, чтобы не путать его с блоками трекера изменений и блоками ввода/вывода. Кроме того, "кусок" в модуле blksnap означает примерно то же самое, что и "кусок" в модуле dm-snap. -Размер куска определяется параметрами модуля chunk_minimum_shift и chunk_maximum_count. Параметр chunk_minimum_shift ограничивает минимальный размер куска, в то время как chunk_maximum_count определяет их максимальное допустимое количество. Значения по умолчанию для этих параметров определяются объявлениями конфигурации модуля: CONFIG_BLK_SNAP_CHUNK_MINIMUM_SHIFT и CONFIG_BLK_SNAP_CHUNK_MAXIMUM_COUNT. Размер куска определяется в зависимости от размера блочного устройства в момент снятия снапшота. Размер куска должен быть степенью двойки. +Размер куска определяется параметрами модуля chunk_minimum_shift, chunk_maximum_shift и chunk_maximum_count. Параметр chunk_minimum_shift ограничивает минимальный размер куска, в то время как chunk_maximum_count определяет их максимальное допустимое количество. Размер куска определяется в зависимости от размера блочного устройства в момент снятия снапшота. Размер куска должен быть степенью двойки. Однако размер куска не должен превышать chunk_maximum_shift. Если размер куска достигает этого значения, то их количесто может превысить chunk_maximum_count. -Один кусок описывается структурой &struct chunk. Для каждого блочного устройства создаётся массив структур. Структура содержит всю необходимую информацию для копирования данных куска с оригинального блочного устройства в хранилище изменений. Эта же информация позволяет отобразить образ снапшота. В структуре расположен семафор, позволяющий обеспечить синхронизацию потоков, обращающихся к одному куску. На время, пока выполняется чтение данных куска с оригинального блочного устройства, инициировавший запрос записи поток переводится в состояние ожидания. +Один кусок описывается структурой "struct chunk". Для каждого блочного устройства создаётся массив структур. Структура содержит всю необходимую информацию для копирования данных куска с оригинального блочного устройства в хранилище изменений. Эта же информация позволяет отобразить образ снапшота. В структуре расположен семафор, позволяющий обеспечить синхронизацию потоков, обращающихся к одному куску. На время, пока выполняется чтение данных куска с оригинального блочного устройства, инициировавший запрос записи поток переводится в состояние ожидания. -Особенность фильтра блочного слоя в том, что при перехвате запроса ввода/вывода нельзя выполнять синхронные запросы ввода/вывода. Синхронные запросы могут быть причиной переполнения стека в случае рекурсивного вызова submit_bio_noacct(). Кроме того, они увеличивают риск возникновения ситуации взаимной блокировки. Поэтому перед тем как вызвать функцию обратного вызова фильтра инициализируется переменная current->bio_list, а функция submit_bio_noacct() в этом случае добавляет запрос ввода/вывода в этот связанный список. После выполнения функции обратного вызова обработки запроса из списка current->bio_list все запросы извлекаются, и для них вызывается submit_bio_noacct(). Поэтому алгоритм копирования при записи выполняется асинхронно. +У алгоритма копирования при записи есть недостаток. Так как при перезаписи хотя бы одного сектора производится копирование целого куска, возможна ситуация быстрого заполнения хранилища изменений при записи на блочное устройство данных маленькими порциями в случайном порядке. Такая ситуация возможна при сильной фрагментации данных на файловой системе. При этом производительность машины сильно деградирует и без модуля blksnap. Поэтому эта проблема не встречается на реальных серверах, хотя легко может быть создана искусственными тестами. -У блочного уровня есть и другая особенность. Если послать запрос на чтение, а вслед нему послать запрос на запись, то сначала может быть выполнена запись, а лишь затем чтение. Даже использование флагов REQ_SYNC и REQ_PREFLUSH не обеспечивает корректного поведения. +### Хранилище изменений -В силу этих особенностей текущая реализация предполагает повторое вхождение в обработчик. При первом вызове функции обратного вызова блокируется семафор куска и формируется связанный список запросов, читающих перезаписываемый кусок. После этого блочный уровень передаёт запросы на выполение и снова вызывает функцию обратного вызова фильтра. При втором вызове функции обратного вызова при попытке заблокировать семафор куска процесс блокируется до тех пор, пока не завершиться чтение данных куска с оригинальгого блочного устройства. Если же при выполнении функции обратного вызова становится ясно, что данные куска уже были скопированы, то запрос на запись просто пропускается без каких либо промедлений. +Для одного снапшота создаётся одно, общее для всех блочных устройств снапшота, хранилище изменений. +Хранилище изменений снапшота может использовать: +- файл на обычной файловой системе; +- блочное устройство; +- файл на tmpfs. -Такой алгоритм позволяет эффективно выполнять резервные копии систем с работающими на них Round Robin Database [RRD](https://www.loriotpro.com/Products/On-line_Documentation_V5/LoriotProDoc_EN/V22-RRD_Collector_RRD_Manager/V22-A1_Introduction_RRD_EN.htm). Такие базы способны несколько раз перезаписаться за время выполнения резервного копирования машины. Конечно, ценность резервной копии данных RRD-системы мониторинга можно поставить под сомнение. Однако часто стоит задача сделать резервную копию всей инфраструктуры предприятия целиком, чтобы в случае проблем восстановить или реплизировать её тоже целиком. +Файл или блочное устройство должны открываться в флагом O_EXCL, чтобы обеспечить эксклюцивный доспуп модуля к данным. -Но есть и недостаток. Так как при перезаписи хотя бы одного сектора производится копирование целого куска, возможна ситуация быстрого заполнения хранилища изменений при записи на блочное устройство данных маленькими порциями в случайном порядке. Такая ситуация возможна при сильной фрагментации данных на файловой системе. При этом производительность машины сильно деградирует и без модуля blksnap. Поэтому эта проблема не встречается на реальных серверах, хотя легко может быть создана искусственными тестами. +#### Файл на обычной файловой системе -### Хранилище изменений -Прежде чем рассмотреть, как модуль blksnap организует хранилище изменений, рассмотрим как обстоят дела в других похожих решениях. +Файл на обычной файловой системе может быть применён для большинства систем. Для файловых систем, поддерживающих fallocate(), доступно динамическое увеличение хранилища изменений. Нет необходимости выделять файл большого размера перед созданием снапшота. Модуль ядра сам увеличи его размер по мере необходимости, но в рамках заданного ограничения. +Однако файл на обычной файловой системе не может быть применён в случае, если он расположен на блочном устройстве, для которого создаётся снапшот. Это означает, что на системе должна быть файловая система, которая не участвует в резервном копировании. -BTRFS реализует снапшоты на уровне файловой системы. Если снапшот снят, то при перезаписи файлов запись выполняется в новые блоки. Старые блоки остаются и используются для образа снапшота файловой системы. Поэтому при переполнении снапшота не остаётся места для сохранения новых актуальных данных и система теряет работоспособность. +#### Блочное устройство -Device Mapper реализует поддержку снапшотов с помощью dm-snap. Он реализует логику снапшота блочного устройства. Алгоритм копирования при записи примерно такой же, как у модуля blksnap. Прежде чем перезаписать данные на оригинальном устройстве, они считываются и сохраняются в специально выделенное для этих целей блочное устройство. На практике это означает, что при снятии снапшота с нескольких блочных устройств нужно иметь несколько или одно пустое блочное устройство, выделить на нём области для каждого устройства, с которого снимается снапшот. Первая проблема в том, что в системе может не быть свободного дискового пространства для целей хранения изменений. Если же на диске есть свободное пространство, то возникает вопрос: "А достаточно ли свободного дискового пространства, и как его разделить между блочными устройствами?". Можно разделить это пространство поровну либо пропорционально размеру. Но нагрузка на разные диски, как правило, распределена неравномерно. В результате, для одного из блочных устройств происходит переполнение снапшота, в то время как для других всё зарезервированное пространство может оставаться свободным. Получается, что зарезервированное пространство используется неоптимально. +Такой вариант хранилища изменений позволяет получить максимально возможную производительность, но требует зарезервированного дискового пространства. +Эксклюзивный доступ к блочному устройству гарантирует, что на нём нет смонтированных файловых систем. Динамическое увеличение хранилища изменений в этом случае не функционирует. Хранилище ограничено одним блочным устройтсвом. -Хранилище изменений модуля blksnap не имеет перечисленных недостатков. -1. Копирование при записи в хранилище изменений сохраняет старые данные, необходимые для образов снапшотов. Поэтому при переполнении снапшота образы снапшотов становятся неконсистентными. Процесс резервного копирования прерывается, но система сохраняет свою работоспособность без потери данных. -2. Хранилище изменений общее для всех блочных устройств снапшота. Отпадает необходимость распределять область хранения изменений между блочными устройствами. -3. Хранилище изменений представляет собой пул областей дискового пространства на разных блочных устройствах. То есть нагрузка по хранению изменений может быть распределена. -4. Нет необходимости выделять сразу большое дисковое пространство перед снятием снапшота. Уже во время удержания снапшота хранилище изменений может быть расширено. +#### Файл на tmpfs -Благодаря перечисленным возможностям хранилища изменений модуля blksnap для не нужно заранее выделять свободное дисковое пространство. Достаточно иметь свободное пространство на файловой системе. Области дискового пространства можно аллоцировать с помощью fallocate(). К сожалению, не все файловые системы поддерживают этот системный вызов, но самые распространённые XFS и EXT4 его поддерживают (а также BTRFS, но требуется преобразование виртуальных смещений в физические). При удержании снапшота можно опрашивать его состояние с помощью специального ioctl. Когда свободное пространство в хранилище изменений заканчивается, модуль уведомляет об этом пользовательский процесс, который может подготовить новую область и передать её модулю для расширения хранилища изменеий. +Файл на tmpfs может быть применён в случае, если нет свободного дискового пространства и все файловые системы участвуют в резервном копировании. В этом случае, хранилище изменений располагается в виртуальной памяти, то есть в ОЗУ и файле (разделе) подкачки. Для малонагруженных систем такой вариант может быть приемлем. Высоконагруженным серверам может потребоваться раздел подкачки довольно большого размера, иначе виртуальной памяти может быть недостаточно для храниения изменений, что может привести к переполнению снапшота. ## Как этим пользоваться + +Кроме самого модуля ядра, был разработан набор сопутствующего ПО под лицензиями GPL и LGPL. +Консольный инструмент и С++-библиотека для управления модулем могут быть использованы для интеграции с другими проектами. + В зависимости от потребностей и выбранной лицензии можно выбрать разные варианты управления модулем: 1. непосредственно через ioctl; 2. используя статическую С++ библиотеку; 3. используя консольный инструмент blksnap. ### Иcпользование ioctl -Модуль ядра предоставляет заголовочный файл blksnap.h. В нём описаны все доступные ioctl и структуры для взаимодействия с модулем. Каждый ioctl и структура подробно документированы. Этого должно быть достаточно, чтобы разобраться в интерфейсе. Если же вы считаете, что чего-то не хватает, я предлагаю расширить описание в заголовочном файле. -Кроме того, библиотека libblksnap и инструмент blksnap используют этот интерфейс, поэтому в случае возникновения сложности вы можете использовать их исходный код как пример. -### Статическая С++-библиотека -Библиотека создавалась прежде всего для упрощения создания тестов на С++. Ещё это хороший пример применения ioctl-интерфейса модуля. Можно использовать её непосредственно в приложении с лицензией GPL-2+ или сделать библиотеку с лицензией LGPL-2+, с который будет динамически линковаться проприетарное приложение. +В состав ядра входят заголовочные файлы uapi/linux/blk-filter.h и uapi/linux/blksnap.h. Управление фильтром выполняется с помощью ioctl: *BLKFILTER_ATTACH*, *BLKFILTER_DETACH*, *BLKFILTER_CTL*. Они позволяют подключать фильтр к блочному устройству, отклчючать его и передавать фильтру команды управления. Кроме того, модуль blksnap создаёт файл /dev/blksnap-control. С его помощью модулю передаются команды управления снапшотами. Подробное описание интерфейса фильтра блочного устройтсва в документации ядра Documentation/block/blkfilter.rst или [онлайн](https://www.kernel.org/doc/html/latest/block/blkfilter.html). Описание интерфейса модуля blksnap в документации ядра Documentation/block/blksnap.rst или [онлайн](https://www.kernel.org/doc/html/latest/block/blksnap.html). + +### Статическая C++ библиотека + +Библиотека создавалась для упрощения создания тестов на С++. Библиотека является примером применения ioctl-интерфейса модуля. -Интерфейс библиотеки достаточно прост и интуитивен. Есть тесты, которые её применяют. Они могут являться примером применения библиотеки. Поэтому коротко опишу лишь назначение ключевых классов. -* Класс blksnap::CBlksnap (include/blksnap/Blksnap.h) является тонкой С++-обёрткой для ioctl-интерфейса модуля ядра. Вам может быть достаточно абстракции на этом уровне. -* Интерфейс blksnap::ISession (include/blksnap/Session.h). Его статический метод Create() создаёт объект снапшота и удерживает его. Снапшот освобождается при освобождении этого объекта. Объект содержит в себе реализацию алгоритма выделения новых порций для хранилища изменений и обработку событий от модуля. Для работы достаточно создать сессию и с помощью вызова GetImageDevice() получить имя блочного устройства образа снапшота. Очень подойдёт для быстрого создания прототипа приложения. -* Интерфейс blksnap::ICbt (include/blksnap/Cbt.h) позволяет получить доступ к данным трекера изменений. -* Файл include/blksnap/Service.h содержит функцию получения версии модуля ядра и может содержать прочую функциональность для отладки модуля. +#### Класс blksnap::CTracker + +Класс *blksnap::CTracker* из ([include/blksnap/Tracker.h](../include/blksnap/Tracker.h)) является тонкой С++-обёрткой для интерфейса фильтра блочного устройства, который реализован в ядре в виде ioctl: +- *BLKFILTER_ATTACH* +- *BLKFILTER_DETACH* +- *BLKFILTER_CTL*. + +Методы класса: +- *Attach* - подключает фильтр блочного устройства 'blksnap' +- *Detach* - отключает фильтр +- *CbtInfo* - предоставляет состояние трекера изменений для блочного устройства +- *ReadCbtMap* - читает таблицу изменений блочного устройства +- *MarkDirtyBlock* - задаёт 'грязные блоки' трекера изменений +- *SnapshotAdd* - добавляет блочное устройство в снапшот +- *SnapshotInfo* - позволяет получить статус снапшота блочного устройства. + +#### Класс blksnap::CSnapshot + +Класс *blksnap::CSnapshot* из ([include/blksnap/Snapshot.h](../include/blksnap/Snapshot.h)) это тонкая С++-обёртка для интерфейса упралвения модулем blknsnap. +Реализует вызовы ioctl: +- *IOCTL_BLKSNAP_VERSION* +- *IOCTL_BLKSNAP_SNAPSHOT_CREATE* +- *IOCTL_BLKSNAP_SNAPSHOT_COLLECT* +- *IOCTL_BLKSNAP_SNAPSHOT_TAKE* +- *IOCTL_BLKSNAP_SNAPSHOT_DESTROY* +- *IOCTL_BLKSNAP_SNAPSHOT_WAIT_EVENT*. + +Статические методы класса: +- *Collect* - позволяет получить список UUID всех снапшотов модуля blksnap +- *Version* - запршивает версию модуля +- *Create* - создаёт экземпляр класса *blksnap::CSnapshot*, при этом модуль создаёт снапшот, в который можно добавлять устройства +- *Open* - создаёт экземпляр класса *blksnap::CSnapshot* для существующего снапшота по его UUID. + +Методы класса: +- *Take* - снимает снапшот +- *Destroy* - уничтожает снапшот +- *WaitEvent* - позволяет получать события об изменении состояния модуля +- *Id* - запрашивает у экземпляра класса UUID снапшота. + +#### Класс blksnap::ISession + +Класс *blksnap::ISession* ([include/blksnap/Session.h](../include/blksnap/Session.h)) создаёт сессию снапшота. +Статический метод класса *Create* создаёт экземпляр класса, который создаёт снимает и удерживает санпшот. Класс содержит рабочий поток, который проверяет состояние снапшота и при получении событий сохраняет их в очередь. Метод *GetError* позволяет прочитать сообщение из этой очереди. Деструктор класса уничтожает снапшот. + +#### Класс blksnap::ICbt + +Класс *blksnap::ICbt* из ([include/blksnap/Cbt.h](../include/blksnap/Cbt.h)) позволяет получить доступ к данным трекера изменений. +Статический метод *Create* создаёт объект для взаимодействия с трекером изменений блочного устройтсва. + +Методы класса: +- *GetCbtInfo* - предоставляет информацию о текущем состоянии трекера изменений для блочного устройства +- *GetCbtData* - позволяют прочитать таблицу изменений +- *GetImage* - предоставлят имя блочного устройтсва образа снапшота +- *GetError* - позволяет проверить состояние снапшота блочного устройства. + +#### Структура blksnap::SRange + +Структура *blksnap::SRange* ([include/blksnap/Sector.h](../include/blksnap/Sector.h)) описывает область блочного устройства, объединяет смещение от начала блочного устройтсва и размер области в виде количества секторов. + +#### blksnap::Version + +Функция *blksnap::Version* из ([include/blksnap/Service.h](include/blksnap/Service.h)) позволяет получить версию модуля ядра. ### Консольный инструмент blksnap -В соответствии с парадигмой "лучшая документация — это код", инструмент содержит подробную встроенную справку. Вызов "blksnap --help" позволяет получить список команд. При запросе "blksnap \ --help" выводится описание команды. Если на ваш взгляд этой документации недостаточно, предлагаю добавить нужную информацию во встроенную справочную систему инструмента. -Если остались вопросы, как пользоваться инструментом, то в качестве примера можно использовать тесты, написанные на bash. +Инструмент содержит подробную встроенную справку. Вызов "blksnap --help" позволяет получить список команд. При запросе "blksnap \ --help" выводится описание команды. Страница [man](./blksnap.8) также может быть полезна. Пользуйтесь документацией встроенной в инструмент. + +### Регрессионные тесты + +Набор тестов позволяет проводить регрессионное тестирование модуля blksnap в составе ядра. Тесты созданы наскриптах bash и на С++. +Тесты на скриптах bash довольно просты. Они проверяют основной базовый функционал. Взаимодействие с модулем ядра осуществляется с помощью инструмента blksnap. +Доступна документация на С++ тесты: +- [boundary](./tests/boundary_ru.md) +- [corrupt](./tests/corrupt_ru.md) + +## Лицензия -## Что дальше -* Нужно обеспечить работоспособность модуля и сопутствующего ПО на разных архитектурах. На текущий момент он проверенно работает на amd64. Проверка на архитектуре arm64le была вполне успешной. На архитектуре ppc64le было зафиксировано падение системы. Тестирование на других архитектурах пока не выполнялось. -* Для консольного инструмента blksnap нужно добавить возможность возвращать данные в виде json-структуры. Это должно облегчить интеграцию с другими программами, например на python. -* Для консольного инструмента blksnap было бы здорово сделать man-страницу. +Модуль ядра, как и ядро Linux имеет лицензию GPL-2. +Консольный инструмент blksnap имеет лицензию GPL-2+. +Библиотеки имеют лицензию LGPL-3+. diff --git a/doc/blksnap_ru.rst b/doc/blksnap_ru.rst deleted file mode 100644 index c9776836..00000000 --- a/doc/blksnap_ru.rst +++ /dev/null @@ -1,253 +0,0 @@ -.. SPDX-License-Identifier: GPL-2.0 - -=============================================== -Модуль для создания снапшотов блочных устройств -=============================================== - -Введение -======== - -На первый взгляд, в идее создания снапшотов для блочных устройств нет новизны. -В ядре Linux уже есть механизмы для создания снапшотов. -В состав Device Mapper входит dm-snap, который позволяет создавать снапшоты блочных устройств. -BTRFS поддерживает снапшоты на уровне файловой системы. -Однако у обоих этих вариантов есть недостатки, которые не позволяют использовать их как универсальное средство для создания резервных копий. - -Недостатки Device Mapper: - -- На блочных устройствах должна быть LVM-разметка. - Если при установке системы логические тома не были созданы, то dm-snap не может быть применён. -- Для хранения изменений снапшота одного логического тома необходимо зарезервировать фиксированный диапазон секторов на зарезервированном пустом логическом томе. - Во-первых, требуется, чтобы в системе было достаточно пространства, не занятого файловой системой, что на реальных серверах встечается редко. - Во-вторых, как правило, необходимо создать снапшоты сразу для всех логических томов, что требует разделить между несколькими логическими томами это зарезервированное пространство. - Можно разделить это пространство поровну либо пропорционально размеру. Но нагрузка на разные диски, как правило, распределена неравномерно. - В результате, для одного из блочных устройств происходит переполнение снапшота, в то время как для других всё зарезервированное пространство может оставаться свободным. - Это усложняет управление хранилищем изменений и делает практически невозможным создание согласованного снапшота нескольких логических томов. - -Недостатки BTRFS: - -- Снапшоты создают неизменный образ именно файловой системы, а не блочного устройства. Такой снапшот применим только для файлового бэкапа. -- При синхронизации сабволюма снапшота с сабволюмом резервной копии чтение различий приводит к произвольному доступу к блочному устройству, что приводит к снижению эффективности по сравнению с прямым копированием блочного устройства. -- BTRFS позволяет получить инкрементальный бэкап [#btrfs_increment]_, но для этого необходимо удерживать на системе снапшот предыдущего цикла бэкапа, что приводит к чрезмерному потреблению дискового пространства. -- При нехватке свободного пространства на файловой системе во время удержания снапшота новые данные не могут быть сохранены, что приводит к нарушению работы сервера. - -Достоинства модуля blksnap: - -- Наличие трекера изменений. -- Снапшот на уровне блочного устройства. -- Динамическое выделение пространства для хранения изменеий. -- Устойчивость к переполнению снапшота. -- Согласованный снапшот нескольких блочных устройств. - -Более подробное описание особенностей в разделе Характеристики. - -Перечисленный набор возможностей позволяет достигнуть ключевых целей средства резервного копирования: - -- Простота и универсальность использования. -- Надёжность. -- Минимальное потребление системных ресурсов при бэкапе. -- Минимальное время восстановления или репликации системы целиком. - -Характеристики -============== - -Трекер изменений ----------------- - -Трекер изменений позволяет определить, какие блоки были изменены за время между последним созданным снапшотом и любым из предыдущих снапшотов. -Обладая картой изменений, достаточно произвести копирование только изменившихся блоков, а не перечитывать всё блочное устройтсво полностью. -Позволяет реализовывать логику как инкрементального, так и дифференциального бэкапа. -Инкрементальный бэкап критически важен для больших файловых хранилищ, размер которых может составлять сотни терабайт, а время полного резервного копирования которых может достигать более суток. -На таких серверах использование средств резервного копирования без трекера изменений становится фактически невозможным. - -Снапшот на уровне блочного устройства -------------------------------------- - -Снапшот на уровне блочного устройства позволяет упростить алгоритм резервного копирования и снизить потребление системных ресурсов. -Также он позволяет выполнять линейное чтение дискового простанства напрямую, что позволяет достигнуть максимальной скорости чтения при минимальном использовании процессорного времени. -При этом достигается универсальность создания снапшотов для любых блочных устройств вне зависимости от файловой системы, расположенной на них. -Исключением являются BTRFS, ZFS и кластерные файловые системы. - -Динамическое выделение пространства для хранения изменений ----------------------------------------------------------- - -Для хранения изменений модуль не требует заранее зарезервированного диапазона блочного устройства. -Диапазон секторов может быть выделен на любом блочном устройстве непосредственно перед созданием снапшота в отдельных файлах на файловой системе. -Кроме того, размер хранилища изменений может быть увеличен после создания моментального снимка путем добавления новых диапазонов секторов на блочных устройствах. -Диапазоны секторов могут быть распределены на любых блочных устройствах системы, включая те, на которых был создан снапшот. -Общее хранилище изменений для всех образов блочных устройств снапшота позволяет оптимизировать использование дискового пространства. - -Устойчивость к переполнению снапшота ------------------------------------- - -Для создания образов снапшотов блочных устройств модуль хранит блоки оригинального блочного устройства, которые были изменены с момента снятия снапшота. -Для этого модуль перехватывает запросы на запись и считывает блоки, которые должны быть перезаписаны. -Такой алгоритм гарантирует сохранность данных оригинального блочного устройства в случае переполнения снапшота, и даже в случае непредсказуемых критических ошибкок. -Если возникает проблема во время резервного копирования, хранилище изменений освобождается, снапшот закрывается, резервная копия не создаётся, но сервер продолжает работать. - -Согласованный снапшот нескольких блочных устройств --------------------------------------------------- - -Снапшот создаётся одновременно для всех блочных устройств, для которых создаётся резервная копия, обеспечивая их согласованное состояние. - - -Алгоритмы -========= - -Обзор ------ - -Модуль blksnap является фильтром блочного уровня. Он перехватывает все запросы на запись. -Подключение фильтра к блочному устройству выполняется при первом создании снапшота. -Трекер изменений отмечает все перезаписанные блоки. -Информация об истории изменений на блочном устройстве доступна во время удержания снапшота. -Модуль выполняет чтение блоков, которые должны быть перезаписаны, и сохраняет их в хранилище изменений. -При чтении из образа снапшота чтение выполняется либо из оригинального устройства, либо из хранилища изменений. - -Трекер изменений ----------------- - -Для каждого блочного устройства создаётся карта трекера изменений. -Один байт этой карты соответствует одному блоку. -Размер блока задаётся параметрами модуля ``tracking_block_minimum_shift`` и ``tracking_block_maximum_count``. -Параметр ``tracking_block_minimum_shift`` ограничивает минимальный размер блока для трекинга, в то время как ``tracking_block_maximum_count`` определяет их максимальное допустимое количество. -Размер блока трекера изменений определяется в зависимости от размера блочного устройства при добавлении устройства под трекинг, то есть при первом снятии снапшота. -Размер блока должен быть степенью двойки. - -Байт карты изменений хранит число от 0 до 255. Это номер снапшота, с момента создания которого были изменения в блоке. -При каждом создании снапшота номер текущего снапшота увеличивается на единицу. -Этот номер записывается в ячейку карты изменений при записи в блок. -Таким образом, зная номер одного из предыдущих снапшотов и номер последнего снапшота, можно определить по карте изменений, какие блоки были изменены. -Когда номер текущего изменения достигает максимального допустимого значения для карты в 255, при создании следующего снапшота карта изменений обнуляется, а номеру текущего снапшота присваивается значение 1. -Трекер изменений сбрасывается, и генерируется новый UUID — уникальный идентификатор поколения снапшотов. -Идентификатор поколения снапшотов позволяет выявлять, что был выполнен сброс трекинга изменений. - -У карты изменений есть две копии. Одна копия активная, она отслеживает текущие изменения на блочном устройстве. -Вторая копия доступна для чтения на время, пока удерживается снапшот, и содержит историю до момента снятия снапшота. -Копии синхронизируются в момент создания снапшота. -После освобождения снапшота вторая копия карты не нужна, но она не освобождается, чтобы не выделять для неё память снова при следующем создании снапшота. - -Копирование при записи ----------------------- - -Копирование данных выполняется блоками, точнее кусками. Термин "кусок" используется, чтобы не путать его с блоками трекера изменений и блоками ввода/вывода. -Кроме того, "кусок" в модуле blksnap означает примерно то же самое, что и "кусок" в модуле dm-snap. - -Размер куска определяется параметрами модуля ``chunk_minimum_shift`` и ``chunk_maximum_count``. -Параметр ``chunk_minimum_shift`` ограничивает минимальный размер куска, в то время как ``chunk_maximum_count`` определяет их максимальное допустимое количество. -Размер куска определяется в зависимости от размера блочного устройства в момент снятия снапшота. Размер куска должен быть степенью двойки. -Один кусок описывается структурой ``struct chunk``. Для каждого блочного устройства создаётся массив структур. -Структура содержит всю необходимую информацию для копирования данных куска с оригинального блочного устройства в хранилище изменений. -Эта же информация позволяет отобразить образ снапшота. В структуре расположен семафор, позволяющий обеспечить синхронизацию потоков, обращающихся к одному куску. - -У блочного уровня есть особенность. Если послать запрос на чтение, а вслед нему послать запрос на запись, то сначала может быть выполнена запись, а лишь затем чтение. -Поэтому алгоритм копирования при записи выполняется синхронно. -При перехвате запроса на запись выполнение этого запроса будет отложено до тех пор, пока не будут скопированы в хранилище изменений подверженные перезаписи куски. -Но если при перехвате запроса на запись оказывается, что записываемый диапазон секторов уже был скопирован в хранилище изменений, то запрос просто пропускается. - -Такой алгоритм позволяет эффективно выполнять резервные копии систем с работающими на них Round Robin Database. -Такие базы способны несколько раз перезаписаться за время выполнения резервного копирования системы. -Конечно, ценность резервной копии данных RRD-системы мониторинга можно поставить под сомнение. -Однако часто стоит задача сделать резервную копию всей инфраструктуры предприятия целиком, чтобы в случае проблем восстановить или реплицировать её тоже целиком. - -Но есть и недостаток. Так как при перезаписи хотя бы одного сектора производится копирование целого куска, возможна ситуация быстрого заполнения хранилища изменений при записи на блочное устройство данных маленькими порциями в случайном порядке. -Такая ситуация возможна при сильной фрагментации данных на файловой системе. -Но надо учитывать, что при такой фрагментации данных производительность систем, как правило, сильно деградирует. -Поэтому эта проблема не встречается на реальных серверах, хотя легко может быть создана искусственными тестами. - -Хранилище изменений -------------------- - -Хранилище изменений представляет собой пул областей дискового пространства и является общим для всех блочных устройств снапшота. -Поэтому нет необходимость распределять область хранения изменений между блочными устройствами, а само хранилище изменений может быть расположено на разных блочных устройствах. - -Нет необходимости выделять сразу большое дисковое пространство перед снятием снапшота. -Уже во время удержания снапшота хранилище изменений может быть расширено. -Достаточно иметь свободное пространство на файловой системе. - -Области дискового пространства можно аллоцировать на файловой системе с помощью fallocate(), а запросить расположение файла можно с помощью Fiemap Ioctl или Fibmap Ioctl. -К сожалению, не все файловые системы поддерживают работу этих механизмов, но самые распространённые XFS, EXT4 и BTRFS его поддерживают. -Для BTRFS требуется дополнительное преобразование виртуальных смещений в физические. - -При удержании снапшота пользовательский процесс может опрашивать состояние модуля. -Когда свободное пространство в хранилище изменений уменьшается до порогового значения, модуль генерирует событие об этом. -Пользовательский процесс может подготовить новую область и передать её модулю для расширения хранилища изменений. -Пороговое значение определяется как половина от значения параметра модуля ``diff_storage_minimum``. - -Если свободное пространство в хранилище изменений заканчивается, то генерируется событие о переполнении снапшота. -Такой снапшот считается повреждённым, а запросы на чтение к образам снапшотов будут завершаться с кодом ошибки. -Хранилище изменений сохраняет устаревшие данные, необходимые для образов снапшотов, поэтому при переполнении снапшота процесс резервного копирования прерывается, но система сохраняет свою работоспособность без потери данных. - -Как этим пользоваться -===================== - -В зависимости от потребностей и выбранной лицензии можно выбрать разные варианты управления модулем: - -- Используя ioctl напрямую. -- Используя статическую С++ библиотеку. -- Используя консольный инструмент blksnap. - -Иcпользование ioctl -------------------- - -Модуль предоставляет заголовочный файл ``include/uapi/blksnap.h``. -В нём описаны все доступные ioctl и структуры для взаимодействия с модулем. -Каждый ioctl и структура подробно документированы. -Общий алгоритм вызова управляющих запросов примерно следующий: - -1. ``blk_snap_ioctl_snapshot_create`` инициирует процесс создания снапшота. -2. ``blk_snap_ioctl_snapshot_append_storage`` позволяет добавить первый диапазон блоков для храниения изменений. -3. ``blk_snap_ioctl_snapshot_take`` создаёт блочные устройства образов снапшотов блочных устройств. -4. ``blk_snap_ioctl_snapshot_collect`` и ``blk_snap_ioctl_snapshot_collect_images`` позволяют сопоставить оригинальные блочные устройтсва и соответсвующие им образы снапшотов. -5. Выполняется чтение образов снапшотов с блочных устройтсв, номера которых были получены при вызове ``blk_snap_ioctl_snapshot_collect_images``. Образы снапшотов поддерживают и операцию записи, поэтому перед резервным копированием можно монтировать файловую систему на образе снапшота и проводить необходимый препроцессинг. -6. ``blk_snap_ioctl_tracker_collect`` и ``blk_snap_ioctl_tracker_read_cbt_map`` позволяют получить данные трекера изменений. Если в образ снапшота производилась запись, то трекер изменений учитывает это. Поэтому получать данные трекера необходимо после того, как операции записи были завершены. -7. ``blk_snap_ioctl_snapshot_wait_event`` позволяет отслеживать состояние снапшотов и получать события о требовании расширения хранилища изменений или переполнении снапшота. -8. Расширение хранилища изменений производится с помощью ``blk_snap_ioctl_snapshot_append_storage``. -9. ``blk_snap_ioctl_snapshot_destroy`` освобождает снапшот. -10. Если после создания резервной копии с данными выполняется постпроцессинг, изменяющий блоки резервной копии, неободимо помечать такие блоки как грязные в таблице трекера изменений. Для этого может быть использован ``blk_snap_ioctl_tracker_mark_dirty_blocks``. -11. Есть возможность отключить трекер изменений от какого-либо блочного устройства с помощью ``blk_snap_ioctl_tracker_remove``. - - -Статическая С++ библиотека --------------------------- - -Библиотека [#userspace_libs]_ создавалась прежде всего для упрощения создания тестов на С++, а также это хороший пример применения интерфейса модуля. -При создании приложений прямое использование управляющих вызовов предпочтительнее. -Однако её можно использовать в приложении с лицензией GPL-2+, либо может быть создана библиотека с лицензией LGPL-2+, с который сможет динамически линковаться даже проприетарное приложение. - -Консольный инструмент blksnap ------------------------------ - -Консольный инструмент blksnap [#userspace_tools]_ позволяет управлять модулем из командной строки. -Инструмент содержит подробную встроенную помощь. -Со списком команд можно ознакомиться, введя команду ``blksnap --help``. Команда -``blksnap --help`` позволит получить подробную информацию о параметрах вызова каждой команды. -Этот вариант может быть удобен при создании проприетарного программного обеспечения, так как позволяет не компилироваться с открытым кодом. -В тоже время с помощью инструмента blksnap могут быть созданы скрипты для выполнения резервных копий. -Например, может быть вызван rsync для синхронизации файлов на файловой системе смонтированного образа снапшота и файлов в архиве на файловой системе, поддерживающей сжатие. - -Тесты ------ - -Для проведения регрессионного тестирования был создан набор тестов [#userspace_tests]_. -На bash написаны тесты с простыми алгоритмами, которые используют консольный инструмент ``blksnap`` для управления модулем. -Более сложные алгоритмы тестирования реализованы на С++. -Документацию [#userspace_tests_doc]_ о них можно найти на репозитории проекта. - -Ссылки -====== - -.. [#btrfs_increment] https://btrfs.wiki.kernel.org/index.php/Incremental_Backup - -.. [#userspace_tools] https://github.com/veeam/blksnap/tree/master/tools/blksnap - -.. [#userspace_libs] https://github.com/veeam/blksnap/tree/master/lib/blksnap - -.. [#userspace_tests] https://github.com/veeam/blksnap/tree/master/tests - -.. [#userspace_tests_doc] https://github.com/veeam/blksnap/tree/master/doc - -Описание интерфейса модуля -========================== - -.. kernel-doc:: include/uapi/linux/blksnap.h diff --git a/doc/specification.md b/doc/specification.md deleted file mode 100644 index a62981fe..00000000 --- a/doc/specification.md +++ /dev/null @@ -1,22 +0,0 @@ -This project should implement the task of creating snapshots of a block -device for an OS based on the Linux kernel for backup purposes. -On the kernel side, the blksnap kernel module should be used. - -This project should implement: -1. A library in C for managing the blksnap kernel module -2. A console program for managing snapshots and other features of the - blksnap module -3. Regression tests for checking the main execution branches of the - console application, the library and the kernel module -4. Scripts for building packages for deb and rpm -5. Documentation - -The blksnap kernel module should provide the following features: -1. Create snapshots of any block devices of the Linux kernel -2. Create snapshots for several block devices simultaneously -3. Track changes on the block devices during the time between the creation - of snapshots and provide the user level with a map of these changes -4. Ensure data integrity for the block device even in case of critical - errors in the operation of the library and the blksnap kernel module -5. Allow to use any disk space to store snapshot changes and - dynamically expand it while holding snapshots of block devices diff --git a/include/blksnap/Blksnap.h b/include/blksnap/Blksnap.h deleted file mode 100644 index b1033cb5..00000000 --- a/include/blksnap/Blksnap.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2022 Veeam Software Group GmbH - * - * This file is part of libblksnap - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation, either version 3 of the License, or (at your option) any - * later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Lesser Public License for more - * details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ -#pragma once -/* - * The low-level abstraction over ioctl for the blksnap kernel module. - * Allows to interact with the module with minimal overhead and maximum - * flexibility. Uses structures that are directly passed to the kernel module. - */ - -#include -#include -#include - -#ifndef BLK_SNAP_MODIFICATION -/* Allow to use additional IOCTL from module modification */ -# define BLK_SNAP_MODIFICATION -/* Allow to get any sector state. Can be used only for debug purpose */ -# define BLK_SNAP_DEBUG_SECTOR_STATE -#endif -#include "Sector.h" -#include "blksnap.h" - -namespace blksnap -{ - struct SBlksnapEventLowFreeSpace - { - unsigned long long requestedSectors; - }; - - struct SBlksnapEventCorrupted - { - struct blk_snap_dev origDevId; - int errorCode; - }; - - struct SBlksnapEvent - { - unsigned int code; - long long time; - union - { - SBlksnapEventLowFreeSpace lowFreeSpace; - SBlksnapEventCorrupted corrupted; - }; - }; - - class CBlksnap - { - public: - CBlksnap(); - ~CBlksnap(); - - void Version(struct blk_snap_version& version); - void CollectTrackers(std::vector& cbtInfoVector); - void ReadCbtMap(struct blk_snap_dev dev_id, unsigned int offset, unsigned int length, uint8_t* buff); - - void Create(const std::vector& devices, uuid_t& id); - void Destroy(const uuid_t& id); - void Collect(const uuid_t& id, std::vector& images); - void AppendDiffStorage(const uuid_t& id, const struct blk_snap_dev& dev_id, - const std::vector& ranges); - void Take(const uuid_t& id); - bool WaitEvent(const uuid_t& id, unsigned int timeoutMs, SBlksnapEvent& ev); - -#ifdef BLK_SNAP_MODIFICATION - /* Additional functional */ - bool Modification(struct blk_snap_mod& mod); -# ifdef BLK_SNAP_DEBUG_SECTOR_STATE - void GetSectorState(struct blk_snap_dev image_dev_id, off_t offset, struct blk_snap_sector_state& state); -# endif -#endif - private: - int m_fd; - }; - -} diff --git a/include/blksnap/Cbt.h b/include/blksnap/Cbt.h index a54a2198..c137b6af 100644 --- a/include/blksnap/Cbt.h +++ b/include/blksnap/Cbt.h @@ -29,13 +29,12 @@ namespace blksnap { struct SCbtInfo { - SCbtInfo(){}; - SCbtInfo(const unsigned int inOriginalMajor, const unsigned int inOriginalMinor, const uint32_t inBlockSize, - const uint32_t inBlockCount, const uint64_t inDeviceCapacity, const uuid_t& inGenerationId, + SCbtInfo() + {}; + SCbtInfo(const uint32_t inBlockSize, const uint32_t inBlockCount, + const uint64_t inDeviceCapacity, const uuid_t& inGenerationId, const uint8_t inSnapNumber) - : originalMajor(inOriginalMajor) - , originalMinor(inOriginalMinor) - , blockSize(inBlockSize) + : blockSize(inBlockSize) , blockCount(inBlockCount) , deviceCapacity(inDeviceCapacity) , snapNumber(inSnapNumber) @@ -44,8 +43,6 @@ namespace blksnap }; ~SCbtInfo(){}; - unsigned int originalMajor; - unsigned int originalMinor; unsigned int blockSize; unsigned int blockCount; unsigned long long deviceCapacity; @@ -66,12 +63,14 @@ namespace blksnap struct ICbt { - virtual ~ICbt(){}; + virtual ~ICbt() = default; - virtual std::shared_ptr GetCbtInfo(const std::string& original) = 0; - virtual std::shared_ptr GetCbtData(const std::shared_ptr& ptrCbtInfo) = 0; + virtual std::string GetImage() = 0; + virtual int GetError() = 0; + virtual std::shared_ptr GetCbtInfo() = 0; + virtual std::shared_ptr GetCbtData() = 0; - static std::shared_ptr Create(); + static std::shared_ptr Create(const std::string& original); }; } diff --git a/include/blksnap/Service.h b/include/blksnap/Service.h index 76b28f11..24cd96c4 100644 --- a/include/blksnap/Service.h +++ b/include/blksnap/Service.h @@ -26,14 +26,4 @@ namespace blksnap { std::string Version(); - - struct SectorState - { - uint8_t snapNumberPrevious; - uint8_t snapNumberCurrent; - unsigned int chunkState; - }; - - void GetSectorState(const std::string& image, off_t offset, SectorState& state); - } diff --git a/include/blksnap/Session.h b/include/blksnap/Session.h index aaa4bd95..4d44b1d0 100644 --- a/include/blksnap/Session.h +++ b/include/blksnap/Session.h @@ -30,17 +30,14 @@ namespace blksnap { struct ISession { - virtual ~ISession(){}; + virtual ~ISession() = default; - virtual std::string GetImageDevice(const std::string& original) = 0; - virtual std::string GetOriginalDevice(const std::string& image) = 0; virtual bool GetError(std::string& errorMessage) = 0; - // TODO: add limits - static std::shared_ptr Create(const std::vector& devices, - const std::string& diffStorage); - static std::shared_ptr Create(const std::vector& devices, - const SStorageRanges& diffStorageRanges); + static std::shared_ptr Create( + const std::vector& devices, + const std::string& diffStorageFilePath, + const unsigned long long limit); }; } diff --git a/include/blksnap/Snapshot.h b/include/blksnap/Snapshot.h new file mode 100644 index 00000000..b0b809d2 --- /dev/null +++ b/include/blksnap/Snapshot.h @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2022 Veeam Software Group GmbH + * + * This file is part of libblksnap + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Lesser Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +#pragma once +/* + * The low-level abstraction over ioctl for the blksnap kernel module. + * Allows to interact with the module with minimal overhead and maximum + * flexibility. Uses structures that are directly passed to the kernel module. + */ + +#include +#include +#include +#include +#include + +#include "Sector.h" +#include +#include + +namespace blksnap +{ + struct SBlksnapEventCorrupted + { + unsigned int origDevIdMj; + unsigned int origDevIdMn; + int errorCode; + }; + + struct SBlksnapEvent + { + unsigned int code; + long long time; + SBlksnapEventCorrupted corrupted; + }; + + class CSnapshotId + { + public: + CSnapshotId() + { + uuid_clear(m_id); + }; + CSnapshotId(const uuid_t& id) + { + uuid_copy(m_id, id); + }; + CSnapshotId(const __u8 buf[16]) + { + memcpy(m_id, buf, sizeof(uuid_t)); + }; + CSnapshotId(const std::string& idStr) + { + uuid_parse(idStr.c_str(), m_id); + }; + + void FromString(const std::string& idStr) + { + uuid_parse(idStr.c_str(), m_id); + }; + const uuid_t& Get() const + { + return m_id; + }; + std::string ToString() const + { + char idStr[64]; + + uuid_unparse(m_id, idStr); + + return std::string(idStr); + }; + private: + uuid_t m_id; + }; + + class OpenFileHolder + { + public: + OpenFileHolder(const std::string& filename, int flags, int mode = 0); + ~OpenFileHolder(); + int Get(); + + private: + int m_fd; + }; + + class CSnapshot + { + public: + static void Collect(std::vector& ids); + static void Version(struct blksnap_version& version); + static std::shared_ptr Create(const std::string& filePath, const unsigned long long limit); + static std::shared_ptr Open(const CSnapshotId& id); + + public: + virtual ~CSnapshot() {}; + + void Take(); + void Destroy(); + bool WaitEvent(unsigned int timeoutMs, SBlksnapEvent& ev); + + const uuid_t& Id() const + { + return m_id.Get(); + } + private: + CSnapshot(const CSnapshotId& id, const std::shared_ptr& ctl); + + CSnapshotId m_id; + std::shared_ptr m_ctl; + }; + +} diff --git a/include/blksnap/Tracker.h b/include/blksnap/Tracker.h new file mode 100644 index 00000000..7b8a22a2 --- /dev/null +++ b/include/blksnap/Tracker.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2022 Veeam Software Group GmbH + * + * This file is part of libblksnap + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Lesser Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +#pragma once +/* + * The low-level abstraction over ioctl for the blksnap kernel module. + * Allows to interact with the module with minimal overhead and maximum + * flexibility. Uses structures that are directly passed to the kernel module. + */ + +#include +#include +#include +#include + +#include "Sector.h" +#include +#include + +namespace blksnap +{ + class CTracker + { + public: + CTracker(const std::string& devicePath); + ~CTracker(); + + bool Attach(); + void Detach(); + + void CbtInfo(struct blksnap_cbtinfo& cbtInfo); + void ReadCbtMap(unsigned int offset, unsigned int length, uint8_t* buff); + void MarkDirtyBlock(std::vector& ranges); + void SnapshotAdd(const uuid_t& id); + void SnapshotInfo(struct blksnap_snapshotinfo& snapshotinfo); + + private: + int m_fd; + }; + +} diff --git a/include/blksnap/blksnap.h b/include/blksnap/blksnap.h deleted file mode 100644 index 596b282c..00000000 --- a/include/blksnap/blksnap.h +++ /dev/null @@ -1,687 +0,0 @@ -/* - * Copyright (C) 2022 Veeam Software Group GmbH - * - * This file is part of libblksnap - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation, either version 3 of the License, or (at your option) any - * later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Lesser Public License for more - * details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ -#ifndef _UAPI_LINUX_BLK_SNAP_H -#define _UAPI_LINUX_BLK_SNAP_H - -#include - -#define BLK_SNAP_CTL "blksnap-control" -#define BLK_SNAP_IMAGE_NAME "blksnap-image" -#define BLK_SNAP 'V' - -#ifdef BLK_SNAP_MODIFICATION -#define IOCTL_MOD 32 -#endif - -enum blk_snap_ioctl { - /* - * Service controls - */ - blk_snap_ioctl_version, - /* - * Change tracking controls - */ - blk_snap_ioctl_tracker_remove, - blk_snap_ioctl_tracker_collect, - blk_snap_ioctl_tracker_read_cbt_map, - blk_snap_ioctl_tracker_mark_dirty_blocks, - /* - * Snapshot controls - */ - blk_snap_ioctl_snapshot_create, - blk_snap_ioctl_snapshot_destroy, - blk_snap_ioctl_snapshot_append_storage, - blk_snap_ioctl_snapshot_take, - blk_snap_ioctl_snapshot_collect, - blk_snap_ioctl_snapshot_collect_images, - blk_snap_ioctl_snapshot_wait_event, -#ifdef BLK_SNAP_MODIFICATION - /* - * Additional controls for any standalone modification - */ - blk_snap_ioctl_mod = IOCTL_MOD, - blk_snap_ioctl_setlog, - blk_snap_ioctl_get_sector_state, - blk_snap_ioctl_end_mod -#endif -}; - -/** - * DOC: Service controls - */ - -/** - * struct blk_snap_version - Module version. - * @major: - * Version major part. - * @minor: - * Version minor part. - * @revision: - * Revision number. - * @build: - * Build number. Should be zero. - */ -struct blk_snap_version { - __u16 major; - __u16 minor; - __u16 revision; - __u16 build; -}; - -/** - * define IOCTL_BLK_SNAP_VERSION - Get module version. - * - * The version may increase when the API changes. But linking the user space - * behavior to the version code does not seem to be a good idea. - * To ensure backward compatibility, API changes should be made by adding new - * ioctl without changing the behavior of existing ones. The version should be - * used for logs. - * - * Return: 0 if succeeded, negative errno otherwise. - */ -#define IOCTL_BLK_SNAP_VERSION \ - _IOW(BLK_SNAP, blk_snap_ioctl_version, struct blk_snap_version) - -#ifdef BLK_SNAP_MODIFICATION - -enum blk_snap_compat_flags { - blk_snap_compat_flag_debug_sector_state, - blk_snap_compat_flag_setlog, - /* - * Reserved for new features - */ - blk_snap_compat_flags_end -}; -static_assert(blk_snap_compat_flags_end <= 64, - "There are too many compatibility flags."); - -#define BLK_SNAP_MOD_NAME_LIMIT 32 - -/** - * struct blk_snap_modification - Result for &IOCTL_BLK_SNAP_VERSION control. - * - * @compatibility_flags: - * [TBD] Reserved for new modification specific features. - * @name: - * Name of modification of the module blksnap (fork name, for example). - * It's should be empty string for upstream module. - */ -struct blk_snap_mod { - __u64 compatibility_flags; - __u8 name[BLK_SNAP_MOD_NAME_LIMIT]; -}; - -/** - * IOCTL_BLK_SNAP_MOD - Get modification name and compatibility flags. - * - * Linking the product behavior to the version code does not seem to me a very - * good idea. However, such an ioctl is good for checking that the module has - * loaded and is responding to requests. - * - * The compatibility flags allows to safely extend the functionality of the - * module. When the blk_snap kernel module receives new ioctl it will be - * enough to add a bit. - * - * The name of the modification can be used by the authors of forks and branches - * of the original module. The module in upstream have not any modifications. - */ -#define IOCTL_BLK_SNAP_MOD \ - _IOW(BLK_SNAP, blk_snap_ioctl_mod, struct blk_snap_mod) - -#endif - -/* - * The main functionality of the module is change block tracking (CBT). - * Next, a number of ioctls will describe the interface for the CBT mechanism. - */ - -/** - * DOC: Interface for the change tracking mechanism - */ - -/** - * struct blk_snap_dev - Block device ID. - * @mj: - * Device ID major part. - * @mn: - * Device ID minor part. - * - * In user space and in kernel space, block devices are encoded differently. - * We need to enter our own type to guarantee the correct transmission of the - * major and minor parts. - */ -struct blk_snap_dev { - __u32 mj; - __u32 mn; -}; - -/** - * struct blk_snap_tracker_remove - Input argument for the - * &IOCTL_BLK_SNAP_TRACKER_REMOVE control. - * @dev_id: - * Device ID. - */ -struct blk_snap_tracker_remove { - struct blk_snap_dev dev_id; -}; - -/** - * define IOCTL_BLK_SNAP_TRACKER_REMOVE - Remove a device from tracking. - * - * Removes the device from tracking changes. Adding a device for tracking is - * performed when creating a snapshot that includes this block device. - * - * Return: 0 if succeeded, negative errno otherwise. - */ -#define IOCTL_BLK_SNAP_TRACKER_REMOVE \ - _IOW(BLK_SNAP, blk_snap_ioctl_tracker_remove, \ - struct blk_snap_tracker_remove) - -/** - * struct blk_snap_uuid - Unique 16-byte identifier. - * @b: - * An array of 16 bytes. - */ -struct blk_snap_uuid { - __u8 b[16]; -}; - -/** - * struct blk_snap_cbt_info - Information about change tracking for a block - * device. - * @dev_id: - * Device ID. - * @blk_size: - * Block size in bytes. - * @device_capacity: - * Device capacity in bytes. - * @blk_count: - * Number of blocks. - * @generation_id: - * Unique identifier of change tracking generation. - * @snap_number: - * Current changes number. - */ -struct blk_snap_cbt_info { - struct blk_snap_dev dev_id; - __u32 blk_size; - __u64 device_capacity; - __u32 blk_count; - struct blk_snap_uuid generation_id; - __u8 snap_number; -}; - -/** - * struct blk_snap_tracker_collect - Argument for the - * &IOCTL_BLK_SNAP_TRACKER_COLLECT control. - * @count: - * Size of &blk_snap_tracker_collect.cbt_info_array. - * @cbt_info_array: - * Pointer to the array for output. - */ -struct blk_snap_tracker_collect { - __u32 count; - struct blk_snap_cbt_info *cbt_info_array; -}; - -/** - * define IOCTL_BLK_SNAP_TRACKER_COLLECT - Collect all tracked devices. - * - * Getting information about all devices under tracking. - * - * If in &blk_snap_tracker_collect.count is less than required to - * store the &blk_snap_tracker_collect.cbt_info_array, the array is not filled, - * and the ioctl returns the required count for - * &blk_snap_tracker_collect.cbt_info_array. - * - * So, it is recommended to call the ioctl twice. The first call with an null - * pointer &blk_snap_tracker_collect.cbt_info_array and a zero value in - * &blk_snap_tracker_collect.count. It will set the required array size in - * &blk_snap_tracker_collect.count. The second call with a pointer - * &blk_snap_tracker_collect.cbt_info_array to an array of the required size - * will allow to get information about the tracked block devices. - * - * Return: 0 if succeeded, negative errno otherwise. - */ -#define IOCTL_BLK_SNAP_TRACKER_COLLECT \ - _IOW(BLK_SNAP, blk_snap_ioctl_tracker_collect, \ - struct blk_snap_tracker_collect) - -/** - * struct blk_snap_tracker_read_cbt_bitmap - Argument for the - * &IOCTL_BLK_SNAP_TRACKER_READ_CBT_MAP control. - * @dev_id: - * Device ID. - * @offset: - * Offset from the beginning of the CBT bitmap in bytes. - * @length: - * Size of @buff in bytes. - * @buff: - * Pointer to the buffer for output. - */ -struct blk_snap_tracker_read_cbt_bitmap { - struct blk_snap_dev dev_id; - __u32 offset; - __u32 length; - __u8 *buff; -}; - -/** - * define IOCTL_BLK_SNAP_TRACKER_READ_CBT_MAP - Read the CBT map. - * - * Allows to read the table of changes. - * - * The size of the table can be quite large. Thus, the table is read in a loop, - * in each cycle of which the next offset is set to - * &blk_snap_tracker_read_cbt_bitmap.offset. - * - * Return: a count of bytes read if succeeded, negative errno otherwise. - */ -#define IOCTL_BLK_SNAP_TRACKER_READ_CBT_MAP \ - _IOR(BLK_SNAP, blk_snap_ioctl_tracker_read_cbt_map, \ - struct blk_snap_tracker_read_cbt_bitmap) - -/** - * struct blk_snap_block_range - Element of array for - * &struct blk_snap_tracker_mark_dirty_blocks. - * @sector_offset: - * Offset from the beginning of the disk in sectors. - * @sector_count: - * Number of sectors. - */ -struct blk_snap_block_range { - __u64 sector_offset; - __u64 sector_count; -}; - -/** - * struct blk_snap_tracker_mark_dirty_blocks - Argument for the - * &IOCTL_BLK_SNAP_TRACKER_MARK_DIRTY_BLOCKS control. - * @dev_id: - * Device ID. - * @count: - * Size of @dirty_blocks_array in the number of - * &struct blk_snap_block_range. - * @dirty_blocks_array: - * Pointer to the array of &struct blk_snap_block_range. - */ -struct blk_snap_tracker_mark_dirty_blocks { - struct blk_snap_dev dev_id; - __u32 count; - struct blk_snap_block_range *dirty_blocks_array; -}; - -/** - * define IOCTL_BLK_SNAP_TRACKER_MARK_DIRTY_BLOCKS - Set dirty blocks in the - * CBT map. - * - * There are cases when some blocks need to be marked as changed. - * This ioctl allows to do this. - * - * Return: 0 if succeeded, negative errno otherwise. - */ -#define IOCTL_BLK_SNAP_TRACKER_MARK_DIRTY_BLOCKS \ - _IOR(BLK_SNAP, blk_snap_ioctl_tracker_mark_dirty_blocks, \ - struct blk_snap_tracker_mark_dirty_blocks) - -/** - * DOC: Interface for managing snapshots - */ - -/** - * struct blk_snap_snapshot_create - Argument for the - * &IOCTL_BLK_SNAP_SNAPSHOT_CREATE control. - * @count: - * Size of @dev_id_array in the number of &struct blk_snap_dev. - * @dev_id_array: - * Pointer to the array of &struct blk_snap_dev. - * @id: - * Return ID of the created snapshot. - */ -struct blk_snap_snapshot_create { - __u32 count; - struct blk_snap_dev *dev_id_array; - struct blk_snap_uuid id; -}; - -/** - * define IOCTL_BLK_SNAP_SNAPSHOT_CREATE - Create snapshot. - * - * Creates a snapshot structure in the memory and allocates an identifier for - * it. Further interaction with the snapshot is possible by this identifier. - * A snapshot is created for several block devices at once. - * Several snapshots can be created at the same time, but with the condition - * that one block device can only be included in one snapshot. - * - * Return: 0 if succeeded, negative errno otherwise. - */ -#define IOCTL_BLK_SNAP_SNAPSHOT_CREATE \ - _IOW(BLK_SNAP, blk_snap_ioctl_snapshot_create, \ - struct blk_snap_snapshot_create) - -/** - * struct blk_snap_snapshot_destroy - Argument for the - * &IOCTL_BLK_SNAP_SNAPSHOT_DESTROY control. - * @id: - * Snapshot ID. - */ -struct blk_snap_snapshot_destroy { - struct blk_snap_uuid id; -}; - -/** - * define IOCTL_BLK_SNAP_SNAPSHOT_DESTROY - Release and destroy the snapshot. - * - * Destroys snapshot with &blk_snap_snapshot_destroy.id. This leads to the - * deletion of all block device images of the snapshot. The difference storage - * is being released. But the change tracker keeps tracking. - * - * Return: 0 if succeeded, negative errno otherwise. - */ -#define IOCTL_BLK_SNAP_SNAPSHOT_DESTROY \ - _IOR(BLK_SNAP, blk_snap_ioctl_snapshot_destroy, \ - struct blk_snap_snapshot_destroy) - -/** - * struct blk_snap_snapshot_append_storage - Argument for the - * &IOCTL_BLK_SNAP_SNAPSHOT_APPEND_STORAGE control. - * @id: - * Snapshot ID. - * @dev_id: - * Device ID. - * @count: - * Size of @ranges in the number of &struct blk_snap_block_range. - * @ranges: - * Pointer to the array of &struct blk_snap_block_range. - */ -struct blk_snap_snapshot_append_storage { - struct blk_snap_uuid id; - struct blk_snap_dev dev_id; - __u32 count; - struct blk_snap_block_range *ranges; -}; - -/** - * define IOCTL_BLK_SNAP_SNAPSHOT_APPEND_STORAGE - Append storage to the - * difference storage of the snapshot. - * - * The snapshot difference storage can be set either before or after creating - * the snapshot images. This allows to dynamically expand the difference - * storage while holding the snapshot. - * - * Return: 0 if succeeded, negative errno otherwise. - */ -#define IOCTL_BLK_SNAP_SNAPSHOT_APPEND_STORAGE \ - _IOW(BLK_SNAP, blk_snap_ioctl_snapshot_append_storage, \ - struct blk_snap_snapshot_append_storage) - -/** - * struct blk_snap_snapshot_take - Argument for the - * &IOCTL_BLK_SNAP_SNAPSHOT_TAKE control. - * @id: - * Snapshot ID. - */ -struct blk_snap_snapshot_take { - struct blk_snap_uuid id; -}; - -/** - * define IOCTL_BLK_SNAP_SNAPSHOT_TAKE - Take snapshot. - * - * Creates snapshot images of block devices and switches change trackers tables. - * The snapshot must be created before this call, and the areas of block - * devices should be added to the difference storage. - * - * Return: 0 if succeeded, negative errno otherwise. - */ -#define IOCTL_BLK_SNAP_SNAPSHOT_TAKE \ - _IOR(BLK_SNAP, blk_snap_ioctl_snapshot_take, \ - struct blk_snap_snapshot_take) - -/** - * struct blk_snap_snapshot_collect - Argument for the - * &IOCTL_BLK_SNAP_SNAPSHOT_COLLECT control. - * @count: - * Size of &blk_snap_snapshot_collect.ids in the number of 16-byte UUID. - * @ids: - * Pointer to the array with the snapshot ID for output. - */ -struct blk_snap_snapshot_collect { - __u32 count; - struct blk_snap_uuid *ids; -}; - -/** - * define IOCTL_BLK_SNAP_SNAPSHOT_COLLECT - Get collection of created snapshots. - * - * Multiple snapshots can be created at the same time. This allows for one - * system to create backups for different data with a independent schedules. - * - * If in &blk_snap_snapshot_collect.count is less than required to store the - * &blk_snap_snapshot_collect.ids, the array is not filled, and the ioctl - * returns the required count for &blk_snap_snapshot_collect.ids. - * - * So, it is recommended to call the ioctl twice. The first call with an null - * pointer &blk_snap_snapshot_collect.ids and a zero value in - * &blk_snap_snapshot_collect.count. It will set the required array size in - * &blk_snap_snapshot_collect.count. The second call with a pointer - * &blk_snap_snapshot_collect.ids to an array of the required size will allow to - * get collection of active snapshots. - * - * Return: 0 if succeeded, -ENODATA if there is not enough space in the array - * to store collection of active snapshots, or negative errno otherwise. - */ -#define IOCTL_BLK_SNAP_SNAPSHOT_COLLECT \ - _IOW(BLK_SNAP, blk_snap_ioctl_snapshot_collect, \ - struct blk_snap_snapshot_collect) -/** - * struct blk_snap_image_info - Associates the original device in the snapshot - * and the corresponding snapshot image. - * @orig_dev_id: - * Device ID. - * @image_dev_id: - * Image ID. - */ -struct blk_snap_image_info { - struct blk_snap_dev orig_dev_id; - struct blk_snap_dev image_dev_id; -}; - -/** - * struct blk_snap_snapshot_collect_images - Argument for the - * &IOCTL_BLK_SNAP_SNAPSHOT_COLLECT_IMAGES control. - * @id: - * Snapshot ID. - * @count: - * Size of &image_info_array in the number of &struct blk_snap_image_info. - * @image_info_array: - * Pointer to the array for output. - */ -struct blk_snap_snapshot_collect_images { - struct blk_snap_uuid id; - __u32 count; - struct blk_snap_image_info *image_info_array; -}; - -/** - * define IOCTL_BLK_SNAP_SNAPSHOT_COLLECT_IMAGES - Get a collection of devices - * and their snapshot images. - * - * While holding the snapshot, this ioctl allows to get a table of - * correspondences of the original devices and their snapshot images. - * - * If &blk_snap_snapshot_collect_images.count is less than required to store the - * &blk_snap_snapshot_collect_images.image_info_array, the array is not filled, - * and the ioctl returns the required count for - * &blk_snap_snapshot_collect.image_info_array. - * - * So, it is recommended to call the ioctl twice. The first call with an null - * pointer &blk_snap_snapshot_collect_images.image_info_array and a zero value - * in &blk_snap_snapshot_collect_images.count. It will set the required array - * size in &blk_snap_snapshot_collect_images.count. The second call with a - * pointer &blk_snap_snapshot_collect_images.image_info_array to an array of the - * required size will allow to get collection of devices and their snapshot - * images. - * - * Return: 0 if succeeded, -ENODATA if there is not enough space in the array - * to store collection of devices and their snapshot images, negative errno - * otherwise. - */ -#define IOCTL_BLK_SNAP_SNAPSHOT_COLLECT_IMAGES \ - _IOW(BLK_SNAP, blk_snap_ioctl_snapshot_collect_images, \ - struct blk_snap_snapshot_collect_images) - -/** - * enum blk_snap_event_codes - Variants of event codes. - * - * @blk_snap_event_code_low_free_space: - * Low free space in difference storage event. - * If the free space in the difference storage is reduced to the specified - * limit, the module generates this event. - * @blk_snap_event_code_corrupted: - * Snapshot image is corrupted event. - * If a chunk could not be allocated when trying to save data to the - * difference storage, this event is generated. However, this does not mean - * that the backup process was interrupted with an error. If the snapshot - * image has been read to the end by this time, the backup process is - * considered successful. - */ -enum blk_snap_event_codes { - blk_snap_event_code_low_free_space, - blk_snap_event_code_corrupted, -}; - -/** - * struct blk_snap_snapshot_event - Argument for the - * &IOCTL_BLK_SNAP_SNAPSHOT_WAIT_EVENT control. - * @id: - * Snapshot ID. - * @timeout_ms: - * Timeout for waiting in milliseconds. - * @time_label: - * Timestamp of the received event. - * @code: - * Code of the received event &enum blk_snap_event_codes. - * @data: - * The received event body. - */ -struct blk_snap_snapshot_event { - struct blk_snap_uuid id; - __u32 timeout_ms; - __u32 code; - __s64 time_label; - __u8 data[4096 - 32]; -}; -static_assert(sizeof(struct blk_snap_snapshot_event) == 4096, - "The size struct blk_snap_snapshot_event should be equal to the size of the page."); - -/** - * define IOCTL_BLK_SNAP_SNAPSHOT_WAIT_EVENT - Wait and get the event from the - * snapshot. - * - * While holding the snapshot, the kernel module can transmit information about - * changes in its state in the form of events to the user level. - * It is very important to receive these events as quickly as possible, so the - * user's thread is in the state of interruptable sleep. - * - * Return: 0 if succeeded, negative errno otherwise. - */ -#define IOCTL_BLK_SNAP_SNAPSHOT_WAIT_EVENT \ - _IOW(BLK_SNAP, blk_snap_ioctl_snapshot_wait_event, \ - struct blk_snap_snapshot_event) - -/** - * struct blk_snap_event_low_free_space - Data for the - * &blk_snap_event_code_low_free_space event. - * @requested_nr_sect: - * The required number of sectors. - */ -struct blk_snap_event_low_free_space { - __u64 requested_nr_sect; -}; - -/** - * struct blk_snap_event_corrupted - Data for the - * &blk_snap_event_code_corrupted event. - * @orig_dev_id: - * Device ID. - * @err_code: - * Error code. - */ -struct blk_snap_event_corrupted { - struct blk_snap_dev orig_dev_id; - __s32 err_code; -}; - - -#ifdef BLK_SNAP_MODIFICATION -/** - * @tz_minuteswest: - * Time zone offset in minutes. - * The system time is in UTC. In order for the module to write local time - * to the log, its offset should be specified. - * @level: - * 0 - disable logging to file - * 3 - only error messages - * 4 - log warnings - * 6 - log info messages - * 7 - log debug messages - * @filepath_size: - * Count of bytes in &filepath. - * @filename: - * Full path for log file. - */ -struct blk_snap_setlog { - __s32 tz_minuteswest; - __u32 level; - __u32 filepath_size; - __u8 *filepath; -}; - -/** - * - */ -#define IOCTL_BLK_SNAP_SETLOG \ - _IOW(BLK_SNAP, blk_snap_ioctl_setlog, struct blk_snap_setlog) - -/** - * - */ -struct blk_snap_sector_state { - __u8 snap_number_prev; - __u8 snap_number_curr; - __u32 chunk_state; -}; - -struct blk_snap_get_sector_state { - struct blk_snap_dev image_dev_id; - __u64 sector; - struct blk_snap_sector_state state; -}; - -/** - * - */ -#define IOCTL_BLK_SNAP_GET_SECTOR_STATE \ - _IOW(BLK_SNAP, blk_snap_ioctl_get_sector_state, \ - struct blk_snap_get_sector_state) - -#endif /* BLK_SNAP_MODIFICATION */ - -#endif /* _UAPI_LINUX_BLK_SNAP_H */ diff --git a/include/linux/blk-filter.h b/include/linux/blk-filter.h new file mode 100644 index 00000000..3c0218dc --- /dev/null +++ b/include/linux/blk-filter.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022 Veeam Software Group GmbH + * + * This file is part of libblksnap + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Lesser Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +#ifndef _UAPI_LINUX_BLK_FILTER_H +#define _UAPI_LINUX_BLK_FILTER_H + +#include +#include + +#ifndef BLKFILTER_ATTACH + +#define BLKFILTER_ATTACH _IOWR(0x12, 140, struct blkfilter_name) +#define BLKFILTER_DETACH _IOWR(0x12, 141, struct blkfilter_name) +#define BLKFILTER_CTL _IOWR(0x12, 142, struct blkfilter_ctl) + +#define BLKFILTER_NAME_LENGTH 32 + +struct blkfilter_name { + __u8 name[BLKFILTER_NAME_LENGTH]; +}; + +/** + * struct blkfilter_ctl - parameter for BLKFILTER ioctl + * + * @name: Name of block device filter. + * @cmd: Command code opcode (BLKFILTER_CMD_*) + * @optlen: Size of data at @opt + * @opt: userspace buffer with options + */ +struct blkfilter_ctl { + __u8 name[BLKFILTER_NAME_LENGTH]; + __u32 cmd; + __u32 optlen; + __u64 opt; +}; + +#endif + +#endif diff --git a/include/linux/blksnap.h b/include/linux/blksnap.h new file mode 100644 index 00000000..04427384 --- /dev/null +++ b/include/linux/blksnap.h @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2022 Veeam Software Group GmbH + * + * This file is part of libblksnap + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Lesser Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include "blk-filter.h" + +#ifndef _UAPI_LINUX_BLKSNAP_H +#define _UAPI_LINUX_BLKSNAP_H + +#include + +#define BLKSNAP_CTL "blksnap-control" +#define BLKSNAP_IMAGE_NAME "blksnap-image" +#define BLKSNAP 'V' + +/** + * DOC: Block device filter interface. + * + * Control commands that are transmitted through the block device filter + * interface. + */ + +/** + * enum blkfilter_ctl_blksnap - List of commands for BLKFILTER_CTL ioctl + * + * @blkfilter_ctl_blksnap_cbtinfo: + * Get CBT information. + * The result of executing the command is a &struct blksnap_cbtinfo. + * Return 0 if succeeded, negative errno otherwise. + * @blkfilter_ctl_blksnap_cbtmap: + * Read the CBT map. + * The option passes the &struct blksnap_cbtmap. + * The size of the table can be quite large. Thus, the table is read in + * a loop, in each cycle of which the next offset is set to + * &blksnap_tracker_read_cbt_bitmap.offset. + * Return a count of bytes read if succeeded, negative errno otherwise. + * @blkfilter_ctl_blksnap_cbtdirty: + * Set dirty blocks in the CBT map. + * The option passes the &struct blksnap_cbtdirty. + * There are cases when some blocks need to be marked as changed. + * This ioctl allows to do this. + * Return 0 if succeeded, negative errno otherwise. + * @blkfilter_ctl_blksnap_snapshotadd: + * Add device to snapshot. + * The option passes the &struct blksnap_snapshotadd. + * Return 0 if succeeded, negative errno otherwise. + * @blkfilter_ctl_blksnap_snapshotinfo: + * Get information about snapshot. + * The result of executing the command is a &struct blksnap_snapshotinfo. + * Return 0 if succeeded, negative errno otherwise. + */ +enum blkfilter_ctl_blksnap { + blkfilter_ctl_blksnap_cbtinfo, + blkfilter_ctl_blksnap_cbtmap, + blkfilter_ctl_blksnap_cbtdirty, + blkfilter_ctl_blksnap_snapshotadd, + blkfilter_ctl_blksnap_snapshotinfo, +}; + +#ifndef UUID_SIZE +#define UUID_SIZE 16 +#endif + +/** + * struct blksnap_uuid - Unique 16-byte identifier. + * + * @b: + * An array of 16 bytes. + */ +struct blksnap_uuid { + __u8 b[UUID_SIZE]; +}; + +/** + * struct blksnap_cbtinfo - Result for the command + * &blkfilter_ctl_blksnap.blkfilter_ctl_blksnap_cbtinfo. + * + * @device_capacity: + * Device capacity in bytes. + * @block_size: + * Block size in bytes. + * @block_count: + * Number of blocks. + * @generation_id: + * Unique identifier of change tracking generation. + * @changes_number: + * Current changes number. + */ +struct blksnap_cbtinfo { + __u64 device_capacity; + __u32 block_size; + __u32 block_count; + struct blksnap_uuid generation_id; + __u8 changes_number; +}; + +/** + * struct blksnap_cbtmap - Option for the command + * &blkfilter_ctl_blksnap.blkfilter_ctl_blksnap_cbtmap. + * + * @offset: + * Offset from the beginning of the CBT bitmap in bytes. + * @length: + * Size of @buff in bytes. + * @buffer: + * Pointer to the buffer for output. + */ +struct blksnap_cbtmap { + __u32 offset; + __u32 length; + __u64 buffer; +}; + +/** + * struct blksnap_sectors - Description of the block device region. + * + * @offset: + * Offset from the beginning of the disk in sectors. + * @count: + * Count of sectors. + */ +struct blksnap_sectors { + __u64 offset; + __u64 count; +}; + +/** + * struct blksnap_cbtdirty - Option for the command + * &blkfilter_ctl_blksnap.blkfilter_ctl_blksnap_cbtdirty. + * + * @count: + * Count of elements in the @dirty_sectors. + * @dirty_sectors: + * Pointer to the array of &struct blksnap_sectors. + */ +struct blksnap_cbtdirty { + __u32 count; + __u64 dirty_sectors; +}; + +/** + * struct blksnap_snapshotadd - Option for the command + * &blkfilter_ctl_blksnap.blkfilter_ctl_blksnap_snapshotadd. + * + * @id: + * ID of the snapshot to which the block device should be added. + */ +struct blksnap_snapshotadd { + struct blksnap_uuid id; +}; + +#define IMAGE_DISK_NAME_LEN 32 + +/** + * struct blksnap_snapshotinfo - Result for the command + * &blkfilter_ctl_blksnap.blkfilter_ctl_blksnap_snapshotinfo. + * + * @error_code: + * Zero if there were no errors while holding the snapshot. + * The error code -ENOSPC means that while holding the snapshot, a snapshot + * overflow situation has occurred. Other error codes mean other reasons + * for failure. + * The error code is reset when the device is added to a new snapshot. + * @image: + * If the snapshot was taken, it stores the block device name of the + * image, or empty string otherwise. + */ +struct blksnap_snapshotinfo { + __s32 error_code; + __u8 image[IMAGE_DISK_NAME_LEN]; +}; + +/** + * DOC: Interface for managing snapshots + * + * Control commands that are transmitted through the blksnap module interface. + */ +enum blksnap_ioctl { + blksnap_ioctl_version, + blksnap_ioctl_snapshot_create, + blksnap_ioctl_snapshot_destroy, + blksnap_ioctl_snapshot_take, + blksnap_ioctl_snapshot_collect, + blksnap_ioctl_snapshot_wait_event, +}; + +/** + * struct blksnap_version - Module version. + * + * @major: + * Version major part. + * @minor: + * Version minor part. + * @revision: + * Revision number. + * @build: + * Build number. Should be zero. + */ +struct blksnap_version { + __u16 major; + __u16 minor; + __u16 revision; + __u16 build; +}; + +/** + * define IOCTL_BLKSNAP_VERSION - Get module version. + * + * The version may increase when the API changes. But linking the user space + * behavior to the version code does not seem to be a good idea. + * To ensure backward compatibility, API changes should be made by adding new + * ioctl without changing the behavior of existing ones. The version should be + * used for logs. + * + * Return: 0 if succeeded, negative errno otherwise. + */ +#define IOCTL_BLKSNAP_VERSION \ + _IOR(BLKSNAP, blksnap_ioctl_version, struct blksnap_version) + +/** + * struct blksnap_snapshot_create - Argument for the + * &IOCTL_BLKSNAP_SNAPSHOT_CREATE control. + * + * @diff_storage_limit_sect: + * The maximum allowed difference storage size in sectors. + * @diff_storage_fd: + * The difference storage file descriptor. + * @id: + * Generated new snapshot ID. + */ +struct blksnap_snapshot_create { + __u64 diff_storage_limit_sect; + __u32 diff_storage_fd; + struct blksnap_uuid id; +}; + +/** + * define IOCTL_BLKSNAP_SNAPSHOT_CREATE - Create snapshot. + * + * Creates a snapshot structure and initializes the difference storage. + * A snapshot is created for several block devices at once. Several snapshots + * can be created at the same time, but with the condition that one block + * device can only be included in one snapshot. + * + * The difference storage can be dynamically increase as it fills up. + * The file is increased in portions, the size of which is determined by the + * module parameter &diff_storage_minimum. Each time the amount of free space + * in the difference storage is reduced to the half of &diff_storage_minimum, + * the file is expanded by a portion, until it reaches the allowable limit + * &diff_storage_limit_sect. + * + * Return: 0 if succeeded, negative errno otherwise. + */ +#define IOCTL_BLKSNAP_SNAPSHOT_CREATE \ + _IOWR(BLKSNAP, blksnap_ioctl_snapshot_create, \ + struct blksnap_snapshot_create) + +/** + * define IOCTL_BLKSNAP_SNAPSHOT_DESTROY - Release and destroy the snapshot. + * + * Destroys snapshot with &blksnap_snapshot_destroy.id. This leads to the + * deletion of all block device images of the snapshot. The difference storage + * is being released. But the change tracker keeps tracking. + * + * Return: 0 if succeeded, negative errno otherwise. + */ +#define IOCTL_BLKSNAP_SNAPSHOT_DESTROY \ + _IOW(BLKSNAP, blksnap_ioctl_snapshot_destroy, \ + struct blksnap_uuid) + +/** + * define IOCTL_BLKSNAP_SNAPSHOT_TAKE - Take snapshot. + * + * Creates snapshot images of block devices and switches change trackers tables. + * The snapshot must be created before this call, and the areas of block + * devices should be added to the difference storage. + * + * Return: 0 if succeeded, negative errno otherwise. + */ +#define IOCTL_BLKSNAP_SNAPSHOT_TAKE \ + _IOW(BLKSNAP, blksnap_ioctl_snapshot_take, \ + struct blksnap_uuid) + +/** + * struct blksnap_snapshot_collect - Argument for the + * &IOCTL_BLKSNAP_SNAPSHOT_COLLECT control. + * + * @count: + * Size of &blksnap_snapshot_collect.ids in the number of 16-byte UUID. + * @ids: + * Pointer to the array of struct blksnap_uuid for output. + */ +struct blksnap_snapshot_collect { + __u32 count; + __u64 ids; +}; + +/** + * define IOCTL_BLKSNAP_SNAPSHOT_COLLECT - Get collection of created snapshots. + * + * Multiple snapshots can be created at the same time. This allows for one + * system to create backups for different data with a independent schedules. + * + * If in &blksnap_snapshot_collect.count is less than required to store the + * &blksnap_snapshot_collect.ids, the array is not filled, and the ioctl + * returns the required count for &blksnap_snapshot_collect.ids. + * + * So, it is recommended to call the ioctl twice. The first call with an null + * pointer &blksnap_snapshot_collect.ids and a zero value in + * &blksnap_snapshot_collect.count. It will set the required array size in + * &blksnap_snapshot_collect.count. The second call with a pointer + * &blksnap_snapshot_collect.ids to an array of the required size will allow to + * get collection of active snapshots. + * + * Return: 0 if succeeded, -ENODATA if there is not enough space in the array + * to store collection of active snapshots, or negative errno otherwise. + */ +#define IOCTL_BLKSNAP_SNAPSHOT_COLLECT \ + _IOR(BLKSNAP, blksnap_ioctl_snapshot_collect, \ + struct blksnap_snapshot_collect) + +/** + * enum blksnap_event_codes - Variants of event codes. + * + * @blksnap_event_code_corrupted: + * Snapshot image is corrupted event. + * If a chunk could not be allocated when trying to save data to the + * difference storage, this event is generated. However, this does not mean + * that the backup process was interrupted with an error. If the snapshot + * image has been read to the end by this time, the backup process is + * considered successful. + */ +enum blksnap_event_codes { + blksnap_event_code_corrupted, +}; + +/** + * struct blksnap_snapshot_event - Argument for the + * &IOCTL_BLKSNAP_SNAPSHOT_WAIT_EVENT control. + * + * @id: + * Snapshot ID. + * @timeout_ms: + * Timeout for waiting in milliseconds. + * @time_label: + * Timestamp of the received event. + * @code: + * Code of the received event &enum blksnap_event_codes. + * @data: + * The received event body. + */ +struct blksnap_snapshot_event { + struct blksnap_uuid id; + __u32 timeout_ms; + __u32 code; + __s64 time_label; + __u8 data[4096 - 32]; +}; + +/** + * define IOCTL_BLKSNAP_SNAPSHOT_WAIT_EVENT - Wait and get the event from the + * snapshot. + * + * While holding the snapshot, the kernel module can transmit information about + * changes in its state in the form of events to the user level. + * It is very important to receive these events as quickly as possible, so the + * user's thread is in the state of interruptible sleep. + * + * Return: 0 if succeeded, negative errno otherwise. + */ +#define IOCTL_BLKSNAP_SNAPSHOT_WAIT_EVENT \ + _IOR(BLKSNAP, blksnap_ioctl_snapshot_wait_event, \ + struct blksnap_snapshot_event) + +/** + * struct blksnap_event_corrupted - Data for the + * &blksnap_event_code_corrupted event. + * + * @dev_id_mj: + * Major part of original device ID. + * @dev_id_mn: + * Minor part of original device ID. + * @err_code: + * Error code. + */ +struct blksnap_event_corrupted { + __u32 dev_id_mj; + __u32 dev_id_mn; + __s32 err_code; +}; + +#endif /* _UAPI_LINUX_BLKSNAP_H */ diff --git a/lib/blksnap/Blksnap.cpp b/lib/blksnap/Blksnap.cpp deleted file mode 100644 index 71d690df..00000000 --- a/lib/blksnap/Blksnap.cpp +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright (C) 2022 Veeam Software Group GmbH - * - * This file is part of libblksnap - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation, either version 3 of the License, or (at your option) any - * later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Lesser Public License for more - * details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static const char* blksnap_filename = "/dev/" BLK_SNAP_CTL; - -using namespace blksnap; - -CBlksnap::CBlksnap() - : m_fd(0) -{ - int fd = ::open(blksnap_filename, O_RDWR); - if (fd < 0) - throw std::system_error(errno, std::generic_category(), blksnap_filename); - - m_fd = fd; -} - -CBlksnap::~CBlksnap() -{ - if (m_fd) - ::close(m_fd); -} - -void CBlksnap::Version(struct blk_snap_version& version) -{ - if (::ioctl(m_fd, IOCTL_BLK_SNAP_VERSION, &version)) - throw std::system_error(errno, std::generic_category(), "Failed to get version."); -} - -#ifdef BLK_SNAP_MODIFICATION -bool CBlksnap::Modification(struct blk_snap_mod& mod) -{ - if (::ioctl(m_fd, IOCTL_BLK_SNAP_MOD, &mod)) - { - if (errno == ENOTTY) - return false; - throw std::system_error(errno, std::generic_category(), "Failed to get modification."); - } - return true; -} -#endif - -void CBlksnap::CollectTrackers(std::vector& cbtInfoVector) -{ - struct blk_snap_tracker_collect param = {0}; - - if (::ioctl(m_fd, IOCTL_BLK_SNAP_TRACKER_COLLECT, ¶m)) - throw std::system_error(errno, std::generic_category(), - "[TBD]Failed to collect block devices with change tracking."); - - cbtInfoVector.resize(param.count); - param.cbt_info_array = cbtInfoVector.data(); - - if (::ioctl(m_fd, IOCTL_BLK_SNAP_TRACKER_COLLECT, ¶m)) - throw std::system_error(errno, std::generic_category(), - "[TBD]Failed to collect block devices with change tracking."); -} - -void CBlksnap::ReadCbtMap(struct blk_snap_dev dev_id, unsigned int offset, unsigned int length, uint8_t* buff) -{ - struct blk_snap_tracker_read_cbt_bitmap param - = {.dev_id = dev_id, .offset = offset, .length = length, .buff = buff}; - - int ret = ::ioctl(m_fd, IOCTL_BLK_SNAP_TRACKER_READ_CBT_MAP, ¶m); - if (ret < 0) - throw std::system_error(errno, std::generic_category(), - "[TBD]Failed to read difference map from change tracking."); - if (ret != length) - throw std::runtime_error("[TBD]Cannot read required bytes of difference map from change tracking."); -} - -void CBlksnap::Create(const std::vector& devices, uuid_t& id) -{ - struct blk_snap_snapshot_create param = {0}; - - std::vector localDevices = devices; - param.count = localDevices.size(); - param.dev_id_array = localDevices.data(); - - if (::ioctl(m_fd, IOCTL_BLK_SNAP_SNAPSHOT_CREATE, ¶m)) - throw std::system_error(errno, std::generic_category(), "[TBD]Failed to create snapshot object."); - - uuid_copy(id, param.id.b); -} - -void CBlksnap::Destroy(const uuid_t& id) -{ - struct blk_snap_snapshot_destroy param = {0}; - - uuid_copy(param.id.b, id); - - if (::ioctl(m_fd, IOCTL_BLK_SNAP_SNAPSHOT_DESTROY, ¶m)) - throw std::system_error(errno, std::generic_category(), "[TBD]Failed to destroy snapshot."); -} - -void CBlksnap::Collect(const uuid_t& id, std::vector& images) -{ - struct blk_snap_snapshot_collect_images param = {0}; - - uuid_copy(param.id.b, id); - - if (::ioctl(m_fd, IOCTL_BLK_SNAP_SNAPSHOT_COLLECT_IMAGES, ¶m)) - throw std::system_error(errno, std::generic_category(), - "[TBD]Failed to get device collection for snapshot images."); - - if (param.count == 0) - return; - - images.resize(param.count); - param.image_info_array = images.data(); - - if (::ioctl(m_fd, IOCTL_BLK_SNAP_SNAPSHOT_COLLECT_IMAGES, ¶m)) - throw std::system_error(errno, std::generic_category(), - "[TBD]Failed to get device collection for snapshot images."); -} - -void CBlksnap::AppendDiffStorage(const uuid_t& id, const struct blk_snap_dev& dev_id, - const std::vector& ranges) -{ - struct blk_snap_snapshot_append_storage param = {0}; - - uuid_copy(param.id.b, id); - param.dev_id = dev_id; - std::vector localRanges = ranges; - param.count = localRanges.size(); - param.ranges = localRanges.data(); - - if (::ioctl(m_fd, IOCTL_BLK_SNAP_SNAPSHOT_APPEND_STORAGE, ¶m)) - throw std::system_error(errno, std::generic_category(), "[TBD]Failed to append storage for snapshot."); -} - -void CBlksnap::Take(const uuid_t& id) -{ - struct blk_snap_snapshot_take param; - - uuid_copy(param.id.b, id); - - if (::ioctl(m_fd, IOCTL_BLK_SNAP_SNAPSHOT_TAKE, ¶m)) - throw std::system_error(errno, std::generic_category(), "[TBD]Failed to take snapshot."); -} - -bool CBlksnap::WaitEvent(const uuid_t& id, unsigned int timeoutMs, SBlksnapEvent& ev) -{ - struct blk_snap_snapshot_event param; - - uuid_copy(param.id.b, id); - param.timeout_ms = timeoutMs; - - if (::ioctl(m_fd, IOCTL_BLK_SNAP_SNAPSHOT_WAIT_EVENT, ¶m)) - { - if ((errno == ENOENT) || (errno == EINTR)) - return false; - else - throw std::system_error(errno, std::generic_category(), "[TBD]Failed to get event from snapshot."); - } - ev.code = param.code; - ev.time = param.time_label; - - switch (param.code) - { - case blk_snap_event_code_low_free_space: - { - struct blk_snap_event_low_free_space* lowFreeSpace = (struct blk_snap_event_low_free_space*)(param.data); - - ev.lowFreeSpace.requestedSectors = lowFreeSpace->requested_nr_sect; - break; - } - case blk_snap_event_code_corrupted: - { - struct blk_snap_event_corrupted* corrupted = (struct blk_snap_event_corrupted*)(param.data); - - ev.corrupted.origDevId = corrupted->orig_dev_id; - ev.corrupted.errorCode = corrupted->err_code; - break; - } - } - return true; -} - -#if defined(BLK_SNAP_MODIFICATION) && defined(BLK_SNAP_DEBUG_SECTOR_STATE) -void CBlksnap::GetSectorState(struct blk_snap_dev image_dev_id, off_t offset, struct blk_snap_sector_state& state) -{ - struct blk_snap_get_sector_state param - = {.image_dev_id = image_dev_id, .sector = static_cast<__u64>(offset >> SECTOR_SHIFT), .state = {0}}; - - if (::ioctl(m_fd, IOCTL_BLK_SNAP_GET_SECTOR_STATE, ¶m)) - throw std::system_error(errno, std::generic_category(), "[TBD]Failed to get sectors state."); - - state = param.state; -} -#endif diff --git a/lib/blksnap/CMakeLists.txt b/lib/blksnap/CMakeLists.txt index e551fcd4..bbebba6e 100644 --- a/lib/blksnap/CMakeLists.txt +++ b/lib/blksnap/CMakeLists.txt @@ -31,7 +31,8 @@ if (NOT LIBUUID_LIBRARY) endif () set(SOURCE_FILES - Blksnap.cpp + Snapshot.cpp + Tracker.cpp Cbt.cpp Service.cpp Session.cpp @@ -42,8 +43,8 @@ set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "blksnap") target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../../include) -install(TARGETS ${PROJECT_NAME} DESTINATION lib) +install(TARGETS ${PROJECT_NAME} DESTINATION /usr/lib) install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../../include/blksnap - DESTINATION include + DESTINATION /usr/include/blksnap FILES_MATCHING PATTERN "*.h") diff --git a/lib/blksnap/Cbt.cpp b/lib/blksnap/Cbt.cpp index eb0c5338..04b2eec1 100644 --- a/lib/blksnap/Cbt.cpp +++ b/lib/blksnap/Cbt.cpp @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ -#include +#include #include #include #include @@ -28,59 +28,63 @@ using namespace blksnap; class CCbt : public ICbt { public: - CCbt(); - ~CCbt() override{}; + CCbt(const std::string& devicePath) + : m_ctl(devicePath) + {}; + ~CCbt() override + {}; - std::shared_ptr GetCbtInfo(const std::string& original) override; - std::shared_ptr GetCbtData(const std::shared_ptr& ptrCbtInfo) override; + std::string GetImage() override + { + struct blksnap_snapshotinfo snapshotinfo; -private: - const struct blk_snap_cbt_info& GetCbtInfoInternal(unsigned int mj, unsigned int mn); + m_ctl.SnapshotInfo(snapshotinfo); -private: - CBlksnap m_blksnap; - std::vector m_cbtInfos; -}; + std::string name("/dev/"); + for (int inx = 0; (inx < IMAGE_DISK_NAME_LEN) && (snapshotinfo.image[inx] != '\0'); inx++) + name += static_cast(snapshotinfo.image[inx]); -std::shared_ptr ICbt::Create() -{ - return std::make_shared(); -} + return name; + } -CCbt::CCbt() -{ - m_blksnap.CollectTrackers(m_cbtInfos); -} + int GetError() override + { + struct blksnap_snapshotinfo snapshotinfo; -const struct blk_snap_cbt_info& CCbt::GetCbtInfoInternal(unsigned int mj, unsigned int mn) -{ - for (const struct blk_snap_cbt_info& cbtInfo : m_cbtInfos) - if ((mj == cbtInfo.dev_id.mj) && (mn == cbtInfo.dev_id.mn)) - return cbtInfo; + m_ctl.SnapshotInfo(snapshotinfo); + return snapshotinfo.error_code; + }; - throw std::runtime_error("The device [" + std::to_string(mj) + ":" + std::to_string(mn) - + "] was not found in the CBT table"); -} + std::shared_ptr GetCbtInfo() override + { + struct blksnap_cbtinfo cbtInfo; -std::shared_ptr CCbt::GetCbtInfo(const std::string& original) -{ - struct stat st; - - if (::stat(original.c_str(), &st)) - throw std::system_error(errno, std::generic_category(), original); + m_ctl.CbtInfo(cbtInfo); - const struct blk_snap_cbt_info& cbtInfo = GetCbtInfoInternal(major(st.st_rdev), minor(st.st_rdev)); + return std::make_shared( + cbtInfo.block_size, + cbtInfo.block_count, + cbtInfo.device_capacity, + cbtInfo.generation_id.b, + cbtInfo.changes_number); + }; - return std::make_shared(major(st.st_rdev), minor(st.st_rdev), cbtInfo.blk_size, cbtInfo.blk_count, - cbtInfo.device_capacity, cbtInfo.generation_id.b, cbtInfo.snap_number); -} + std::shared_ptr GetCbtData() override + { + struct blksnap_cbtinfo cbtInfo; + m_ctl.CbtInfo(cbtInfo); -std::shared_ptr CCbt::GetCbtData(const std::shared_ptr& ptrCbtInfo) -{ - struct blk_snap_dev originalDevId = {.mj = ptrCbtInfo->originalMajor, .mn = ptrCbtInfo->originalMinor}; - auto ptrCbtMap = std::make_shared(ptrCbtInfo->blockCount); + auto ptrCbtMap = std::make_shared(cbtInfo.block_count); + m_ctl.ReadCbtMap(0, ptrCbtMap->vec.size(), ptrCbtMap->vec.data()); - m_blksnap.ReadCbtMap(originalDevId, 0, ptrCbtMap->vec.size(), ptrCbtMap->vec.data()); + return ptrCbtMap; + }; +private: + CTracker m_ctl; +}; - return ptrCbtMap; +std::shared_ptr ICbt::Create(const std::string& devicePath) +{ + return std::make_shared(devicePath); } + diff --git a/lib/blksnap/Service.cpp b/lib/blksnap/Service.cpp index 2332d88c..d761758d 100644 --- a/lib/blksnap/Service.cpp +++ b/lib/blksnap/Service.cpp @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ -#include +#include #include #include #include @@ -24,23 +24,6 @@ #include #include -namespace -{ - static inline struct blk_snap_dev deviceByName(const std::string& name) - { - struct stat st; - - if (::stat(name.c_str(), &st)) - throw std::system_error(errno, std::generic_category(), name); - - struct blk_snap_dev device = { - .mj = major(st.st_rdev), - .mn = minor(st.st_rdev), - }; - return device; - } - -} /** * The version is displayed as a string for informational purposes only. @@ -48,60 +31,12 @@ namespace */ std::string blksnap::Version() { - struct blk_snap_version version; + struct blksnap_version version; - CBlksnap blksnap; - blksnap.Version(version); + CSnapshot::Version(version); std::stringstream ss; ss << version.major << "." << version.minor << "." << version.revision << "." << version.build; -#ifdef BLK_SNAP_MODIFICATION - struct blk_snap_mod mod; - - if (blksnap.Modification(mod)) - { - if (mod.compatibility_flags) - ss << "-0x" << std::hex << mod.compatibility_flags << std::dec; - - std::string modification; - for (int inx = 0; inx < BLK_SNAP_MOD_NAME_LIMIT; inx++) - { - if (!mod.name[inx]) - break; - modification += mod.name[inx]; - } - if (!modification.empty()) - ss << "-" << modification; - } -#endif return ss.str(); } - -void blksnap::GetSectorState(const std::string& image, off_t offset, SectorState& state) -{ -#ifdef BLK_SNAP_MODIFICATION - CBlksnap blksnap; - struct blk_snap_mod mod; - - if (!blksnap.Modification(mod)) - throw std::runtime_error("Failed to get sector state. Modification is not supported in blksnap module"); - -# ifdef BLK_SNAP_DEBUG_SECTOR_STATE - if (!(mod.compatibility_flags & (1 << blk_snap_compat_flag_debug_sector_state))) - throw std::runtime_error( - "Failed to get sector state. Sectors state getting is not supported in blksnap module"); - - struct blk_snap_sector_state st = {0}; - blksnap.GetSectorState(deviceByName(image), offset, st); - - state.snapNumberPrevious = st.snap_number_prev; - state.snapNumberCurrent = st.snap_number_curr; - state.chunkState = st.chunk_state; -# else - throw std::runtime_error("Failed to get sector state. It's not allowed"); -# endif -#else - throw std::runtime_error("Failed to get sector state. It's not implemented"); -#endif -} diff --git a/lib/blksnap/Session.cpp b/lib/blksnap/Session.cpp index 38b315f2..d216bd46 100644 --- a/lib/blksnap/Session.cpp +++ b/lib/blksnap/Session.cpp @@ -17,7 +17,9 @@ * along with this program. If not, see . */ #include -#include + +#include +#include #include #include #include @@ -40,252 +42,41 @@ namespace fs = boost::filesystem; using namespace blksnap; -struct SSessionInfo -{ - struct blk_snap_dev original; - struct blk_snap_dev image; - std::string originalName; - std::string imageName; -}; - -struct SRangeVectorPos -{ - size_t rangeInx; - sector_t rangeOfs; - - SRangeVectorPos() - : rangeInx(0) - , rangeOfs(0) - {}; -}; - struct SState { std::atomic stop; std::string diffStorage; - uuid_t id; std::mutex lock; std::list errorMessage; - std::vector diffStorageFiles; - - std::vector diffStorageRanges; - int diffDeviceMajor; - int diffDeviceMinor; - SRangeVectorPos diffStoragePosition; }; class CSession : public ISession { public: - CSession(const std::vector& devices, const std::string& diffStorage, const SStorageRanges& diffStorageRanges); + CSession(const std::vector& devices, + const std::string& diffStorageFilePath, + const unsigned long long limit); ~CSession() override; - std::string GetImageDevice(const std::string& original) override; - std::string GetOriginalDevice(const std::string& image) override; bool GetError(std::string& errorMessage) override; private: - uuid_t m_id; - std::vector m_devices; + CSnapshotId m_id; - std::shared_ptr m_ptrBlksnap; + std::shared_ptr m_ptrCtl; std::shared_ptr m_ptrState; std::shared_ptr m_ptrThread; }; -std::shared_ptr ISession::Create(const std::vector& devices, const std::string& diffStorage) +std::shared_ptr ISession::Create( + const std::vector& devices, + const std::string& diffStorageFilePath, + const unsigned long long limit) { - SStorageRanges diffStorageRanges; - - return std::make_shared(devices, diffStorage, diffStorageRanges); -} - -std::shared_ptr ISession::Create(const std::vector& devices, const SStorageRanges& diffStorageRanges) -{ - std::string diffStorage; - - return std::make_shared(devices, diffStorage, diffStorageRanges); + return std::make_shared(devices, diffStorageFilePath, limit); } -namespace -{ - static inline struct blk_snap_dev deviceByName(const std::string& name) - { - struct stat st; - - if (::stat(name.c_str(), &st)) - throw std::system_error(errno, std::generic_category(), name); - - struct blk_snap_dev device = { - .mj = major(st.st_rdev), - .mn = minor(st.st_rdev), - }; - return device; - } - - static void FiemapStorage(const std::string& filename, struct blk_snap_dev& dev_id, - std::vector& ranges) - { - int ret = 0; - const char* errMessage; - int fd = -1; - struct fiemap* map = NULL; - int extentMax = 500; - long long fileSize; - struct stat64 st; - - if (::stat64(filename.c_str(), &st)) - throw std::system_error(errno, std::generic_category(), "Failed to get file size."); - - fileSize = st.st_size; - dev_id.mj = major(st.st_dev); - dev_id.mn = minor(st.st_dev); - - fd = ::open(filename.c_str(), O_RDONLY | O_EXCL | O_LARGEFILE); - if (fd < 0) - { - ret = errno; - errMessage = "Failed to open file."; - goto out; - } - - map = (struct fiemap*)::malloc(sizeof(struct fiemap) + sizeof(struct fiemap_extent) * extentMax); - if (!map) - { - ret = ENOMEM; - errMessage = "Failed to allocate memory for fiemap structure."; - goto out; - } - - for (long long fileOffset = 0; fileOffset < fileSize;) - { - map->fm_start = fileOffset; - map->fm_length = fileSize - fileOffset; - map->fm_extent_count = extentMax; - map->fm_flags = 0; - - if (::ioctl(fd, FS_IOC_FIEMAP, map)) - { - ret = errno; - errMessage = "Failed to call FS_IOC_FIEMAP."; - goto out; - } - - for (int i = 0; i < map->fm_mapped_extents; ++i) - { - struct blk_snap_block_range rg; - struct fiemap_extent* extent = map->fm_extents + i; - - if (extent->fe_physical & (SECTOR_SIZE - 1)) - { - ret = EINVAL; - errMessage = "File location is not ordered by sector size."; - goto out; - } - - rg.sector_offset = extent->fe_physical >> SECTOR_SHIFT; - rg.sector_count = extent->fe_length >> SECTOR_SHIFT; - ranges.push_back(rg); - - fileOffset = extent->fe_logical + extent->fe_length; - - // std::cout << "allocate range: ofs=" << rg.sector_offset << " cnt=" << rg.sector_count << std::endl; - } - } - - out: - if (map) - ::free(map); - if (fd >= 0) - ::close(fd); - if (ret) - throw std::system_error(ret, std::generic_category(), errMessage); - } - - static void FallocateStorage(const std::string& filename, const off_t filesize) - { - int fd = ::open(filename.c_str(), O_CREAT | O_RDWR | O_EXCL | O_LARGEFILE, 0600); - if (fd < 0) - throw std::system_error(errno, std::generic_category(), "[TBD]Failed to create file for diff storage."); - - if (::fallocate64(fd, 0, 0, filesize)) - { - int err = errno; - - ::remove(filename.c_str()); - ::close(fd); - throw std::system_error(err, std::generic_category(), "[TBD]Failed to allocate file for diff storage."); - } - ::close(fd); - } - - static void DeviceNumberByName(const std::string& deviceName, int& mj, int& mn) - { - struct stat64 st; - - if (::stat64(deviceName.c_str(), &st)) - throw std::system_error(errno, std::generic_category(), "Failed to get file size."); - - mj = major(st.st_rdev); - mn = minor(st.st_rdev); - } - - static void AllocateDiffStorage(std::shared_ptr ptrState, sector_t requestedSectors, - struct blk_snap_dev& dev_id, std::vector& ranges) - { - if (ptrState->diffStorageRanges.size() <= ptrState->diffStoragePosition.rangeInx) - throw std::runtime_error("Failed to allocate diff storage. Not enough free ranges"); - - dev_id.mj = ptrState->diffDeviceMajor; - dev_id.mn = ptrState->diffDeviceMinor; - - while (requestedSectors) - { - const SRange& rg = ptrState->diffStorageRanges[ptrState->diffStoragePosition.rangeInx]; - - sector_t ofs = rg.sector + ptrState->diffStoragePosition.rangeOfs; - sector_t sz = std::min(rg.count - ptrState->diffStoragePosition.rangeOfs, requestedSectors); - - if (sz == 0) - { - ptrState->diffStoragePosition.rangeOfs = 0; - if (ptrState->diffStorageRanges.size() == ++ptrState->diffStoragePosition.rangeInx) - break; - - continue; - } - - { - struct blk_snap_block_range rg = { - .sector_offset = ofs, - .sector_count = sz - }; - - ranges.push_back(rg); - } - - ptrState->diffStoragePosition.rangeOfs += sz; - requestedSectors -= sz; - } - } - static void LogAppendedRanges(std::vector& ranges) - { - sector_t totalSectors = 0; - - std::cout << "" << std::endl; - std::cout << "Append " << ranges.size() << " ranges: " << std::endl; - for (const struct blk_snap_block_range& rg : ranges) - { - totalSectors += rg.sector_count; - std::cout << std::to_string(rg.sector_offset) << ":"<< std::to_string(rg.sector_count) << std::endl; - - } - std::cout << "Total sectors append: " << totalSectors << std::endl; - - } -} // - -static void BlksnapThread(std::shared_ptr ptrBlksnap, std::shared_ptr ptrState) +static void BlksnapThread(std::shared_ptr ptrCtl, std::shared_ptr ptrState) { struct SBlksnapEvent ev; int diffStorageNumber = 1; @@ -295,7 +86,7 @@ static void BlksnapThread(std::shared_ptr ptrBlksnap, std::shared_ptr< { try { - is_eventReady = ptrBlksnap->WaitEvent(ptrState->id, 100, ev); + is_eventReady = ptrCtl->WaitEvent(100, ev); } catch (std::exception& ex) { @@ -312,39 +103,9 @@ static void BlksnapThread(std::shared_ptr ptrBlksnap, std::shared_ptr< { switch (ev.code) { - case blk_snap_event_code_low_free_space: - { - struct blk_snap_dev dev_id; - std::vector ranges; - - if (!ptrState->diffStorage.empty()) - { - fs::path filepath(ptrState->diffStorage); - filepath += std::string("diff_storage#" + std::to_string(diffStorageNumber++)); - if (fs::exists(filepath)) - fs::remove(filepath); - std::string filename = filepath.string(); - - { - std::lock_guard guard(ptrState->lock); - ptrState->diffStorageFiles.push_back(filename); - } - FallocateStorage(filename, ev.lowFreeSpace.requestedSectors << SECTOR_SHIFT); - FiemapStorage(filename, dev_id, ranges); - } - else - AllocateDiffStorage(ptrState, ev.lowFreeSpace.requestedSectors, dev_id, ranges); - - LogAppendedRanges(ranges); - ptrBlksnap->AppendDiffStorage(ptrState->id, dev_id, ranges); - } - break; - case blk_snap_event_code_corrupted: + case blksnap_event_code_corrupted: throw std::system_error(ev.corrupted.errorCode, std::generic_category(), - std::string("Snapshot corrupted for device " - + std::to_string(ev.corrupted.origDevId.mj) + ":" - + std::to_string(ev.corrupted.origDevId.mn))); - break; + std::string("Snapshot corrupted for device " + std::to_string(ev.corrupted.origDevIdMj) + ":" + std::to_string(ev.corrupted.origDevIdMn))); default: throw std::runtime_error("Invalid blksnap event code received."); } @@ -358,180 +119,66 @@ static void BlksnapThread(std::shared_ptr ptrBlksnap, std::shared_ptr< } } -CSession::CSession(const std::vector& devices, const std::string& diffStorage, const SStorageRanges& diffStorageRanges) +CSession::CSession(const std::vector& devices, const std::string& diffStorageFilePath, const unsigned long long limit) { - m_ptrBlksnap = std::make_shared(); - for (const auto& name : devices) - { - SSessionInfo info; + CTracker(name).Attach(); - info.originalName = name; - info.original = deviceByName(name); - info.image = {0}; - info.imageName = ""; - m_devices.push_back(info); - } + // Create snapshot + auto snapshot = CSnapshot::Create(diffStorageFilePath, limit); - /* - * Create snapshot - */ - std::vector blk_snap_devs; - for (const SSessionInfo& info : m_devices) - blk_snap_devs.push_back(info.original); - m_ptrBlksnap->Create(blk_snap_devs, m_id); + // Add devices to snapshot + for (const auto& name : devices) + CTracker(name).SnapshotAdd(snapshot->Id()); - /* - * Prepare state structure for thread - */ + // Prepare state structure for thread m_ptrState = std::make_shared(); m_ptrState->stop = false; - if (!diffStorage.empty()) - m_ptrState->diffStorage = diffStorage; - if (!diffStorageRanges.ranges.empty()) - m_ptrState->diffStorageRanges = diffStorageRanges.ranges; - if (!diffStorageRanges.device.empty()) - DeviceNumberByName(diffStorageRanges.device, m_ptrState->diffDeviceMajor, m_ptrState->diffDeviceMinor); - uuid_copy(m_ptrState->id, m_id); - /* - * Append first portion for diff storage - */ + // Append first portion for diff storage struct SBlksnapEvent ev; - if (m_ptrBlksnap->WaitEvent(m_id, 100, ev)) + if (snapshot->WaitEvent(100, ev)) { switch (ev.code) { - case blk_snap_event_code_low_free_space: - { - struct blk_snap_dev dev_id; - std::vector ranges; - - if (!m_ptrState->diffStorage.empty()) - { - fs::path filepath(m_ptrState->diffStorage); - filepath += std::string("diff_storage#" + std::to_string(0)); - if (fs::exists(filepath)) - fs::remove(filepath); - std::string filename = filepath.string(); - - m_ptrState->diffStorageFiles.push_back(filename); - FallocateStorage(filename, ev.lowFreeSpace.requestedSectors << SECTOR_SHIFT); - FiemapStorage(filename, dev_id, ranges); - } - else - AllocateDiffStorage(m_ptrState, ev.lowFreeSpace.requestedSectors, dev_id, ranges); - - LogAppendedRanges(ranges); - m_ptrBlksnap->AppendDiffStorage(m_id, dev_id, ranges); - } - break; - case blk_snap_event_code_corrupted: + case blksnap_event_code_corrupted: throw std::system_error(ev.corrupted.errorCode, std::generic_category(), std::string("Failed to create snapshot for device " - + std::to_string(ev.corrupted.origDevId.mj) + ":" - + std::to_string(ev.corrupted.origDevId.mn))); + + std::to_string(ev.corrupted.origDevIdMj) + ":" + + std::to_string(ev.corrupted.origDevIdMn))); break; default: throw std::runtime_error("Invalid blksnap event code received."); } } - /* - * Start stretch snapshot thread - */ - m_ptrThread = std::make_shared(BlksnapThread, m_ptrBlksnap, m_ptrState); + // Start stretch snapshot thread + m_ptrThread = std::make_shared(BlksnapThread, m_ptrCtl, m_ptrState); ::usleep(0); - /* - * Take snapshot - */ - m_ptrBlksnap->Take(m_id); - /* - * Collect images - */ - std::vector images; - m_ptrBlksnap->Collect(m_id, images); - - for (const struct blk_snap_image_info& imageInfo : images) - { - for (size_t inx = 0; inx < m_devices.size(); inx++) - { - if ((m_devices[inx].original.mj == imageInfo.orig_dev_id.mj) - && (m_devices[inx].original.mn == imageInfo.orig_dev_id.mn)) - { - m_devices[inx].image = imageInfo.image_dev_id; - m_devices[inx].imageName - = std::string("/dev/" BLK_SNAP_IMAGE_NAME) + std::to_string(imageInfo.image_dev_id.mn); - } - } - } + // Take snapshot + snapshot->Take(); } CSession::~CSession() { // std::cout << "Destroy blksnap session" << std::endl; - /** - * Stop thread - */ + + // Stop thread m_ptrState->stop = true; m_ptrThread->join(); - /** - * Destroy snapshot - */ + // Destroy snapshot try { - m_ptrBlksnap->Destroy(m_id); + m_ptrCtl->Destroy(); } catch (std::exception& ex) { std::cerr << ex.what() << std::endl; return; } - - /** - * Cleanup diff storage files - */ - for (const std::string& filename : m_ptrState->diffStorageFiles) - { - try - { - if (::remove(filename.c_str())) - throw std::system_error(errno, std::generic_category(), "[TBD]Failed to remove diff storage file."); - } - catch (std::exception& ex) - { - std::cerr << ex.what() << std::endl; - } - } -} - -std::string CSession::GetImageDevice(const std::string& original) -{ - struct blk_snap_dev devId = deviceByName(original); - - for (size_t inx = 0; inx < m_devices.size(); inx++) - { - if ((m_devices[inx].original.mj == devId.mj) && (m_devices[inx].original.mn == devId.mn)) - return m_devices[inx].imageName; - } - - throw std::runtime_error("Failed to get image device for [" + original + "]."); -} - -std::string CSession::GetOriginalDevice(const std::string& image) -{ - struct blk_snap_dev devId = deviceByName(image); - - for (size_t inx = 0; inx < m_devices.size(); inx++) - { - if ((m_devices[inx].image.mj == devId.mj) && (m_devices[inx].image.mn == devId.mn)) - return m_devices[inx].originalName; - } - - throw std::runtime_error("Failed to get original device for [" + image + "]."); } bool CSession::GetError(std::string& errorMessage) diff --git a/lib/blksnap/Snapshot.cpp b/lib/blksnap/Snapshot.cpp new file mode 100644 index 00000000..488c6859 --- /dev/null +++ b/lib/blksnap/Snapshot.cpp @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2022 Veeam Software Group GmbH + * + * This file is part of libblksnap + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Lesser Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fs = boost::filesystem; + +static const char* blksnap_filename = "/dev/" BLKSNAP_CTL; + +using namespace blksnap; + +OpenFileHolder::OpenFileHolder(const std::string& filename, int flags, int mode/* = 0 */) +{ + int fd = mode ? ::open(filename.c_str(), flags, mode) : ::open(filename.c_str(), flags); + if (fd < 0) + throw std::system_error(errno, std::generic_category(), + "Cannot open file [" + filename + "]"); + m_fd = fd; +}; +OpenFileHolder::~OpenFileHolder() +{ + ::close(m_fd); +}; + +int OpenFileHolder::Get() +{ + return m_fd; +}; + +static inline bool isBlockFile(const std::string& path) +{ + struct stat st; + + if (::stat(path.c_str(), &st)) + throw std::system_error(errno, std::generic_category(), "Failed to get status for '"+path+"'."); + + return S_ISBLK(st.st_mode); +} + +static inline std::shared_ptr OpenBlksnapCtl() +{ + return std::make_shared(blksnap_filename, O_RDWR); +} + +CSnapshot::CSnapshot(const CSnapshotId& id, const std::shared_ptr& ctl) + : m_id(id) + , m_ctl(ctl) +{ } + +void CSnapshot::Version(struct blksnap_version& version) +{ + auto ctl = OpenBlksnapCtl(); + if (::ioctl(ctl->Get(), IOCTL_BLKSNAP_VERSION, &version)) + throw std::system_error(errno, std::generic_category(), + "Failed to get version."); +} + +std::shared_ptr CSnapshot::Create(const std::string& filePath, const unsigned long long limit) +{ + int flags = O_RDWR | O_EXCL; + + if (fs::is_directory(filePath)) + flags |= O_TMPFILE; + else if (!fs::is_regular_file(filePath) && !isBlockFile(filePath)) + throw std::invalid_argument("The filePath should have been either the name of a regular file, the name of a block device, or the directory for creating a temporary file."); + + OpenFileHolder fd(filePath, flags, 0600); + + struct blksnap_snapshot_create param = {0}; + param.diff_storage_limit_sect = limit / 512; + param.diff_storage_fd = fd.Get(); + + auto ctl = OpenBlksnapCtl(); + if (::ioctl(ctl->Get(), IOCTL_BLKSNAP_SNAPSHOT_CREATE, ¶m)) + throw std::system_error(errno, std::generic_category(), + "Failed to create snapshot object."); + + return std::shared_ptr(new CSnapshot(CSnapshotId(param.id.b), ctl)); +} + +std::shared_ptr CSnapshot::Open(const CSnapshotId& id) +{ + return std::shared_ptr(new CSnapshot(id, OpenBlksnapCtl())); +} + +void CSnapshot::Collect(std::vector& ids) +{ + struct blksnap_snapshot_collect param = {0}; + auto ctl = OpenBlksnapCtl(); + + ids.clear(); + if (::ioctl(ctl->Get(), IOCTL_BLKSNAP_SNAPSHOT_COLLECT, ¶m)) + throw std::system_error(errno, std::generic_category(), + "Failed to get list of active snapshots."); + + if (param.count == 0) + return; + + std::vector id_array(param.count); + param.ids = (__u64)id_array.data(); + + if (::ioctl(ctl->Get(), IOCTL_BLKSNAP_SNAPSHOT_COLLECT, ¶m)) + throw std::system_error(errno, std::generic_category(), + "Failed to get list of snapshots."); + + for (size_t inx = 0; inx < param.count; inx++) + ids.emplace_back(id_array[inx].b); +} + +void CSnapshot::Take() +{ + struct blksnap_uuid param; + + uuid_copy(param.b, m_id.Get()); + if (::ioctl(m_ctl->Get(), IOCTL_BLKSNAP_SNAPSHOT_TAKE, ¶m)) + throw std::system_error(errno, std::generic_category(), + "Failed to take snapshot."); +} + +void CSnapshot::Destroy() +{ + struct blksnap_uuid param; + + uuid_copy(param.b, m_id.Get()); + if (::ioctl(m_ctl->Get(), IOCTL_BLKSNAP_SNAPSHOT_DESTROY, ¶m)) + throw std::system_error(errno, std::generic_category(), + "Failed to destroy snapshot."); +} + +bool CSnapshot::WaitEvent(unsigned int timeoutMs, SBlksnapEvent& ev) +{ + struct blksnap_snapshot_event param; + + uuid_copy(param.id.b, m_id.Get()); + param.timeout_ms = timeoutMs; + + if (::ioctl(m_ctl->Get(), IOCTL_BLKSNAP_SNAPSHOT_WAIT_EVENT, ¶m)) + { + if ((errno == ENOENT) || (errno == EINTR)) + return false; + + throw std::system_error(errno, std::generic_category(), "Failed to get event from snapshot."); + } + ev.code = param.code; + ev.time = param.time_label; + + switch (param.code) + { + case blksnap_event_code_corrupted: + { + struct blksnap_event_corrupted* corrupted = (struct blksnap_event_corrupted*)(param.data); + + ev.corrupted.origDevIdMj = corrupted->dev_id_mj; + ev.corrupted.origDevIdMn = corrupted->dev_id_mn; + ev.corrupted.errorCode = corrupted->err_code; + break; + } + } + return true; +} diff --git a/lib/blksnap/Tracker.cpp b/lib/blksnap/Tracker.cpp new file mode 100644 index 00000000..7795c056 --- /dev/null +++ b/lib/blksnap/Tracker.cpp @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2022 Veeam Software Group GmbH + * + * This file is part of libblksnap + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Lesser Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace blksnap; + +#define BLKSNAP_FILTER_NAME {'b','l','k','s','n','a','p','\0'} + +CTracker::CTracker(const std::string& devicePath) +{ + m_fd = ::open(devicePath.c_str(), O_DIRECT, 0600); + if (m_fd < 0) + throw std::system_error(errno, std::generic_category(), + "Failed to open block device ["+devicePath+"]."); +} +CTracker::~CTracker() +{ + if (m_fd > 0) { + ::close(m_fd); + m_fd = 0; + } +} + +bool CTracker::Attach() +{ + struct blkfilter_name name = { + .name = BLKSNAP_FILTER_NAME, + }; + + if (::ioctl(m_fd, BLKFILTER_ATTACH, &name) < 0) { + if (errno == EALREADY) + return false; + else + throw std::system_error(errno, std::generic_category(), + "Failed to attach 'blksnap' filter."); + } + return true; +} +void CTracker::Detach() +{ + struct blkfilter_name name = { + .name = BLKSNAP_FILTER_NAME, + }; + + if (::ioctl(m_fd, BLKFILTER_DETACH, &name) < 0) + throw std::system_error(errno, std::generic_category(), + "Failed to detach 'blksnap' filter."); + +} + +void CTracker::CbtInfo(struct blksnap_cbtinfo& cbtInfo) +{ + struct blkfilter_ctl ctl = { + .name = BLKSNAP_FILTER_NAME, + .cmd = blkfilter_ctl_blksnap_cbtinfo, + .optlen = sizeof(cbtInfo), + .opt = (__u64)&cbtInfo, + }; + + if (::ioctl(m_fd, BLKFILTER_CTL, &ctl) < 0) + throw std::system_error(errno, std::generic_category(), + "Failed to get CBT information."); +} +void CTracker::ReadCbtMap(unsigned int offset, unsigned int length, uint8_t* buff) +{ + struct blksnap_cbtmap arg = { + .offset = offset, + .buffer = (__u64)buff + }; + struct blkfilter_ctl ctl = { + .name = BLKSNAP_FILTER_NAME, + .cmd = blkfilter_ctl_blksnap_cbtmap, + .optlen = sizeof(arg), + .opt = (__u64)&arg, + }; + + if (::ioctl(m_fd, BLKFILTER_CTL, &ctl) < 0) + throw std::system_error(errno, std::generic_category(), + "Failed to read CBT map."); + +} +void CTracker::MarkDirtyBlock(std::vector& ranges) +{ + struct blksnap_cbtdirty arg = { + .count = static_cast(ranges.size()), + .dirty_sectors = (__u64)ranges.data(), + }; + struct blkfilter_ctl ctl = { + .name = BLKSNAP_FILTER_NAME, + .cmd = blkfilter_ctl_blksnap_cbtdirty, + .optlen = sizeof(arg), + .opt = (__u64)&arg, + }; + + if (::ioctl(m_fd, BLKFILTER_CTL, &ctl) < 0) + throw std::system_error(errno, std::generic_category(), + "Failed to mark block as 'dirty' in CBT map."); +} +void CTracker::SnapshotAdd(const uuid_t& id) +{ + struct blksnap_snapshotadd arg; + uuid_copy(arg.id.b, id); + + struct blkfilter_ctl ctl = { + .name = BLKSNAP_FILTER_NAME, + .cmd = blkfilter_ctl_blksnap_snapshotadd, + .optlen = sizeof(arg), + .opt = (__u64)&arg, + }; + + if (::ioctl(m_fd, BLKFILTER_CTL, &ctl) < 0) + throw std::system_error(errno, std::generic_category(), + "Failed to add device to snapshot."); +} +void CTracker::SnapshotInfo(struct blksnap_snapshotinfo& snapshotinfo) +{ + struct blkfilter_ctl ctl = { + .name = BLKSNAP_FILTER_NAME, + .cmd = blkfilter_ctl_blksnap_snapshotinfo, + .optlen = sizeof(snapshotinfo), + .opt = (__u64)&snapshotinfo, + }; + + if (::ioctl(m_fd, BLKFILTER_CTL, &ctl) < 0) + throw std::system_error(errno, std::generic_category(), + "Failed to get snapshot information."); +} + + + diff --git a/module/.clang-format b/module/.clang-format deleted file mode 100644 index fa959436..00000000 --- a/module/.clang-format +++ /dev/null @@ -1,560 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -# -# clang-format configuration file. Intended for clang-format >= 4. -# -# For more information, see: -# -# Documentation/process/clang-format.rst -# https://clang.llvm.org/docs/ClangFormat.html -# https://clang.llvm.org/docs/ClangFormatStyleOptions.html -# ---- -AccessModifierOffset: -4 -AlignAfterOpenBracket: Align -AlignConsecutiveAssignments: false -AlignConsecutiveDeclarations: false -#AlignEscapedNewlines: Left # Unknown to clang-format-4.0 -AlignOperands: true -AlignTrailingComments: false -AllowAllParametersOfDeclarationOnNextLine: false -AllowShortBlocksOnASingleLine: false -AllowShortCaseLabelsOnASingleLine: false -AllowShortFunctionsOnASingleLine: None -AllowShortIfStatementsOnASingleLine: false -AllowShortLoopsOnASingleLine: false -AlwaysBreakAfterDefinitionReturnType: None -AlwaysBreakAfterReturnType: None -AlwaysBreakBeforeMultilineStrings: false -AlwaysBreakTemplateDeclarations: false -BinPackArguments: true -BinPackParameters: true -BraceWrapping: - AfterClass: false - AfterControlStatement: false - AfterEnum: false - AfterFunction: true - AfterNamespace: true - AfterObjCDeclaration: false - AfterStruct: false - AfterUnion: false - #AfterExternBlock: false # Unknown to clang-format-5.0 - BeforeCatch: false - BeforeElse: false - IndentBraces: false - #SplitEmptyFunction: true # Unknown to clang-format-4.0 - #SplitEmptyRecord: true # Unknown to clang-format-4.0 - #SplitEmptyNamespace: true # Unknown to clang-format-4.0 -BreakBeforeBinaryOperators: None -BreakBeforeBraces: Custom -#BreakBeforeInheritanceComma: false # Unknown to clang-format-4.0 -BreakBeforeTernaryOperators: false -BreakConstructorInitializersBeforeComma: false -#BreakConstructorInitializers: BeforeComma # Unknown to clang-format-4.0 -BreakAfterJavaFieldAnnotations: false -BreakStringLiterals: false -ColumnLimit: 80 -CommentPragmas: '^ IWYU pragma:' -#CompactNamespaces: false # Unknown to clang-format-4.0 -ConstructorInitializerAllOnOneLineOrOnePerLine: false -ConstructorInitializerIndentWidth: 8 -ContinuationIndentWidth: 8 -Cpp11BracedListStyle: false -DerivePointerAlignment: false -DisableFormat: false -ExperimentalAutoDetectBinPacking: false -#FixNamespaceComments: false # Unknown to clang-format-4.0 - -# Taken from: -# git grep -h '^#define [^[:space:]]*for_each[^[:space:]]*(' include/ \ -# | sed "s,^#define \([^[:space:]]*for_each[^[:space:]]*\)(.*$, - '\1'," \ -# | sort | uniq -ForEachMacros: - - 'apei_estatus_for_each_section' - - 'ata_for_each_dev' - - 'ata_for_each_link' - - '__ata_qc_for_each' - - 'ata_qc_for_each' - - 'ata_qc_for_each_raw' - - 'ata_qc_for_each_with_internal' - - 'ax25_for_each' - - 'ax25_uid_for_each' - - '__bio_for_each_bvec' - - 'bio_for_each_bvec' - - 'bio_for_each_bvec_all' - - 'bio_for_each_integrity_vec' - - '__bio_for_each_segment' - - 'bio_for_each_segment' - - 'bio_for_each_segment_all' - - 'bio_list_for_each' - - 'bip_for_each_vec' - - 'bitmap_for_each_clear_region' - - 'bitmap_for_each_set_region' - - 'blkg_for_each_descendant_post' - - 'blkg_for_each_descendant_pre' - - 'blk_queue_for_each_rl' - - 'bond_for_each_slave' - - 'bond_for_each_slave_rcu' - - 'bpf_for_each_spilled_reg' - - 'btree_for_each_safe128' - - 'btree_for_each_safe32' - - 'btree_for_each_safe64' - - 'btree_for_each_safel' - - 'card_for_each_dev' - - 'cgroup_taskset_for_each' - - 'cgroup_taskset_for_each_leader' - - 'cpufreq_for_each_entry' - - 'cpufreq_for_each_entry_idx' - - 'cpufreq_for_each_valid_entry' - - 'cpufreq_for_each_valid_entry_idx' - - 'css_for_each_child' - - 'css_for_each_descendant_post' - - 'css_for_each_descendant_pre' - - 'device_for_each_child_node' - - 'displayid_iter_for_each' - - 'dma_fence_chain_for_each' - - 'do_for_each_ftrace_op' - - 'drm_atomic_crtc_for_each_plane' - - 'drm_atomic_crtc_state_for_each_plane' - - 'drm_atomic_crtc_state_for_each_plane_state' - - 'drm_atomic_for_each_plane_damage' - - 'drm_client_for_each_connector_iter' - - 'drm_client_for_each_modeset' - - 'drm_connector_for_each_possible_encoder' - - 'drm_for_each_bridge_in_chain' - - 'drm_for_each_connector_iter' - - 'drm_for_each_crtc' - - 'drm_for_each_crtc_reverse' - - 'drm_for_each_encoder' - - 'drm_for_each_encoder_mask' - - 'drm_for_each_fb' - - 'drm_for_each_legacy_plane' - - 'drm_for_each_plane' - - 'drm_for_each_plane_mask' - - 'drm_for_each_privobj' - - 'drm_mm_for_each_hole' - - 'drm_mm_for_each_node' - - 'drm_mm_for_each_node_in_range' - - 'drm_mm_for_each_node_safe' - - 'flow_action_for_each' - - 'for_each_acpi_dev_match' - - 'for_each_active_dev_scope' - - 'for_each_active_drhd_unit' - - 'for_each_active_iommu' - - 'for_each_aggr_pgid' - - 'for_each_available_child_of_node' - - 'for_each_bio' - - 'for_each_board_func_rsrc' - - 'for_each_bvec' - - 'for_each_card_auxs' - - 'for_each_card_auxs_safe' - - 'for_each_card_components' - - 'for_each_card_dapms' - - 'for_each_card_pre_auxs' - - 'for_each_card_prelinks' - - 'for_each_card_rtds' - - 'for_each_card_rtds_safe' - - 'for_each_card_widgets' - - 'for_each_card_widgets_safe' - - 'for_each_cgroup_storage_type' - - 'for_each_child_of_node' - - 'for_each_clear_bit' - - 'for_each_clear_bit_from' - - 'for_each_cmsghdr' - - 'for_each_compatible_node' - - 'for_each_component_dais' - - 'for_each_component_dais_safe' - - 'for_each_comp_order' - - 'for_each_console' - - 'for_each_cpu' - - 'for_each_cpu_and' - - 'for_each_cpu_not' - - 'for_each_cpu_wrap' - - 'for_each_dapm_widgets' - - 'for_each_dev_addr' - - 'for_each_dev_scope' - - 'for_each_dma_cap_mask' - - 'for_each_dpcm_be' - - 'for_each_dpcm_be_rollback' - - 'for_each_dpcm_be_safe' - - 'for_each_dpcm_fe' - - 'for_each_drhd_unit' - - 'for_each_dss_dev' - - 'for_each_dtpm_table' - - 'for_each_efi_memory_desc' - - 'for_each_efi_memory_desc_in_map' - - 'for_each_element' - - 'for_each_element_extid' - - 'for_each_element_id' - - 'for_each_endpoint_of_node' - - 'for_each_evictable_lru' - - 'for_each_fib6_node_rt_rcu' - - 'for_each_fib6_walker_rt' - - 'for_each_free_mem_pfn_range_in_zone' - - 'for_each_free_mem_pfn_range_in_zone_from' - - 'for_each_free_mem_range' - - 'for_each_free_mem_range_reverse' - - 'for_each_func_rsrc' - - 'for_each_hstate' - - 'for_each_if' - - 'for_each_iommu' - - 'for_each_ip_tunnel_rcu' - - 'for_each_irq_nr' - - 'for_each_link_codecs' - - 'for_each_link_cpus' - - 'for_each_link_platforms' - - 'for_each_lru' - - 'for_each_matching_node' - - 'for_each_matching_node_and_match' - - 'for_each_member' - - 'for_each_memcg_cache_index' - - 'for_each_mem_pfn_range' - - '__for_each_mem_range' - - 'for_each_mem_range' - - '__for_each_mem_range_rev' - - 'for_each_mem_range_rev' - - 'for_each_mem_region' - - 'for_each_migratetype_order' - - 'for_each_msi_entry' - - 'for_each_msi_entry_safe' - - 'for_each_net' - - 'for_each_net_continue_reverse' - - 'for_each_netdev' - - 'for_each_netdev_continue' - - 'for_each_netdev_continue_rcu' - - 'for_each_netdev_continue_reverse' - - 'for_each_netdev_feature' - - 'for_each_netdev_in_bond_rcu' - - 'for_each_netdev_rcu' - - 'for_each_netdev_reverse' - - 'for_each_netdev_safe' - - 'for_each_net_rcu' - - 'for_each_new_connector_in_state' - - 'for_each_new_crtc_in_state' - - 'for_each_new_mst_mgr_in_state' - - 'for_each_new_plane_in_state' - - 'for_each_new_private_obj_in_state' - - 'for_each_node' - - 'for_each_node_by_name' - - 'for_each_node_by_type' - - 'for_each_node_mask' - - 'for_each_node_state' - - 'for_each_node_with_cpus' - - 'for_each_node_with_property' - - 'for_each_nonreserved_multicast_dest_pgid' - - 'for_each_of_allnodes' - - 'for_each_of_allnodes_from' - - 'for_each_of_cpu_node' - - 'for_each_of_pci_range' - - 'for_each_old_connector_in_state' - - 'for_each_old_crtc_in_state' - - 'for_each_old_mst_mgr_in_state' - - 'for_each_oldnew_connector_in_state' - - 'for_each_oldnew_crtc_in_state' - - 'for_each_oldnew_mst_mgr_in_state' - - 'for_each_oldnew_plane_in_state' - - 'for_each_oldnew_plane_in_state_reverse' - - 'for_each_oldnew_private_obj_in_state' - - 'for_each_old_plane_in_state' - - 'for_each_old_private_obj_in_state' - - 'for_each_online_cpu' - - 'for_each_online_node' - - 'for_each_online_pgdat' - - 'for_each_pci_bridge' - - 'for_each_pci_dev' - - 'for_each_pci_msi_entry' - - 'for_each_pcm_streams' - - 'for_each_physmem_range' - - 'for_each_populated_zone' - - 'for_each_possible_cpu' - - 'for_each_present_cpu' - - 'for_each_prime_number' - - 'for_each_prime_number_from' - - 'for_each_process' - - 'for_each_process_thread' - - 'for_each_prop_codec_conf' - - 'for_each_prop_dai_codec' - - 'for_each_prop_dai_cpu' - - 'for_each_prop_dlc_codecs' - - 'for_each_prop_dlc_cpus' - - 'for_each_prop_dlc_platforms' - - 'for_each_property_of_node' - - 'for_each_registered_fb' - - 'for_each_requested_gpio' - - 'for_each_requested_gpio_in_range' - - 'for_each_reserved_mem_range' - - 'for_each_reserved_mem_region' - - 'for_each_rtd_codec_dais' - - 'for_each_rtd_components' - - 'for_each_rtd_cpu_dais' - - 'for_each_rtd_dais' - - 'for_each_set_bit' - - 'for_each_set_bit_from' - - 'for_each_set_clump8' - - 'for_each_sg' - - 'for_each_sg_dma_page' - - 'for_each_sg_page' - - 'for_each_sgtable_dma_page' - - 'for_each_sgtable_dma_sg' - - 'for_each_sgtable_page' - - 'for_each_sgtable_sg' - - 'for_each_sibling_event' - - 'for_each_subelement' - - 'for_each_subelement_extid' - - 'for_each_subelement_id' - - '__for_each_thread' - - 'for_each_thread' - - 'for_each_unicast_dest_pgid' - - 'for_each_vsi' - - 'for_each_wakeup_source' - - 'for_each_zone' - - 'for_each_zone_zonelist' - - 'for_each_zone_zonelist_nodemask' - - 'fwnode_for_each_available_child_node' - - 'fwnode_for_each_child_node' - - 'fwnode_graph_for_each_endpoint' - - 'gadget_for_each_ep' - - 'genradix_for_each' - - 'genradix_for_each_from' - - 'hash_for_each' - - 'hash_for_each_possible' - - 'hash_for_each_possible_rcu' - - 'hash_for_each_possible_rcu_notrace' - - 'hash_for_each_possible_safe' - - 'hash_for_each_rcu' - - 'hash_for_each_safe' - - 'hctx_for_each_ctx' - - 'hlist_bl_for_each_entry' - - 'hlist_bl_for_each_entry_rcu' - - 'hlist_bl_for_each_entry_safe' - - 'hlist_for_each' - - 'hlist_for_each_entry' - - 'hlist_for_each_entry_continue' - - 'hlist_for_each_entry_continue_rcu' - - 'hlist_for_each_entry_continue_rcu_bh' - - 'hlist_for_each_entry_from' - - 'hlist_for_each_entry_from_rcu' - - 'hlist_for_each_entry_rcu' - - 'hlist_for_each_entry_rcu_bh' - - 'hlist_for_each_entry_rcu_notrace' - - 'hlist_for_each_entry_safe' - - 'hlist_for_each_entry_srcu' - - '__hlist_for_each_rcu' - - 'hlist_for_each_safe' - - 'hlist_nulls_for_each_entry' - - 'hlist_nulls_for_each_entry_from' - - 'hlist_nulls_for_each_entry_rcu' - - 'hlist_nulls_for_each_entry_safe' - - 'i3c_bus_for_each_i2cdev' - - 'i3c_bus_for_each_i3cdev' - - 'ide_host_for_each_port' - - 'ide_port_for_each_dev' - - 'ide_port_for_each_present_dev' - - 'idr_for_each_entry' - - 'idr_for_each_entry_continue' - - 'idr_for_each_entry_continue_ul' - - 'idr_for_each_entry_ul' - - 'in_dev_for_each_ifa_rcu' - - 'in_dev_for_each_ifa_rtnl' - - 'inet_bind_bucket_for_each' - - 'inet_lhash2_for_each_icsk_rcu' - - 'key_for_each' - - 'key_for_each_safe' - - 'klp_for_each_func' - - 'klp_for_each_func_safe' - - 'klp_for_each_func_static' - - 'klp_for_each_object' - - 'klp_for_each_object_safe' - - 'klp_for_each_object_static' - - 'kunit_suite_for_each_test_case' - - 'kvm_for_each_memslot' - - 'kvm_for_each_vcpu' - - 'list_for_each' - - 'list_for_each_codec' - - 'list_for_each_codec_safe' - - 'list_for_each_continue' - - 'list_for_each_entry' - - 'list_for_each_entry_continue' - - 'list_for_each_entry_continue_rcu' - - 'list_for_each_entry_continue_reverse' - - 'list_for_each_entry_from' - - 'list_for_each_entry_from_rcu' - - 'list_for_each_entry_from_reverse' - - 'list_for_each_entry_lockless' - - 'list_for_each_entry_rcu' - - 'list_for_each_entry_reverse' - - 'list_for_each_entry_safe' - - 'list_for_each_entry_safe_continue' - - 'list_for_each_entry_safe_from' - - 'list_for_each_entry_safe_reverse' - - 'list_for_each_entry_srcu' - - 'list_for_each_prev' - - 'list_for_each_prev_safe' - - 'list_for_each_safe' - - 'llist_for_each' - - 'llist_for_each_entry' - - 'llist_for_each_entry_safe' - - 'llist_for_each_safe' - - 'mci_for_each_dimm' - - 'media_device_for_each_entity' - - 'media_device_for_each_intf' - - 'media_device_for_each_link' - - 'media_device_for_each_pad' - - 'nanddev_io_for_each_page' - - 'netdev_for_each_lower_dev' - - 'netdev_for_each_lower_private' - - 'netdev_for_each_lower_private_rcu' - - 'netdev_for_each_mc_addr' - - 'netdev_for_each_uc_addr' - - 'netdev_for_each_upper_dev_rcu' - - 'netdev_hw_addr_list_for_each' - - 'nft_rule_for_each_expr' - - 'nla_for_each_attr' - - 'nla_for_each_nested' - - 'nlmsg_for_each_attr' - - 'nlmsg_for_each_msg' - - 'nr_neigh_for_each' - - 'nr_neigh_for_each_safe' - - 'nr_node_for_each' - - 'nr_node_for_each_safe' - - 'of_for_each_phandle' - - 'of_property_for_each_string' - - 'of_property_for_each_u32' - - 'pci_bus_for_each_resource' - - 'pcl_for_each_chunk' - - 'pcl_for_each_segment' - - 'pcm_for_each_format' - - 'ping_portaddr_for_each_entry' - - 'plist_for_each' - - 'plist_for_each_continue' - - 'plist_for_each_entry' - - 'plist_for_each_entry_continue' - - 'plist_for_each_entry_safe' - - 'plist_for_each_safe' - - 'pnp_for_each_card' - - 'pnp_for_each_dev' - - 'protocol_for_each_card' - - 'protocol_for_each_dev' - - 'queue_for_each_hw_ctx' - - 'radix_tree_for_each_slot' - - 'radix_tree_for_each_tagged' - - 'rb_for_each' - - 'rbtree_postorder_for_each_entry_safe' - - 'rdma_for_each_block' - - 'rdma_for_each_port' - - 'rdma_umem_for_each_dma_block' - - 'resource_list_for_each_entry' - - 'resource_list_for_each_entry_safe' - - 'rhl_for_each_entry_rcu' - - 'rhl_for_each_rcu' - - 'rht_for_each' - - 'rht_for_each_entry' - - 'rht_for_each_entry_from' - - 'rht_for_each_entry_rcu' - - 'rht_for_each_entry_rcu_from' - - 'rht_for_each_entry_safe' - - 'rht_for_each_from' - - 'rht_for_each_rcu' - - 'rht_for_each_rcu_from' - - '__rq_for_each_bio' - - 'rq_for_each_bvec' - - 'rq_for_each_segment' - - 'scsi_for_each_prot_sg' - - 'scsi_for_each_sg' - - 'sctp_for_each_hentry' - - 'sctp_skb_for_each' - - 'shdma_for_each_chan' - - '__shost_for_each_device' - - 'shost_for_each_device' - - 'sk_for_each' - - 'sk_for_each_bound' - - 'sk_for_each_entry_offset_rcu' - - 'sk_for_each_from' - - 'sk_for_each_rcu' - - 'sk_for_each_safe' - - 'sk_nulls_for_each' - - 'sk_nulls_for_each_from' - - 'sk_nulls_for_each_rcu' - - 'snd_array_for_each' - - 'snd_pcm_group_for_each_entry' - - 'snd_soc_dapm_widget_for_each_path' - - 'snd_soc_dapm_widget_for_each_path_safe' - - 'snd_soc_dapm_widget_for_each_sink_path' - - 'snd_soc_dapm_widget_for_each_source_path' - - 'tb_property_for_each' - - 'tcf_exts_for_each_action' - - 'udp_portaddr_for_each_entry' - - 'udp_portaddr_for_each_entry_rcu' - - 'usb_hub_for_each_child' - - 'v4l2_device_for_each_subdev' - - 'v4l2_m2m_for_each_dst_buf' - - 'v4l2_m2m_for_each_dst_buf_safe' - - 'v4l2_m2m_for_each_src_buf' - - 'v4l2_m2m_for_each_src_buf_safe' - - 'virtio_device_for_each_vq' - - 'while_for_each_ftrace_op' - - 'xa_for_each' - - 'xa_for_each_marked' - - 'xa_for_each_range' - - 'xa_for_each_start' - - 'xas_for_each' - - 'xas_for_each_conflict' - - 'xas_for_each_marked' - - 'xbc_array_for_each_value' - - 'xbc_for_each_key_value' - - 'xbc_node_for_each_array_value' - - 'xbc_node_for_each_child' - - 'xbc_node_for_each_key_value' - - 'zorro_for_each_dev' - -#IncludeBlocks: Preserve # Unknown to clang-format-5.0 -IncludeCategories: - - Regex: '.*' - Priority: 1 -IncludeIsMainRegex: '(Test)?$' -IndentCaseLabels: false -#IndentPPDirectives: None # Unknown to clang-format-5.0 -IndentWidth: 8 -IndentWrappedFunctionNames: false -JavaScriptQuotes: Leave -JavaScriptWrapImports: true -KeepEmptyLinesAtTheStartOfBlocks: false -MacroBlockBegin: '' -MacroBlockEnd: '' -MaxEmptyLinesToKeep: 1 -NamespaceIndentation: None -#ObjCBinPackProtocolList: Auto # Unknown to clang-format-5.0 -ObjCBlockIndentWidth: 8 -ObjCSpaceAfterProperty: true -ObjCSpaceBeforeProtocolList: true - -# Taken from git's rules -#PenaltyBreakAssignment: 10 # Unknown to clang-format-4.0 -PenaltyBreakBeforeFirstCallParameter: 30 -PenaltyBreakComment: 10 -PenaltyBreakFirstLessLess: 0 -PenaltyBreakString: 10 -PenaltyExcessCharacter: 100 -PenaltyReturnTypeOnItsOwnLine: 60 - -PointerAlignment: Right -ReflowComments: false -SortIncludes: false -#SortUsingDeclarations: false # Unknown to clang-format-4.0 -SpaceAfterCStyleCast: false -SpaceAfterTemplateKeyword: true -SpaceBeforeAssignmentOperators: true -#SpaceBeforeCtorInitializerColon: true # Unknown to clang-format-5.0 -#SpaceBeforeInheritanceColon: true # Unknown to clang-format-5.0 -SpaceBeforeParens: ControlStatements -#SpaceBeforeRangeBasedForLoopColon: true # Unknown to clang-format-5.0 -SpaceInEmptyParentheses: false -SpacesBeforeTrailingComments: 1 -SpacesInAngles: false -SpacesInContainerLiterals: false -SpacesInCStyleCastParentheses: false -SpacesInParentheses: false -SpacesInSquareBrackets: false -Standard: Cpp03 -TabWidth: 8 -UseTab: Always -... diff --git a/module/.gitignore b/module/.gitignore deleted file mode 100644 index 80644461..00000000 --- a/module/.gitignore +++ /dev/null @@ -1,156 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -# -# NOTE! Don't add files that are generated in specific -# subdirectories here. Add them in the ".gitignore" file -# in that subdirectory instead. -# -# NOTE! Please use 'git ls-files -i --exclude-standard' -# command after changing this file, to see if there are -# any tracked files which get ignored after the change. -# -# Normal rules (sorted alphabetically) -# -.* -*.a -*.asn1.[ch] -*.bin -*.bz2 -*.c.[012]*.* -*.dt.yaml -*.dtb -*.dtb.S -*.dwo -*.elf -*.gcno -*.gz -*.i -*.ko -*.lex.c -*.ll -*.lst -*.lz4 -*.lzma -*.lzo -*.mod -*.mod.c -*.o -*.o.* -*.patch -*.s -*.so -*.so.dbg -*.su -*.symtypes -*.tab.[ch] -*.tar -*.xz -Module.symvers -modules.builtin -modules.order - -# -# Top-level generic files -# -/tags -/TAGS -/linux -/vmlinux -/vmlinux.32 -/vmlinux.symvers -/vmlinux-gdb.py -/vmlinuz -/System.map -/Module.markers -/modules.builtin.modinfo -/modules.nsdeps - -# -# RPM spec file (make rpm-pkg) -# -/*.spec - -# -# Debian directory (make deb-pkg) -# -/debian/ - -# -# Snap directory (make snap-pkg) -# -/snap/ - -# -# tar directory (make tar*-pkg) -# -/tar-install/ - -# -# We don't want to ignore the following even if they are dot-files -# -!.clang-format -!.cocciconfig -!.get_maintainer.ignore -!.gitattributes -!.gitignore -!.mailmap - -# -# Generated include files -# -/include/config/ -/include/generated/ -/include/ksym/ -/arch/*/include/generated/ - -# stgit generated dirs -patches-* - -# quilt's files -patches -series - -# cscope files -cscope.* -ncscope.* - -# gnu global files -GPATH -GRTAGS -GSYMS -GTAGS - -# id-utils files -ID - -*.orig -*~ -\#*# - -# -# Leavings from module signing -# -extra_certificates -signing_key.pem -signing_key.priv -signing_key.x509 -x509.genkey - -# Kconfig presets -/all.config -/alldef.config -/allmod.config -/allno.config -/allrandom.config -/allyes.config - -# Kconfig savedefconfig output -/defconfig - -# Kdevelop4 -*.kdev4 - -# Clang's compilation database file -/compile_commands.json - -# cmake files -cmake* \ No newline at end of file diff --git a/module/.gitkeep b/module/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/module/Kconfig b/module/Kconfig deleted file mode 100644 index 2f726fd3..00000000 --- a/module/Kconfig +++ /dev/null @@ -1,12 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -# -# Block device snapshot module configuration -# - -config BLK_SNAP - tristate "Block Devices Snapshots Module (blksnap)" - help - Allow to create snapshots and track block changes for block devices. - Designed for creating backups for simple block devices. Snapshots are - temporary and are released then backup is completed. Change block - tracking allows to create incremental or differential backups. diff --git a/module/Makefile b/module/Makefile deleted file mode 100644 index 1d97832d..00000000 --- a/module/Makefile +++ /dev/null @@ -1,19 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -# for upstream, we don't need any addition makefiles, -# but for a standalone module the configuration is necessary -include ${M}/Makefile-* - -blksnap-y := \ - cbt_map.o \ - chunk.o \ - diff_io.o \ - diff_area.o \ - diff_buffer.o \ - diff_storage.o \ - event_queue.o \ - main.o \ - snapimage.o \ - snapshot.o \ - tracker.o - -obj-$(CONFIG_BLK_SNAP) += blksnap.o diff --git a/module/Makefile-bdevfilter b/module/Makefile-bdevfilter deleted file mode 100644 index 5982226c..00000000 --- a/module/Makefile-bdevfilter +++ /dev/null @@ -1,13 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -# Additions for standalone bdevfilter module - -EXTRA_CFLAGS += "-DSTANDALONE_BDEVFILTER" - -EXTRA_CFLAGS += $(shell \ - grep -qw "struct ftrace_regs" $(srctree)/include/linux/ftrace.h && \ - echo -DHAVE_FTRACE_REGS) -EXTRA_CFLAGS += $(shell \ - grep -qw "ftrace_regs_set_instruction_pointer" $(srctree)/include/linux/ftrace.h && \ - echo -D HAVE_FTRACE_REGS_SET_INSTRUCTION_POINTER) - -obj-m += bdevfilter.o diff --git a/module/Makefile-standalone b/module/Makefile-standalone deleted file mode 100644 index a2005b8b..00000000 --- a/module/Makefile-standalone +++ /dev/null @@ -1,88 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -# Additions for standalone module modification -CONFIG_BLK_SNAP := m - - -ccflags-y += "-D BLK_SNAP_MODIFICATION" -ccflags-y += "-D MOD_NAME=\"standalone\" " - -ccflags-y += $(shell \ - grep -qw "blk_qc_t submit_bio_noacct" $(srctree)/include/linux/blkdev.h && \ - echo -D HAVE_QC_SUBMIT_BIO_NOACCT) -ccflags-y += $(shell \ - grep -qw "void submit_bio_noacct" $(srctree)/include/linux/blkdev.h && \ - echo -D HAVE_VOID_SUBMIT_BIO_NOACCT) - -ccflags-y += $(shell \ - grep -qw "struct super_block \*freeze_bdev" \ - $(srctree)/include/linux/blkdev.h && \ - echo -D HAVE_SUPER_BLOCK_FREEZE) - -ccflags-y += $(shell \ - grep -qw "*bi_bdev;" $(srctree)/include/linux/blk_types.h && \ - echo -D HAVE_BI_BDEV) -ccflags-y += $(shell \ - grep -qw "*bi_disk;" $(srctree)/include/linux/blk_types.h && \ - echo -D HAVE_BI_BDISK) - -ccflags-y += $(shell test -f $(srctree)/include/linux/genhd.h && \ - grep -qw "sector_t bdev_nr_sectors" $(srctree)/include/linux/genhd.h && \ - echo -D HAVE_BDEV_NR_SECTORS) - -ccflags-y += $(shell test -f $(srctree)/include/linux/blkdev.h && \ - grep -qw "sector_t bdev_nr_sectors" $(srctree)/include/linux/blkdev.h && \ - echo -D HAVE_BDEV_NR_SECTORS) - -ccflags-y += $(shell \ - grep -qw "blk_qc_t submit_bio" $(srctree)/include/linux/bio.h && \ - echo -D HAVE_QC_SUBMIT_BIO) - -ccflags-y += $(shell test -f $(srctree)/include/linux/genhd.h && \ - grep -qw "define blk_alloc_disk" $(srctree)/include/linux/genhd.h && \ - echo -D HAVE_BLK_ALLOC_DISK) -ccflags-y += $(shell test -f $(srctree)/include/linux/blkdev.h && \ - grep -qw "define blk_alloc_disk" $(srctree)/include/linux/blkdev.h && \ - echo -D HAVE_BLK_ALLOC_DISK) - -ccflags-y += $(shell \ - grep -qw "BIO_MAX_PAGES" $(srctree)/include/linux/bio.h && \ - echo -D HAVE_BIO_MAX_PAGES) - -ccflags-y += $(shell test -f $(srctree)/include/linux/genhd.h && \ - grep -qw "int add_disk" $(srctree)/include/linux/genhd.h && \ - echo -D HAVE_ADD_DISK_RESULT) -ccflags-y += $(shell test -f $(srctree)/include/linux/blkdev.h && \ - grep -qw "int add_disk" $(srctree)/include/linux/blkdev.h && \ - echo -D HAVE_ADD_DISK_RESULT) -ccflags-y += $(shell test -f $(srctree)/include/linux/genhd.h && \ - grep -qw "int __must_check add_disk" $(srctree)/include/linux/genhd.h && \ - echo -D HAVE_ADD_DISK_RESULT) -ccflags-y += $(shell test -f $(srctree)/include/linux/blkdev.h && \ - grep -qw "int __must_check add_disk" $(srctree)/include/linux/blkdev.h && \ - echo -D HAVE_ADD_DISK_RESULT) - -ccflags-y += $(shell test -f $(srctree)/include/linux/genhd.h && \ - grep -qw "void blk_cleanup_disk" $(srctree)/include/linux/genhd.h && \ - echo -D HAVE_BLK_CLEANUP_DISK) -ccflags-y += $(shell test -f $(srctree)/include/linux/blkdev.h && \ - grep -qw "void blk_cleanup_disk" $(srctree)/include/linux/blkdev.h && \ - echo -D HAVE_BLK_CLEANUP_DISK) - -ccflags-y += $(shell test -f $(srctree)/include/linux/genhd.h && \ - echo -D HAVE_GENHD_H) - -ccflags-y += $(shell \ - grep "bio_alloc_bioset" $(srctree)/include/linux/bio.h | \ - grep -qw "struct block_device" && \ - echo -D HAVE_BDEV_BIO_ALLOC) - -# Specific options for standalone module configuration -ccflags-y += "-D BLK_SNAP_DEBUG_MEMORY_LEAK" -ccflags-y += "-D BLK_SNAP_FILELOG" -ccflags-y += "-D BLK_SNAP_SEQUENTALFREEZE" -# ccflags-y += "-D BLK_SNAP_DEBUGLOG" -# ccflags-y += "-D BLK_SNAP_ALLOW_DIFF_STORAGE_IN_MEMORY" -# ccflags-y += "-D BLK_SNAP_DEBUG_SECTOR_STATE" - -blksnap-$(CONFIG_BLK_SNAP) += memory_checker.o -blksnap-$(CONFIG_BLK_SNAP) += log.o diff --git a/module/bdevfilter.c b/module/bdevfilter.c deleted file mode 100644 index bcbf76d7..00000000 --- a/module/bdevfilter.c +++ /dev/null @@ -1,522 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#ifdef HAVE_GENHD_H -#include -#endif -#include -#include - -#include "bdevfilter.h" -#include "version.h" - -#if defined(BLK_SNAP_DEBUGLOG) -#undef pr_debug -#define pr_debug(fmt, ...) \ -({ \ - printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__); \ -}) -#endif - -struct bdev_extension { - struct list_head link; - - dev_t dev_id; -#if defined(HAVE_BI_BDISK) - struct gendisk *disk; - u8 partno; -#else - struct block_device *bdev; -#endif - - struct bdev_filter *bd_filter; - spinlock_t bd_filter_lock; -}; - -/* The list of extensions for this block device */ -static LIST_HEAD(bdev_extension_list); - -/* Lock the queue of block device to add or delete extension. */ -static DEFINE_SPINLOCK(bdev_extension_list_lock); - -static inline struct bdev_extension *bdev_extension_find(dev_t dev_id) -{ - struct bdev_extension *ext; - - if (list_empty(&bdev_extension_list)) - return NULL; - - list_for_each_entry (ext, &bdev_extension_list, link) - if (dev_id == ext->dev_id) - return ext; - - return NULL; -} - -#if defined(HAVE_BI_BDISK) -static inline struct bdev_extension *bdev_extension_find_part(struct gendisk *disk, - u8 partno) -{ - struct bdev_extension *ext; - - if (list_empty(&bdev_extension_list)) - return NULL; - - list_for_each_entry (ext, &bdev_extension_list, link) - if ((disk == ext->disk) && (partno == ext->partno)) - return ext; - - return NULL; -} -#else -static inline struct bdev_extension *bdev_extension_find_bdev(struct block_device *bdev) -{ - struct bdev_extension *ext; - - if (list_empty(&bdev_extension_list)) - return NULL; - - list_for_each_entry (ext, &bdev_extension_list, link) - if (bdev == ext->bdev) - return ext; - - return NULL; -} -#endif - -static inline struct bdev_extension *bdev_extension_append(struct block_device *bdev) -{ - bool recreate = false; - struct bdev_extension *result = NULL; - struct bdev_extension *ext; - struct bdev_extension *ext_tmp; - - ext_tmp = kzalloc(sizeof(struct bdev_extension), GFP_NOIO); - if (!ext_tmp) - return NULL; - - INIT_LIST_HEAD(&ext_tmp->link); - ext_tmp->dev_id = bdev->bd_dev; -#if defined(HAVE_BI_BDISK) - ext_tmp->disk = bdev->bd_disk; - ext_tmp->partno = bdev->bd_partno; -#else - ext_tmp->bdev = bdev; -#endif - ext_tmp->bd_filter = NULL; - - spin_lock_init(&ext_tmp->bd_filter_lock); - - spin_lock(&bdev_extension_list_lock); - ext = bdev_extension_find(bdev->bd_dev); - if (!ext) { - /* add new extension */ - pr_debug("Add new bdev extension"); - list_add_tail(&ext_tmp->link, &bdev_extension_list); - result = ext_tmp; - ext_tmp = NULL; - } else { -#if defined(HAVE_BI_BDISK) - if ((ext->disk == bdev->bd_disk) && (ext->partno == bdev->bd_partno)) { -#else - if (ext->bdev == bdev) { -#endif - /* extension already exist */ - pr_debug("Bdev extension already exist"); - result = ext; - } else { - /* extension should be recreated */ - pr_debug("Bdev extension should be recreated"); - list_add_tail(&ext_tmp->link, &bdev_extension_list); - result = ext_tmp; - - recreate = true; - list_del(&ext->link); - ext_tmp = ext; - } - } - spin_unlock(&bdev_extension_list_lock); - - /* Recreated block device found */ - if (recreate) { - struct bdev_filter *flt; - - pr_info("Detach all block device filters from %d:%d\n", - MAJOR(bdev->bd_dev), MINOR(bdev->bd_dev)); - - spin_lock(&ext_tmp->bd_filter_lock); - flt = ext_tmp->bd_filter; - ext_tmp->bd_filter = NULL; - spin_unlock(&ext_tmp->bd_filter_lock); - - if (flt) - bdev_filter_put(flt); - } - kfree(ext_tmp); - - return result; -} - -void bdev_filter_free(struct kref *kref) -{ - struct bdev_filter *flt = container_of(kref, struct bdev_filter, kref); - - flt->fops->release(flt); - percpu_free_rwsem(&flt->submit_lock); -}; -EXPORT_SYMBOL(bdev_filter_free); - -/** - * bdev_filter_attach - Attach a filter to original block device. - * @bdev: - * block device - * @flt: - * Pointer to the filter structure. - * - * The bdev_filter_detach() function allows to detach the filter from the block - * device. - * - * Return: - * 0 - OK - * -EBUSY - a filter already exists - */ -int bdev_filter_attach(struct block_device *bdev, struct bdev_filter *flt) -{ - int ret = 0; - struct bdev_extension *ext; - - pr_info("Attach block device filter %d:%d", - MAJOR(bdev->bd_dev), MINOR(bdev->bd_dev)); - - ext = bdev_extension_append(bdev); - if (!ext) - return -ENOMEM; - - spin_lock(&ext->bd_filter_lock); - if (ext->bd_filter) { - pr_debug("filter busy. 0x%p", ext->bd_filter); - ret = -EBUSY; - } else - ext->bd_filter = flt; - spin_unlock(&ext->bd_filter_lock); - - if (!ret) - pr_info("Block device filter has been attached to %d:%d", - MAJOR(bdev->bd_dev), MINOR(bdev->bd_dev)); - return ret; -} -EXPORT_SYMBOL(bdev_filter_attach); - - -/* - * Only for livepatch version - * It is necessary for correct processing of the case when the block device - * was removed from the system. Unlike the upstream version, we have no way - * to handle device extension. - */ -int lp_bdev_filter_detach(const dev_t dev_id) -{ - struct bdev_extension *ext; - struct bdev_filter *flt; - - pr_info("Detach block device filter from %d:%d", - MAJOR(dev_id), MINOR(dev_id)); - - spin_lock(&bdev_extension_list_lock); - ext = bdev_extension_find(dev_id); - spin_unlock(&bdev_extension_list_lock); - if (!ext) - return -ENOENT; - - spin_lock(&ext->bd_filter_lock); - flt = ext->bd_filter; - if (flt) - ext->bd_filter = NULL; - spin_unlock(&ext->bd_filter_lock); - - if (!flt) - return -ENOENT; - - bdev_filter_put(flt); - pr_info("Block device filter has been detached from %d:%d", - MAJOR(dev_id), MINOR(dev_id)); - return 0; -} -EXPORT_SYMBOL(lp_bdev_filter_detach); - -/** - * bdev_filter_detach - Detach a filter from the block device. - * @bdev: - * block device. - * - * The filter should be added using the bdev_filter_attach() function. - * - * Return: - * 0 - OK - * -ENOENT - the filter was not found in the linked list - */ -void bdev_filter_detach(struct block_device *bdev) -{ - int ret = lp_bdev_filter_detach(bdev->bd_dev); - - WARN(ret != 0, "When trying to detach the filter from the block device, the filter was not found."); -} -EXPORT_SYMBOL(bdev_filter_detach); - -/** - * bdev_filter_get_by_bdev - Get filters context value. - * @bdev: - * Block device ID. - * - * Return pointer to &struct bdev_filter or NULL if the filter was not found. - * - * Necessary to lock list of filters by calling bdev_filter_read_lock(). - */ -struct bdev_filter *bdev_filter_get_by_bdev(struct block_device *bdev) -{ - struct bdev_extension *ext; - struct bdev_filter *flt = NULL; - - spin_lock(&bdev_extension_list_lock); -#if defined(HAVE_BI_BDISK) - ext = bdev_extension_find_part(bdev->bd_disk, bdev->bd_partno); -#else - ext = bdev_extension_find_bdev(bdev); -#endif - spin_unlock(&bdev_extension_list_lock); - if (!ext) - return NULL; - - spin_lock(&ext->bd_filter_lock); - flt = ext->bd_filter; - if (flt) - bdev_filter_get(flt); - spin_unlock(&ext->bd_filter_lock); - - return flt; -} -EXPORT_SYMBOL(bdev_filter_get_by_bdev); - -static inline bool bdev_filters_apply(struct bio *bio) -{ - bool completed; - struct bdev_filter *flt; - struct bdev_extension *ext; - - spin_lock(&bdev_extension_list_lock); -#if defined(HAVE_BI_BDISK) - ext = bdev_extension_find_part(bio->bi_disk, bio->bi_partno); -#else - ext = bdev_extension_find_bdev(bio->bi_bdev); -#endif - spin_unlock(&bdev_extension_list_lock); - if (!ext) - return false; - - spin_lock(&ext->bd_filter_lock); - flt = ext->bd_filter; - if (flt) - bdev_filter_get(flt); - spin_unlock(&ext->bd_filter_lock); - - if (!flt) - return false; - - if (bio->bi_opf & REQ_NOWAIT) { - if (!percpu_down_read_trylock(&flt->submit_lock)) { - bio_wouldblock_error(bio); - completed = true; - goto out; - } - } else - percpu_down_read(&flt->submit_lock); - - completed = flt->fops->submit_bio(bio, flt); - - percpu_up_read(&flt->submit_lock); -out: - bdev_filter_put(flt); - - return completed; -} - -#ifdef CONFIG_X86 -#define CALL_INSTRUCTION_LENGTH 5 -#else -#error "Current CPU is not supported yet" -#endif - -#if defined(HAVE_QC_SUBMIT_BIO_NOACCT) -blk_qc_t (*submit_bio_noacct_notrace)(struct bio *) = - (blk_qc_t(*)(struct bio *))((unsigned long)(submit_bio_noacct) + - CALL_INSTRUCTION_LENGTH); -#elif defined(HAVE_VOID_SUBMIT_BIO_NOACCT) -void (*submit_bio_noacct_notrace)(struct bio *) = - (void (*)(struct bio *))((unsigned long)(submit_bio_noacct) + - CALL_INSTRUCTION_LENGTH); -#else -#error "Your kernel is too old for this module." -#endif -EXPORT_SYMBOL(submit_bio_noacct_notrace); - -#if defined(HAVE_QC_SUBMIT_BIO_NOACCT) -static blk_qc_t notrace submit_bio_noacct_handler(struct bio *bio) -#else -static void notrace submit_bio_noacct_handler(struct bio *bio) -#endif -{ - if (!current->bio_list) { - if (bdev_filters_apply(bio)) { -#if defined(HAVE_QC_SUBMIT_BIO_NOACCT) - return BLK_QC_T_NONE; -#elif defined(HAVE_VOID_SUBMIT_BIO_NOACCT) - return; -#else -#error "Your kernel is too old for this module." -#endif - } - } - -#if defined(HAVE_QC_SUBMIT_BIO_NOACCT) - return submit_bio_noacct_notrace(bio); -#elif defined(HAVE_VOID_SUBMIT_BIO_NOACCT) - submit_bio_noacct_notrace(bio); -#else -#error "Your kernel is too old for this module." -#endif -} - -#ifdef CONFIG_LIVEPATCH -#pragma message("livepatch used") - -static struct klp_func funcs[] = { - { - .old_name = "submit_bio_noacct", - .new_func = submit_bio_noacct_handler, - }, - { 0 } -}; - -static struct klp_object objs[] = { - { - /* name being NULL means vmlinux */ - .funcs = funcs, - }, - { 0 } -}; - -static struct klp_patch patch = { - .mod = THIS_MODULE, - .objs = objs, -}; - -static int __init lp_filter_init(void) -{ - return klp_enable_patch(&patch); -} - -/* - * For standalone only: - * Before unload module all filters should be detached and livepatch are - * disabled. - * - * echo 0 > /sys/kernel/livepatch/bdev_filter/enabled - */ -static void __exit lp_filter_done(void) -{ - struct bdev_extension *ext; - - while ((ext = list_first_entry_or_null(&bdev_extension_list, - struct bdev_extension, link))) { - list_del(&ext->link); - kfree(ext); - } -} -module_init(lp_filter_init); -module_exit(lp_filter_done); -MODULE_INFO(livepatch, "Y"); - -#elif defined(CONFIG_HAVE_DYNAMIC_FTRACE_WITH_ARGS) -#pragma message("ftrace filter used") - -static notrace void ftrace_handler_submit_bio_noacct( - unsigned long ip, unsigned long parent_ip, struct ftrace_ops *fops, -#ifdef HAVE_FTRACE_REGS - struct ftrace_regs *fregs -#else - struct pt_regs *regs -#endif - ) -{ -#if defined(HAVE_FTRACE_REGS_SET_INSTRUCTION_POINTER) - ftrace_regs_set_instruction_pointer(fregs, (unsigned long)submit_bio_noacct_handler); -#elif defined(HAVE_FTRACE_REGS) - ftrace_instruction_pointer_set(fregs, (unsigned long)submit_bio_noacct_handler); -#else - instruction_pointer_set(regs, (unsigned long)submit_bio_noacct_handler); -#endif -} - -unsigned char* funcname_submit_bio_noacct = "submit_bio_noacct"; -static struct ftrace_ops ops_submit_bio_noacct = { - .func = ftrace_handler_submit_bio_noacct, - .flags = FTRACE_OPS_FL_DYNAMIC | -#ifndef CONFIG_HAVE_DYNAMIC_FTRACE_WITH_ARGS - FTRACE_OPS_FL_SAVE_REGS | -#endif - FTRACE_OPS_FL_IPMODIFY | - FTRACE_OPS_FL_PERMANENT, -}; - -static int __init trace_filter_init(void) -{ - int ret = 0; - - ret = ftrace_set_filter(&ops_submit_bio_noacct, funcname_submit_bio_noacct, strlen(funcname_submit_bio_noacct), 0); - if (ret) { - pr_err("Failed to set ftrace filter for function '%s' (%d)\n", funcname_submit_bio_noacct, ret); - goto err; - } - - ret = register_ftrace_function(&ops_submit_bio_noacct); - if (ret) { - pr_err("Failed to register ftrace handler (%d)\n", ret); - ftrace_set_filter(&ops_submit_bio_noacct, NULL, 0, 1); - goto err; - } - -err: - return ret; -} - -static void __exit trace_filter_done(void) -{ - struct bdev_extension *ext; - - unregister_ftrace_function(&ops_submit_bio_noacct); - - spin_lock(&bdev_extension_list_lock); - while ((ext = list_first_entry_or_null(&bdev_extension_list, - struct bdev_extension, link))) { - list_del(&ext->link); - kfree(ext); - } - spin_unlock(&bdev_extension_list_lock); -} - -module_init(trace_filter_init); -module_exit(trace_filter_done); -#else -#error "The bdevfilter cannot be used for the current kernels configuration" -#endif - -MODULE_DESCRIPTION("Block Device Filter kernel module"); -MODULE_VERSION(VERSION_STR); -MODULE_AUTHOR("Veeam Software Group GmbH"); -MODULE_LICENSE("GPL"); -/* Allow to be loaded on OpenSUSE/SLES */ -MODULE_INFO(supported, "external"); diff --git a/module/bdevfilter.h b/module/bdevfilter.h deleted file mode 100644 index c907bb10..00000000 --- a/module/bdevfilter.h +++ /dev/null @@ -1,81 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef __LINUX_BDEVFILTER_H -#define __LINUX_BDEVFILTER_H - -#include -#include -#include -#include - -struct bdev_filter; -struct bdev_filter_operations { - bool (*submit_bio)(struct bio *bio, struct bdev_filter *flt); - /* - bool (*read_page_cb)(struct block_device *bdev, - sector_t sector, struct page *page, - struct bdev_filter *flt); - bool (*write_page_cb)(struct block_device *bdev, - sector_t sector, struct page *page, - struct bdev_filter *flt); - */ - void (*release)(struct bdev_filter *flt); -}; - -/** - * struct bdev_filter - Description of the block device filter. - * @kref: - * - * @fops: - * - */ -struct bdev_filter { - struct kref kref; - const struct bdev_filter_operations *fops; - struct percpu_rw_semaphore submit_lock; -}; - -static inline void bdev_filter_init(struct bdev_filter *flt, - const struct bdev_filter_operations *fops) -{ - kref_init(&flt->kref); - flt->fops = fops; - percpu_init_rwsem(&flt->submit_lock); -}; -void bdev_filter_free(struct kref *kref); - -int bdev_filter_attach(struct block_device *bdev, struct bdev_filter *flt); -void bdev_filter_detach(struct block_device *bdev); -struct bdev_filter *bdev_filter_get_by_bdev(struct block_device *bdev); -static inline void bdev_filter_get(struct bdev_filter *flt) -{ - kref_get(&flt->kref); -}; -static inline void bdev_filter_put(struct bdev_filter *flt) -{ - if (likely(flt)) - kref_put(&flt->kref, bdev_filter_free); -}; - -/* Only for livepatch version */ -int lp_bdev_filter_detach(const dev_t dev_id); - -#if defined(HAVE_QC_SUBMIT_BIO_NOACCT) -extern blk_qc_t (*submit_bio_noacct_notrace)(struct bio *); -#elif defined(HAVE_VOID_SUBMIT_BIO_NOACCT) -extern void (*submit_bio_noacct_notrace)(struct bio *); -#endif - - -static inline -void bdevfilter_freeze_queue(struct bdev_filter *flt) -{ - pr_debug("Freeze filtered queue.\n"); - percpu_down_write(&flt->submit_lock); -}; -static inline -void bdevfilter_unfreeze_queue(struct bdev_filter *flt) -{ - percpu_up_write(&flt->submit_lock); - pr_debug("Filtered queue was unfrozen.\n"); -}; -#endif /* __LINUX_BDEVFILTER_H */ diff --git a/module/blksnap.h b/module/blksnap.h deleted file mode 100644 index 0d7b14a5..00000000 --- a/module/blksnap.h +++ /dev/null @@ -1,665 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -#ifndef _UAPI_LINUX_BLK_SNAP_H -#define _UAPI_LINUX_BLK_SNAP_H - -#include - -#define BLK_SNAP_CTL "blksnap-control" -#define BLK_SNAP_IMAGE_NAME "blksnap-image" -#define BLK_SNAP 'V' - -#ifdef BLK_SNAP_MODIFICATION -#define IOCTL_MOD 32 -#endif - -enum blk_snap_ioctl { - /* - * Service controls - */ - blk_snap_ioctl_version, - /* - * Change tracking controls - */ - blk_snap_ioctl_tracker_remove, - blk_snap_ioctl_tracker_collect, - blk_snap_ioctl_tracker_read_cbt_map, - blk_snap_ioctl_tracker_mark_dirty_blocks, - /* - * Snapshot controls - */ - blk_snap_ioctl_snapshot_create, - blk_snap_ioctl_snapshot_destroy, - blk_snap_ioctl_snapshot_append_storage, - blk_snap_ioctl_snapshot_take, - blk_snap_ioctl_snapshot_collect, - blk_snap_ioctl_snapshot_collect_images, - blk_snap_ioctl_snapshot_wait_event, -#ifdef BLK_SNAP_MODIFICATION - /* - * Additional controls for any standalone modification - */ - blk_snap_ioctl_mod = IOCTL_MOD, - blk_snap_ioctl_setlog, - blk_snap_ioctl_get_sector_state, - blk_snap_ioctl_end_mod -#endif -}; - -/** - * DOC: Service controls - */ - -/** - * struct blk_snap_version - Module version. - * @major: - * Version major part. - * @minor: - * Version minor part. - * @revision: - * Revision number. - * @build: - * Build number. Should be zero. - */ -struct blk_snap_version { - __u16 major; - __u16 minor; - __u16 revision; - __u16 build; -}; - -/** - * define IOCTL_BLK_SNAP_VERSION - Get module version. - * - * The version may increase when the API changes. But linking the user space - * behavior to the version code does not seem to be a good idea. - * To ensure backward compatibility, API changes should be made by adding new - * ioctl without changing the behavior of existing ones. The version should be - * used for logs. - * - * Return: 0 if succeeded, negative errno otherwise. - */ -#define IOCTL_BLK_SNAP_VERSION \ - _IOW(BLK_SNAP, blk_snap_ioctl_version, struct blk_snap_version) - -#ifdef BLK_SNAP_MODIFICATION - -enum blk_snap_compat_flags { - blk_snap_compat_flag_debug_sector_state, - blk_snap_compat_flag_setlog, - /* - * Reserved for new features - */ - blk_snap_compat_flags_end -}; -static_assert(blk_snap_compat_flags_end <= 64, - "There are too many compatibility flags."); - -#define BLK_SNAP_MOD_NAME_LIMIT 32 - -/** - * struct blk_snap_modification - Result for &IOCTL_BLK_SNAP_VERSION control. - * - * @compatibility_flags: - * [TBD] Reserved for new modification specific features. - * @name: - * Name of modification of the module blksnap (fork name, for example). - * It's should be empty string for upstream module. - */ -struct blk_snap_mod { - __u64 compatibility_flags; - __u8 name[BLK_SNAP_MOD_NAME_LIMIT]; -}; - -/** - * IOCTL_BLK_SNAP_MOD - Get modification name and compatibility flags. - * - * Linking the product behavior to the version code does not seem to me a very - * good idea. However, such an ioctl is good for checking that the module has - * loaded and is responding to requests. - * - * The compatibility flags allows to safely extend the functionality of the - * module. When the blk_snap kernel module receives new ioctl it will be - * enough to add a bit. - * - * The name of the modification can be used by the authors of forks and branches - * of the original module. The module in upstream have not any modifications. - */ -#define IOCTL_BLK_SNAP_MOD \ - _IOW(BLK_SNAP, blk_snap_ioctl_mod, struct blk_snap_mod) - -#endif - -/** - * DOC: Interface for the change tracking mechanism - */ - -/** - * struct blk_snap_dev - Block device ID. - * @mj: - * Device ID major part. - * @mn: - * Device ID minor part. - * - * In user space and in kernel space, block devices are encoded differently. - * We need to enter our own type to guarantee the correct transmission of the - * major and minor parts. - */ -struct blk_snap_dev { - __u32 mj; - __u32 mn; -}; - -/** - * struct blk_snap_tracker_remove - Input argument for the - * &IOCTL_BLK_SNAP_TRACKER_REMOVE control. - * @dev_id: - * Device ID. - */ -struct blk_snap_tracker_remove { - struct blk_snap_dev dev_id; -}; - -/** - * define IOCTL_BLK_SNAP_TRACKER_REMOVE - Remove a device from tracking. - * - * Removes the device from tracking changes. Adding a device for tracking is - * performed when creating a snapshot that includes this block device. - * - * Return: 0 if succeeded, negative errno otherwise. - */ -#define IOCTL_BLK_SNAP_TRACKER_REMOVE \ - _IOW(BLK_SNAP, blk_snap_ioctl_tracker_remove, \ - struct blk_snap_tracker_remove) - -/** - * struct blk_snap_uuid - Unique 16-byte identifier. - * @b: - * An array of 16 bytes. - */ -struct blk_snap_uuid { - __u8 b[16]; -}; - -/** - * struct blk_snap_cbt_info - Information about change tracking for a block - * device. - * @dev_id: - * Device ID. - * @blk_size: - * Block size in bytes. - * @device_capacity: - * Device capacity in bytes. - * @blk_count: - * Number of blocks. - * @generation_id: - * Unique identifier of change tracking generation. - * @snap_number: - * Current changes number. - */ -struct blk_snap_cbt_info { - struct blk_snap_dev dev_id; - __u32 blk_size; - __u64 device_capacity; - __u32 blk_count; - struct blk_snap_uuid generation_id; - __u8 snap_number; -}; - -/** - * struct blk_snap_tracker_collect - Argument for the - * &IOCTL_BLK_SNAP_TRACKER_COLLECT control. - * @count: - * Size of &blk_snap_tracker_collect.cbt_info_array. - * @cbt_info_array: - * Pointer to the array for output. - */ -struct blk_snap_tracker_collect { - __u32 count; - struct blk_snap_cbt_info *cbt_info_array; -}; - -/** - * define IOCTL_BLK_SNAP_TRACKER_COLLECT - Collect all tracked devices. - * - * Getting information about all devices under tracking. - * - * If in &blk_snap_tracker_collect.count is less than required to - * store the &blk_snap_tracker_collect.cbt_info_array, the array is not filled, - * and the ioctl returns the required count for - * &blk_snap_tracker_collect.cbt_info_array. - * - * So, it is recommended to call the ioctl twice. The first call with an null - * pointer &blk_snap_tracker_collect.cbt_info_array and a zero value in - * &blk_snap_tracker_collect.count. It will set the required array size in - * &blk_snap_tracker_collect.count. The second call with a pointer - * &blk_snap_tracker_collect.cbt_info_array to an array of the required size - * will allow to get information about the tracked block devices. - * - * Return: 0 if succeeded, negative errno otherwise. - */ -#define IOCTL_BLK_SNAP_TRACKER_COLLECT \ - _IOW(BLK_SNAP, blk_snap_ioctl_tracker_collect, \ - struct blk_snap_tracker_collect) - -/** - * struct blk_snap_tracker_read_cbt_bitmap - Argument for the - * &IOCTL_BLK_SNAP_TRACKER_READ_CBT_MAP control. - * @dev_id: - * Device ID. - * @offset: - * Offset from the beginning of the CBT bitmap in bytes. - * @length: - * Size of @buff in bytes. - * @buff: - * Pointer to the buffer for output. - */ -struct blk_snap_tracker_read_cbt_bitmap { - struct blk_snap_dev dev_id; - __u32 offset; - __u32 length; - __u8 *buff; -}; - -/** - * define IOCTL_BLK_SNAP_TRACKER_READ_CBT_MAP - Read the CBT map. - * - * Allows to read the table of changes. - * - * The size of the table can be quite large. Thus, the table is read in a loop, - * in each cycle of which the next offset is set to - * &blk_snap_tracker_read_cbt_bitmap.offset. - * - * Return: a count of bytes read if succeeded, negative errno otherwise. - */ -#define IOCTL_BLK_SNAP_TRACKER_READ_CBT_MAP \ - _IOR(BLK_SNAP, blk_snap_ioctl_tracker_read_cbt_map, \ - struct blk_snap_tracker_read_cbt_bitmap) - -/** - * struct blk_snap_block_range - Element of array for - * &struct blk_snap_tracker_mark_dirty_blocks. - * @sector_offset: - * Offset from the beginning of the disk in sectors. - * @sector_count: - * Number of sectors. - */ -struct blk_snap_block_range { - __u64 sector_offset; - __u64 sector_count; -}; - -/** - * struct blk_snap_tracker_mark_dirty_blocks - Argument for the - * &IOCTL_BLK_SNAP_TRACKER_MARK_DIRTY_BLOCKS control. - * @dev_id: - * Device ID. - * @count: - * Size of @dirty_blocks_array in the number of - * &struct blk_snap_block_range. - * @dirty_blocks_array: - * Pointer to the array of &struct blk_snap_block_range. - */ -struct blk_snap_tracker_mark_dirty_blocks { - struct blk_snap_dev dev_id; - __u32 count; - struct blk_snap_block_range *dirty_blocks_array; -}; - -/** - * define IOCTL_BLK_SNAP_TRACKER_MARK_DIRTY_BLOCKS - Set dirty blocks in the - * CBT map. - * - * There are cases when some blocks need to be marked as changed. - * This ioctl allows to do this. - * - * Return: 0 if succeeded, negative errno otherwise. - */ -#define IOCTL_BLK_SNAP_TRACKER_MARK_DIRTY_BLOCKS \ - _IOR(BLK_SNAP, blk_snap_ioctl_tracker_mark_dirty_blocks, \ - struct blk_snap_tracker_mark_dirty_blocks) - -/** - * DOC: Interface for managing snapshots - */ - -/** - * struct blk_snap_snapshot_create - Argument for the - * &IOCTL_BLK_SNAP_SNAPSHOT_CREATE control. - * @count: - * Size of @dev_id_array in the number of &struct blk_snap_dev. - * @dev_id_array: - * Pointer to the array of &struct blk_snap_dev. - * @id: - * Return ID of the created snapshot. - */ -struct blk_snap_snapshot_create { - __u32 count; - struct blk_snap_dev *dev_id_array; - struct blk_snap_uuid id; -}; - -/** - * define IOCTL_BLK_SNAP_SNAPSHOT_CREATE - Create snapshot. - * - * Creates a snapshot structure in the memory and allocates an identifier for - * it. Further interaction with the snapshot is possible by this identifier. - * A snapshot is created for several block devices at once. - * Several snapshots can be created at the same time, but with the condition - * that one block device can only be included in one snapshot. - * - * Return: 0 if succeeded, negative errno otherwise. - */ -#define IOCTL_BLK_SNAP_SNAPSHOT_CREATE \ - _IOW(BLK_SNAP, blk_snap_ioctl_snapshot_create, \ - struct blk_snap_snapshot_create) - -/** - * struct blk_snap_snapshot_destroy - Argument for the - * &IOCTL_BLK_SNAP_SNAPSHOT_DESTROY control. - * @id: - * Snapshot ID. - */ -struct blk_snap_snapshot_destroy { - struct blk_snap_uuid id; -}; - -/** - * define IOCTL_BLK_SNAP_SNAPSHOT_DESTROY - Release and destroy the snapshot. - * - * Destroys snapshot with &blk_snap_snapshot_destroy.id. This leads to the - * deletion of all block device images of the snapshot. The difference storage - * is being released. But the change tracker keeps tracking. - * - * Return: 0 if succeeded, negative errno otherwise. - */ -#define IOCTL_BLK_SNAP_SNAPSHOT_DESTROY \ - _IOR(BLK_SNAP, blk_snap_ioctl_snapshot_destroy, \ - struct blk_snap_snapshot_destroy) - -/** - * struct blk_snap_snapshot_append_storage - Argument for the - * &IOCTL_BLK_SNAP_SNAPSHOT_APPEND_STORAGE control. - * @id: - * Snapshot ID. - * @dev_id: - * Device ID. - * @count: - * Size of @ranges in the number of &struct blk_snap_block_range. - * @ranges: - * Pointer to the array of &struct blk_snap_block_range. - */ -struct blk_snap_snapshot_append_storage { - struct blk_snap_uuid id; - struct blk_snap_dev dev_id; - __u32 count; - struct blk_snap_block_range *ranges; -}; - -/** - * define IOCTL_BLK_SNAP_SNAPSHOT_APPEND_STORAGE - Append storage to the - * difference storage of the snapshot. - * - * The snapshot difference storage can be set either before or after creating - * the snapshot images. This allows to dynamically expand the difference - * storage while holding the snapshot. - * - * Return: 0 if succeeded, negative errno otherwise. - */ -#define IOCTL_BLK_SNAP_SNAPSHOT_APPEND_STORAGE \ - _IOW(BLK_SNAP, blk_snap_ioctl_snapshot_append_storage, \ - struct blk_snap_snapshot_append_storage) - -/** - * struct blk_snap_snapshot_take - Argument for the - * &IOCTL_BLK_SNAP_SNAPSHOT_TAKE control. - * @id: - * Snapshot ID. - */ -struct blk_snap_snapshot_take { - struct blk_snap_uuid id; -}; - -/** - * define IOCTL_BLK_SNAP_SNAPSHOT_TAKE - Take snapshot. - * - * Creates snapshot images of block devices and switches change trackers tables. - * The snapshot must be created before this call, and the areas of block - * devices should be added to the difference storage. - * - * Return: 0 if succeeded, negative errno otherwise. - */ -#define IOCTL_BLK_SNAP_SNAPSHOT_TAKE \ - _IOR(BLK_SNAP, blk_snap_ioctl_snapshot_take, \ - struct blk_snap_snapshot_take) - -/** - * struct blk_snap_snapshot_collect - Argument for the - * &IOCTL_BLK_SNAP_SNAPSHOT_COLLECT control. - * @count: - * Size of &blk_snap_snapshot_collect.ids in the number of 16-byte UUID. - * @ids: - * Pointer to the array with the snapshot ID for output. - */ -struct blk_snap_snapshot_collect { - __u32 count; - struct blk_snap_uuid *ids; -}; - -/** - * define IOCTL_BLK_SNAP_SNAPSHOT_COLLECT - Get collection of created snapshots. - * - * Multiple snapshots can be created at the same time. This allows for one - * system to create backups for different data with a independent schedules. - * - * If in &blk_snap_snapshot_collect.count is less than required to store the - * &blk_snap_snapshot_collect.ids, the array is not filled, and the ioctl - * returns the required count for &blk_snap_snapshot_collect.ids. - * - * So, it is recommended to call the ioctl twice. The first call with an null - * pointer &blk_snap_snapshot_collect.ids and a zero value in - * &blk_snap_snapshot_collect.count. It will set the required array size in - * &blk_snap_snapshot_collect.count. The second call with a pointer - * &blk_snap_snapshot_collect.ids to an array of the required size will allow to - * get collection of active snapshots. - * - * Return: 0 if succeeded, -ENODATA if there is not enough space in the array - * to store collection of active snapshots, or negative errno otherwise. - */ -#define IOCTL_BLK_SNAP_SNAPSHOT_COLLECT \ - _IOW(BLK_SNAP, blk_snap_ioctl_snapshot_collect, \ - struct blk_snap_snapshot_collect) -/** - * struct blk_snap_image_info - Associates the original device in the snapshot - * and the corresponding snapshot image. - * @orig_dev_id: - * Device ID. - * @image_dev_id: - * Image ID. - */ -struct blk_snap_image_info { - struct blk_snap_dev orig_dev_id; - struct blk_snap_dev image_dev_id; -}; - -/** - * struct blk_snap_snapshot_collect_images - Argument for the - * &IOCTL_BLK_SNAP_SNAPSHOT_COLLECT_IMAGES control. - * @id: - * Snapshot ID. - * @count: - * Size of &image_info_array in the number of &struct blk_snap_image_info. - * @image_info_array: - * Pointer to the array for output. - */ -struct blk_snap_snapshot_collect_images { - struct blk_snap_uuid id; - __u32 count; - struct blk_snap_image_info *image_info_array; -}; - -/** - * define IOCTL_BLK_SNAP_SNAPSHOT_COLLECT_IMAGES - Get a collection of devices - * and their snapshot images. - * - * While holding the snapshot, this ioctl allows to get a table of - * correspondences of the original devices and their snapshot images. - * - * If &blk_snap_snapshot_collect_images.count is less than required to store the - * &blk_snap_snapshot_collect_images.image_info_array, the array is not filled, - * and the ioctl returns the required count for - * &blk_snap_snapshot_collect.image_info_array. - * - * So, it is recommended to call the ioctl twice. The first call with an null - * pointer &blk_snap_snapshot_collect_images.image_info_array and a zero value - * in &blk_snap_snapshot_collect_images.count. It will set the required array - * size in &blk_snap_snapshot_collect_images.count. The second call with a - * pointer &blk_snap_snapshot_collect_images.image_info_array to an array of the - * required size will allow to get collection of devices and their snapshot - * images. - * - * Return: 0 if succeeded, -ENODATA if there is not enough space in the array - * to store collection of devices and their snapshot images, negative errno - * otherwise. - */ -#define IOCTL_BLK_SNAP_SNAPSHOT_COLLECT_IMAGES \ - _IOW(BLK_SNAP, blk_snap_ioctl_snapshot_collect_images, \ - struct blk_snap_snapshot_collect_images) - -/** - * enum blk_snap_event_codes - Variants of event codes. - * - * @blk_snap_event_code_low_free_space: - * Low free space in difference storage event. - * If the free space in the difference storage is reduced to the specified - * limit, the module generates this event. - * @blk_snap_event_code_corrupted: - * Snapshot image is corrupted event. - * If a chunk could not be allocated when trying to save data to the - * difference storage, this event is generated. However, this does not mean - * that the backup process was interrupted with an error. If the snapshot - * image has been read to the end by this time, the backup process is - * considered successful. - */ -enum blk_snap_event_codes { - blk_snap_event_code_low_free_space, - blk_snap_event_code_corrupted, -}; - -/** - * struct blk_snap_snapshot_event - Argument for the - * &IOCTL_BLK_SNAP_SNAPSHOT_WAIT_EVENT control. - * @id: - * Snapshot ID. - * @timeout_ms: - * Timeout for waiting in milliseconds. - * @time_label: - * Timestamp of the received event. - * @code: - * Code of the received event &enum blk_snap_event_codes. - * @data: - * The received event body. - */ -struct blk_snap_snapshot_event { - struct blk_snap_uuid id; - __u32 timeout_ms; - __u32 code; - __s64 time_label; - __u8 data[4096 - 32]; -}; -static_assert(sizeof(struct blk_snap_snapshot_event) == 4096, - "The size struct blk_snap_snapshot_event should be equal to the size of the page."); - -/** - * define IOCTL_BLK_SNAP_SNAPSHOT_WAIT_EVENT - Wait and get the event from the - * snapshot. - * - * While holding the snapshot, the kernel module can transmit information about - * changes in its state in the form of events to the user level. - * It is very important to receive these events as quickly as possible, so the - * user's thread is in the state of interruptable sleep. - * - * Return: 0 if succeeded, negative errno otherwise. - */ -#define IOCTL_BLK_SNAP_SNAPSHOT_WAIT_EVENT \ - _IOW(BLK_SNAP, blk_snap_ioctl_snapshot_wait_event, \ - struct blk_snap_snapshot_event) - -/** - * struct blk_snap_event_low_free_space - Data for the - * &blk_snap_event_code_low_free_space event. - * @requested_nr_sect: - * The required number of sectors. - */ -struct blk_snap_event_low_free_space { - __u64 requested_nr_sect; -}; - -/** - * struct blk_snap_event_corrupted - Data for the - * &blk_snap_event_code_corrupted event. - * @orig_dev_id: - * Device ID. - * @err_code: - * Error code. - */ -struct blk_snap_event_corrupted { - struct blk_snap_dev orig_dev_id; - __s32 err_code; -}; - - -#ifdef BLK_SNAP_MODIFICATION -/** - * @tz_minuteswest: - * Time zone offset in minutes. - * The system time is in UTC. In order for the module to write local time - * to the log, its offset should be specified. - * @level: - * 0 - disable logging to file - * 3 - only error messages - * 4 - log warnings - * 6 - log info messages - * 7 - log debug messages - * @filepath_size: - * Count of bytes in &filepath. - * @filename: - * Full path for log file. - */ -struct blk_snap_setlog { - __s32 tz_minuteswest; - __u32 level; - __u32 filepath_size; - __u8 *filepath; -}; - -/** - * - */ -#define IOCTL_BLK_SNAP_SETLOG \ - _IOW(BLK_SNAP, blk_snap_ioctl_setlog, struct blk_snap_setlog) - -/** - * - */ -struct blk_snap_sector_state { - __u8 snap_number_prev; - __u8 snap_number_curr; - __u32 chunk_state; -}; - -struct blk_snap_get_sector_state { - struct blk_snap_dev image_dev_id; - __u64 sector; - struct blk_snap_sector_state state; -}; - -/** - * - */ -#define IOCTL_BLK_SNAP_GET_SECTOR_STATE \ - _IOW(BLK_SNAP, blk_snap_ioctl_get_sector_state, \ - struct blk_snap_get_sector_state) - -#endif /* BLK_SNAP_MODIFICATION */ - -#endif /* _UAPI_LINUX_BLK_SNAP_H */ diff --git a/module/cbt_map.c b/module/cbt_map.c deleted file mode 100644 index 9bff94e3..00000000 --- a/module/cbt_map.c +++ /dev/null @@ -1,319 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#define pr_fmt(fmt) KBUILD_MODNAME "-cbt_map: " fmt - -#include -#include -#ifdef STANDALONE_BDEVFILTER -#include "blksnap.h" -#else -#include -#endif -#include "memory_checker.h" -#include "cbt_map.h" -#include "log.h" - -extern int tracking_block_minimum_shift; -extern int tracking_block_maximum_count; - -#ifndef HAVE_BDEV_NR_SECTORS -static inline sector_t bdev_nr_sectors(struct block_device *bdev) -{ - return i_size_read(bdev->bd_inode) >> 9; -}; -#endif - -static inline unsigned long long count_by_shift(sector_t capacity, - unsigned long long shift) -{ - sector_t blk_size = 1ull << (shift - SECTOR_SHIFT); - - return round_up(capacity, blk_size) / blk_size; -} - -static void cbt_map_calculate_block_size(struct cbt_map *cbt_map) -{ - unsigned long long shift; - unsigned long long count; - - /* - * The size of the tracking block is calculated based on the size of the disk - * so that the CBT table does not exceed a reasonable size. - */ - shift = tracking_block_minimum_shift; - count = count_by_shift(cbt_map->device_capacity, shift); - - while (count > tracking_block_maximum_count) { - shift = shift << 1; - count = count_by_shift(cbt_map->device_capacity, shift); - } - - cbt_map->blk_size_shift = shift; - cbt_map->blk_count = count; -} - -static int cbt_map_allocate(struct cbt_map *cbt_map) -{ - unsigned char *read_map = NULL; - unsigned char *write_map = NULL; - size_t size = cbt_map->blk_count; - - pr_debug("Allocate CBT map of %zu blocks\n", size); - - if (cbt_map->read_map || cbt_map->write_map) - return -EINVAL; - - read_map = __vmalloc(size, GFP_NOIO | __GFP_ZERO); - if (!read_map) - return -ENOMEM; - - write_map = __vmalloc(size, GFP_NOIO | __GFP_ZERO); - if (!write_map) { - vfree(read_map); - return -ENOMEM; - } - - cbt_map->read_map = read_map; - memory_object_inc(memory_object_cbt_buffer); - cbt_map->write_map = write_map; - memory_object_inc(memory_object_cbt_buffer); - - cbt_map->snap_number_previous = 0; - cbt_map->snap_number_active = 1; - generate_random_uuid(cbt_map->generation_id.b); - cbt_map->is_corrupted = false; - - return 0; -} - -static void cbt_map_deallocate(struct cbt_map *cbt_map) -{ - cbt_map->is_corrupted = false; - - if (cbt_map->read_map) { - memory_object_dec(memory_object_cbt_buffer); - vfree(cbt_map->read_map); - cbt_map->read_map = NULL; - } - - if (cbt_map->write_map) { - memory_object_dec(memory_object_cbt_buffer); - vfree(cbt_map->write_map); - cbt_map->write_map = NULL; - } -} - -int cbt_map_reset(struct cbt_map *cbt_map, sector_t device_capacity) -{ - cbt_map_deallocate(cbt_map); - - cbt_map->device_capacity = device_capacity; - cbt_map_calculate_block_size(cbt_map); - - return cbt_map_allocate(cbt_map); -} - -static inline void cbt_map_destroy(struct cbt_map *cbt_map) -{ - pr_debug("CBT map destroy\n"); - - cbt_map_deallocate(cbt_map); - kfree(cbt_map); - memory_object_dec(memory_object_cbt_map); -} - -struct cbt_map *cbt_map_create(struct block_device *bdev) -{ - struct cbt_map *cbt_map = NULL; - int ret; - - pr_debug("CBT map create\n"); - - cbt_map = kzalloc(sizeof(struct cbt_map), GFP_KERNEL); - if (cbt_map == NULL) - return NULL; - memory_object_inc(memory_object_cbt_map); - - cbt_map->device_capacity = bdev_nr_sectors(bdev); - cbt_map_calculate_block_size(cbt_map); - - ret = cbt_map_allocate(cbt_map); - if (ret) { - pr_err("Failed to create tracker. errno=%d\n", abs(ret)); - cbt_map_destroy(cbt_map); - return NULL; - } - - spin_lock_init(&cbt_map->locker); - kref_init(&cbt_map->kref); - cbt_map->is_corrupted = false; - - return cbt_map; -} - -void cbt_map_destroy_cb(struct kref *kref) -{ - cbt_map_destroy(container_of(kref, struct cbt_map, kref)); -} - -void cbt_map_switch(struct cbt_map *cbt_map) -{ - pr_debug("CBT map switch\n"); - spin_lock(&cbt_map->locker); - - cbt_map->snap_number_previous = cbt_map->snap_number_active; - ++cbt_map->snap_number_active; - if (cbt_map->snap_number_active == 256) { - cbt_map->snap_number_active = 1; - - memset(cbt_map->write_map, 0, cbt_map->blk_count); - - generate_random_uuid(cbt_map->generation_id.b); - - pr_debug("CBT reset\n"); - } else - memcpy(cbt_map->read_map, cbt_map->write_map, cbt_map->blk_count); - spin_unlock(&cbt_map->locker); -} - -static inline int _cbt_map_set(struct cbt_map *cbt_map, sector_t sector_start, - sector_t sector_cnt, u8 snap_number, - unsigned char *map) -{ - int res = 0; - u8 num; - size_t inx; - size_t cbt_block_first = (size_t)( - sector_start >> (cbt_map->blk_size_shift - SECTOR_SHIFT)); - size_t cbt_block_last = (size_t)( - (sector_start + sector_cnt - 1) >> - (cbt_map->blk_size_shift - SECTOR_SHIFT)); - - for (inx = cbt_block_first; inx <= cbt_block_last; ++inx) { - if (unlikely(inx >= cbt_map->blk_count)) { - pr_err("Block index is too large.\n"); - pr_err("Block #%zu was demanded, map size %zu blocks.\n", - inx, cbt_map->blk_count); - res = -EINVAL; - break; - } - - num = map[inx]; - if (num < snap_number) - map[inx] = snap_number; - } - return res; -} - -int cbt_map_set(struct cbt_map *cbt_map, sector_t sector_start, - sector_t sector_cnt) -{ - int res; - - spin_lock(&cbt_map->locker); - if (unlikely(cbt_map->is_corrupted)) { - spin_unlock(&cbt_map->locker); - return -EINVAL; - } - res = _cbt_map_set(cbt_map, sector_start, sector_cnt, - (u8)cbt_map->snap_number_active, cbt_map->write_map); - if (unlikely(res)) - cbt_map->is_corrupted = true; - - spin_unlock(&cbt_map->locker); - - return res; -} - -int cbt_map_set_both(struct cbt_map *cbt_map, sector_t sector_start, - sector_t sector_cnt) -{ - int res; - - spin_lock(&cbt_map->locker); - if (unlikely(cbt_map->is_corrupted)) { - spin_unlock(&cbt_map->locker); - return -EINVAL; - } - res = _cbt_map_set(cbt_map, sector_start, sector_cnt, - (u8)cbt_map->snap_number_active, cbt_map->write_map); - if (!res) - res = _cbt_map_set(cbt_map, sector_start, sector_cnt, - (u8)cbt_map->snap_number_previous, - cbt_map->read_map); - spin_unlock(&cbt_map->locker); - - return res; -} - -size_t cbt_map_read_to_user(struct cbt_map *cbt_map, char __user *user_buff, - size_t offset, size_t size) -{ - size_t readed = 0; - size_t left_size; - size_t real_size = min((cbt_map->blk_count - offset), size); - - if (unlikely(cbt_map->is_corrupted)) { - pr_err("CBT table was corrupted\n"); - return -EFAULT; - } - - left_size = copy_to_user(user_buff, cbt_map->read_map, real_size); - - if (left_size == 0) - readed = real_size; - else { - pr_err("Not all CBT data was read. Left [%zu] bytes\n", - left_size); - readed = real_size - left_size; - } - - return readed; -} - -int cbt_map_mark_dirty_blocks(struct cbt_map *cbt_map, - struct blk_snap_block_range *block_ranges, - unsigned int count) -{ - int inx; - int ret = 0; - - for (inx = 0; inx < count; inx++) { - ret = cbt_map_set_both( - cbt_map, (sector_t)block_ranges[inx].sector_offset, - (sector_t)block_ranges[inx].sector_count); - if (ret) - break; - } - - return ret; -} - -#ifdef BLK_SNAP_DEBUG_SECTOR_STATE - -int cbt_map_get_sector_state(struct cbt_map *cbt_map, sector_t sector, - u8 *snap_number_prev, u8 *snap_number_curr) -{ - int ret = 0; - size_t cbt_block = - (size_t)(sector >> (cbt_map->blk_size_shift - SECTOR_SHIFT)); - - if (unlikely(cbt_block >= cbt_map->blk_count)) { - pr_err("Block index is too large.\n"); - pr_err("Block #%zu was demanded, map size %zu blocks.\n", - cbt_block, cbt_map->blk_count); - return -EINVAL; - } - - spin_lock(&cbt_map->locker); - if (unlikely(cbt_map->is_corrupted)) { - ret = -EINVAL; - goto out; - } - *snap_number_curr = cbt_map->write_map[cbt_block]; - *snap_number_prev = cbt_map->read_map[cbt_block]; -out: - spin_unlock(&cbt_map->locker); - - return ret; -} -#endif diff --git a/module/cbt_map.h b/module/cbt_map.h deleted file mode 100644 index f611dcfd..00000000 --- a/module/cbt_map.h +++ /dev/null @@ -1,118 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef __BLK_SNAP_CBT_MAP_H -#define __BLK_SNAP_CBT_MAP_H - -#include -#include -#include -#include -#include - -struct blk_snap_block_range; - -/** - * struct cbt_map - The table of changes for a block device. - * - * @kref: - * Reference counter. - * @locker: - * Locking for atomic modification of structure members. - * @blk_size_shift: - * The power of 2 used to specify the change tracking block size. - * @blk_count: - * The number of change tracking blocks. - * @device_capacity: - * The actual capacity of the device. - * @read_map: - * A table of changes available for reading. This is the table that can - * be read after taking a snapshot. - * @write_map: - * The current table for tracking changes. - * @snap_number_active: - * The current sequential number of changes. This is the number that is written to - * the current table when the block data changes. - * @snap_number_previous: - * The previous sequential number of changes. This number is used to identify the - * blocks that were changed between the penultimate snapshot and the last snapshot. - * @generation_id: - * UUID of the generation of changes. - * @is_corrupted: - * A flag that the change tracking data is no longer reliable. - * - * The change block tracking map is a byte table. Each byte stores the - * sequential number of changes for one block. To determine which blocks have changed - * since the previous snapshot with the change number 4, it is enough to - * find all bytes with the number more than 4. - * - * Since one byte is allocated to track changes in one block, the change - * table is created again at the 255th snapshot. At the same time, a new - * unique generation identifier is generated. Tracking changes is - * possible only for tables of the same generation. - * - * There are two tables on the change block tracking map. One is - * available for reading, and the other is available for writing. At the moment of taking - * a snapshot, the tables are synchronized. The user's process, when - * calling the corresponding ioctl, can read the readable table. - * At the same time, the change tracking mechanism continues to work with - * the writable table. - * - * To provide the ability to mount a snapshot image as writeable, it is - * possible to make changes to both of these tables simultaneously. - * - */ -struct cbt_map { - struct kref kref; - - spinlock_t locker; - - size_t blk_size_shift; - size_t blk_count; - sector_t device_capacity; - - unsigned char *read_map; - unsigned char *write_map; - - unsigned long snap_number_active; - unsigned long snap_number_previous; - uuid_t generation_id; - - bool is_corrupted; -}; - -struct cbt_map *cbt_map_create(struct block_device *bdev); -int cbt_map_reset(struct cbt_map *cbt_map, sector_t device_capacity); - -void cbt_map_destroy_cb(struct kref *kref); -static inline void cbt_map_get(struct cbt_map *cbt_map) -{ - kref_get(&cbt_map->kref); -}; -static inline void cbt_map_put(struct cbt_map *cbt_map) -{ - if (likely(cbt_map)) - kref_put(&cbt_map->kref, cbt_map_destroy_cb); -}; - -void cbt_map_switch(struct cbt_map *cbt_map); -int cbt_map_set(struct cbt_map *cbt_map, sector_t sector_start, - sector_t sector_cnt); -int cbt_map_set_both(struct cbt_map *cbt_map, sector_t sector_start, - sector_t sector_cnt); - -size_t cbt_map_read_to_user(struct cbt_map *cbt_map, char __user *user_buffer, - size_t offset, size_t size); - -static inline size_t cbt_map_blk_size(struct cbt_map *cbt_map) -{ - return 1 << cbt_map->blk_size_shift; -}; - -int cbt_map_mark_dirty_blocks(struct cbt_map *cbt_map, - struct blk_snap_block_range *block_ranges, - unsigned int count); - -#ifdef BLK_SNAP_DEBUG_SECTOR_STATE -int cbt_map_get_sector_state(struct cbt_map *cbt_map, sector_t sector, - u8 *snap_number_prev, u8 *snap_number_curr); -#endif -#endif /* __BLK_SNAP_CBT_MAP_H */ diff --git a/module/chunk.c b/module/chunk.c deleted file mode 100644 index 1d99376b..00000000 --- a/module/chunk.c +++ /dev/null @@ -1,355 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#define pr_fmt(fmt) KBUILD_MODNAME "-chunk: " fmt - -#include -#include -#include -#include "memory_checker.h" -#include "chunk.h" -#include "diff_io.h" -#include "diff_buffer.h" -#include "diff_area.h" -#include "diff_storage.h" -#include "log.h" - -extern int chunk_maximum_in_cache; - -void chunk_diff_buffer_release(struct chunk *chunk) -{ - if (unlikely(!chunk->diff_buffer)) - return; - - chunk_state_unset(chunk, CHUNK_ST_BUFFER_READY); - diff_buffer_release(chunk->diff_area, chunk->diff_buffer); - chunk->diff_buffer = NULL; -} - -void chunk_store_failed(struct chunk *chunk, int error) -{ - struct diff_area *diff_area = chunk->diff_area; - - chunk_state_set(chunk, CHUNK_ST_FAILED); - chunk_diff_buffer_release(chunk); - diff_storage_free_region(chunk->diff_region); - chunk->diff_region = NULL; - - up(&chunk->lock); - if (error) - diff_area_set_corrupted(diff_area, error); -}; - -int chunk_schedule_storing(struct chunk *chunk, bool is_nowait) -{ - struct diff_area *diff_area = chunk->diff_area; - - if (WARN(!list_is_first(&chunk->cache_link, &chunk->cache_link), - "The chunk already in the cache")) - return -EINVAL; - -#ifdef BLK_SNAP_ALLOW_DIFF_STORAGE_IN_MEMORY - if (diff_area->in_memory) { - up(&chunk->lock); - return 0; - } -#endif - if (!chunk->diff_region) { - struct diff_region *diff_region; - - diff_region = diff_storage_new_region( - diff_area->diff_storage, - diff_area_chunk_sectors(diff_area)); - if (IS_ERR(diff_region)) { - pr_debug("Cannot get store for chunk #%ld\n", - chunk->number); - return PTR_ERR(diff_region); - } - - chunk->diff_region = diff_region; - } - - return chunk_async_store_diff(chunk, is_nowait); -} - -void chunk_schedule_caching(struct chunk *chunk) -{ - int in_cache_count = 0; - struct diff_area *diff_area = chunk->diff_area; - - might_sleep(); - - spin_lock(&diff_area->caches_lock); - - /* - * The locked chunk cannot be in the cache. - * If the check reveals that the chunk is in the cache, then something - * is wrong in the algorithm. - */ - if (WARN(!list_is_first(&chunk->cache_link, &chunk->cache_link), - "The chunk already in the cache")) { - spin_unlock(&diff_area->caches_lock); - - chunk_store_failed(chunk, 0); - return; - } - - if (chunk_state_check(chunk, CHUNK_ST_DIRTY)) { - list_add_tail(&chunk->cache_link, - &diff_area->write_cache_queue); - in_cache_count = - atomic_inc_return(&diff_area->write_cache_count); - } else { - list_add_tail(&chunk->cache_link, &diff_area->read_cache_queue); - in_cache_count = - atomic_inc_return(&diff_area->read_cache_count); - } - spin_unlock(&diff_area->caches_lock); - - up(&chunk->lock); - - /* Initiate the cache clearing process */ - if (in_cache_count > chunk_maximum_in_cache) - queue_work(system_wq, &diff_area->cache_release_work); -} - -static void chunk_notify_load(void *ctx) -{ - struct chunk *chunk = ctx; - int error = chunk->diff_io->error; - - diff_io_free(chunk->diff_io); - chunk->diff_io = NULL; - - might_sleep(); - - if (unlikely(error)) { - chunk_store_failed(chunk, error); - goto out; - } - - if (unlikely(chunk_state_check(chunk, CHUNK_ST_FAILED))) { - pr_err("Chunk in a failed state\n"); - up(&chunk->lock); - goto out; - } - - if (chunk_state_check(chunk, CHUNK_ST_LOADING)) { - int ret; - unsigned int current_flag; - - chunk_state_unset(chunk, CHUNK_ST_LOADING); - chunk_state_set(chunk, CHUNK_ST_BUFFER_READY); - - current_flag = memalloc_noio_save(); - ret = chunk_schedule_storing(chunk, false); - memalloc_noio_restore(current_flag); - if (ret) - chunk_store_failed(chunk, ret); - goto out; - } - - pr_err("invalid chunk state 0x%x\n", atomic_read(&chunk->state)); - up(&chunk->lock); -out: - atomic_dec(&chunk->diff_area->pending_io_count); -} - -static void chunk_notify_store(void *ctx) -{ - struct chunk *chunk = ctx; - int error = chunk->diff_io->error; - - diff_io_free(chunk->diff_io); - chunk->diff_io = NULL; - - might_sleep(); - - if (unlikely(error)) { - chunk_store_failed(chunk, error); - goto out; - } - - if (unlikely(chunk_state_check(chunk, CHUNK_ST_FAILED))) { - pr_err("Chunk in a failed state\n"); - chunk_store_failed(chunk, 0); - goto out; - } - if (chunk_state_check(chunk, CHUNK_ST_STORING)) { - chunk_state_unset(chunk, CHUNK_ST_STORING); - chunk_state_set(chunk, CHUNK_ST_STORE_READY); - - if (chunk_state_check(chunk, CHUNK_ST_DIRTY)) { - /* - * The chunk marked "dirty" was stored in the difference - * storage. Now it is processed in the same way as any - * other stored chunks. - * Therefore, the "dirty" mark can be removed. - */ - chunk_state_unset(chunk, CHUNK_ST_DIRTY); - chunk_diff_buffer_release(chunk); - } else { - unsigned int current_flag; - - current_flag = memalloc_noio_save(); - chunk_schedule_caching(chunk); - memalloc_noio_restore(current_flag); - goto out; - } - } else - pr_err("invalid chunk state 0x%x\n", atomic_read(&chunk->state)); - up(&chunk->lock); -out: - atomic_dec(&chunk->diff_area->pending_io_count); -} - -struct chunk *chunk_alloc(struct diff_area *diff_area, unsigned long number) -{ - struct chunk *chunk; - - chunk = kzalloc(sizeof(struct chunk), GFP_KERNEL); - if (!chunk) - return NULL; - memory_object_inc(memory_object_chunk); - - INIT_LIST_HEAD(&chunk->cache_link); - sema_init(&chunk->lock, 1); - chunk->diff_area = diff_area; - chunk->number = number; - atomic_set(&chunk->state, 0); - - return chunk; -} - -void chunk_free(struct chunk *chunk) -{ - if (unlikely(!chunk)) - return; - - down(&chunk->lock); - chunk_diff_buffer_release(chunk); - diff_storage_free_region(chunk->diff_region); - chunk_state_set(chunk, CHUNK_ST_FAILED); - up(&chunk->lock); - - kfree(chunk); - memory_object_dec(memory_object_chunk); -} - -/* - * Starts asynchronous storing of a chunk to the difference storage. - */ -int chunk_async_store_diff(struct chunk *chunk, bool is_nowait) -{ - int ret; - struct diff_io *diff_io; - struct diff_region *region = chunk->diff_region; - - if (WARN(!list_is_first(&chunk->cache_link, &chunk->cache_link), - "The chunk already in the cache")) - return -EINVAL; - - diff_io = diff_io_new_async_write(chunk_notify_store, chunk, is_nowait); - if (unlikely(!diff_io)) { - if (is_nowait) - return -EAGAIN; - else - return -ENOMEM; - } - - WARN_ON(chunk->diff_io); - chunk->diff_io = diff_io; - chunk_state_set(chunk, CHUNK_ST_STORING); - atomic_inc(&chunk->diff_area->pending_io_count); - - ret = diff_io_do(chunk->diff_io, region, chunk->diff_buffer, is_nowait); - if (ret) { - atomic_dec(&chunk->diff_area->pending_io_count); - diff_io_free(chunk->diff_io); - chunk->diff_io = NULL; - } - - return ret; -} - -/* - * Starts asynchronous loading of a chunk from the original block device. - */ -int chunk_async_load_orig(struct chunk *chunk, const bool is_nowait) -{ - int ret; - struct diff_io *diff_io; - struct diff_region region = { - .bdev = chunk->diff_area->orig_bdev, - .sector = (sector_t)(chunk->number) * - diff_area_chunk_sectors(chunk->diff_area), - .count = chunk->sector_count, - }; - - diff_io = diff_io_new_async_read(chunk_notify_load, chunk, is_nowait); - if (unlikely(!diff_io)) { - if (is_nowait) - return -EAGAIN; - else - return -ENOMEM; - } - - WARN_ON(chunk->diff_io); - chunk->diff_io = diff_io; - chunk_state_set(chunk, CHUNK_ST_LOADING); - atomic_inc(&chunk->diff_area->pending_io_count); - - ret = diff_io_do(chunk->diff_io, ®ion, chunk->diff_buffer, is_nowait); - if (ret) { - atomic_dec(&chunk->diff_area->pending_io_count); - diff_io_free(chunk->diff_io); - chunk->diff_io = NULL; - } - return ret; -} - -/* - * Performs synchronous loading of a chunk from the original block device. - */ -int chunk_load_orig(struct chunk *chunk) -{ - int ret; - struct diff_io *diff_io; - struct diff_region region = { - .bdev = chunk->diff_area->orig_bdev, - .sector = (sector_t)(chunk->number) * - diff_area_chunk_sectors(chunk->diff_area), - .count = chunk->sector_count, - }; - - diff_io = diff_io_new_sync_read(); - if (unlikely(!diff_io)) - return -ENOMEM; - - ret = diff_io_do(diff_io, ®ion, chunk->diff_buffer, false); - if (!ret) - ret = diff_io->error; - - diff_io_free(diff_io); - return ret; -} - -/* - * Performs synchronous loading of a chunk from the difference storage. - */ -int chunk_load_diff(struct chunk *chunk) -{ - int ret; - struct diff_io *diff_io; - struct diff_region *region = chunk->diff_region; - - diff_io = diff_io_new_sync_read(); - if (unlikely(!diff_io)) - return -ENOMEM; - - ret = diff_io_do(diff_io, region, chunk->diff_buffer, false); - if (!ret) - ret = diff_io->error; - - diff_io_free(diff_io); - - return ret; -} diff --git a/module/chunk.h b/module/chunk.h deleted file mode 100644 index 6f235093..00000000 --- a/module/chunk.h +++ /dev/null @@ -1,139 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef __BLK_SNAP_CHUNK_H -#define __BLK_SNAP_CHUNK_H - -#include -#include -#include -#include - -struct diff_area; -struct diff_region; -struct diff_io; - -/** - * enum chunk_st - Possible states for a chunk. - * - * @CHUNK_ST_FAILED: - * An error occurred while processing the chunk data. - * @CHUNK_ST_DIRTY: - * The chunk is in the dirty state. The chunk is marked dirty in case - * there was a write operation to the snapshot image. - * The flag is removed when the data of the chunk is stored in the - * difference storage. - * @CHUNK_ST_BUFFER_READY: - * The data of the chunk is ready to be read from the RAM buffer. - * The flag is removed when a chunk is removed from the cache and its - * buffer is released. - * @CHUNK_ST_STORE_READY: - * The data of the chunk has been written to the difference storage. - * The flag cannot be removed. - * @CHUNK_ST_LOADING: - * The data is being read from the original block device. - * The flag is replaced with the CHUNK_ST_BUFFER_READY flag. - * @CHUNK_ST_STORING: - * The data is being saved to the difference storage. - * The flag is replaced with the CHUNK_ST_STORE_READY flag. - * - * Chunks life circle. - * Copy-on-write when writing to original: - * 0 -> LOADING -> BUFFER_READY -> BUFFER_READY | STORING -> - * BUFFER_READY | STORE_READY -> STORE_READY - * Write to snapshot image: - * 0 -> LOADING -> BUFFER_READY | DIRTY -> DIRTY | STORING -> - * BUFFER_READY | STORE_READY -> STORE_READY - */ -enum chunk_st { - CHUNK_ST_FAILED = (1 << 0), - CHUNK_ST_DIRTY = (1 << 1), - CHUNK_ST_BUFFER_READY = (1 << 2), - CHUNK_ST_STORE_READY = (1 << 3), - CHUNK_ST_LOADING = (1 << 4), - CHUNK_ST_STORING = (1 << 5), -}; - -/** - * struct chunk - Minimum data storage unit. - * - * @cache_link: - * The list header allows to create caches of chunks. - * @diff_area: - * Pointer to the difference area - the storage of changes for a specific device. - * @number: - * Sequential number of the chunk. - * @sector_count: - * Number of sectors in the current chunk. This is especially true - * for the last chunk. - * @lock: - * Binary semaphore. Syncs access to the chunks fields: state, - * diff_buffer, diff_region and diff_io. - * @state: - * Defines the state of a chunk. May contain CHUNK_ST_* bits. - * @diff_buffer: - * Pointer to &struct diff_buffer. Describes a buffer in the memory - * for storing the chunk data. - * @diff_region: - * Pointer to &struct diff_region. Describes a copy of the chunk data - * on the difference storage. - * @diff_io: - * Provides I/O operations for a chunk. - * - * This structure describes the block of data that the module operates - * with when executing the copy-on-write algorithm and when performing I/O - * to snapshot images. - * - * If the data of the chunk has been changed or has just been read, then - * the chunk gets into cache. - * - * The semaphore is blocked for writing if there is no actual data in the - * buffer, since a block of data is being read from the original device or - * from a diff storage. If data is being read from or written to the - * diff_buffer, the semaphore must be locked. - */ -struct chunk { - struct list_head cache_link; - struct diff_area *diff_area; - - unsigned long number; - sector_t sector_count; - - struct semaphore lock; - - atomic_t state; - struct diff_buffer *diff_buffer; - struct diff_region *diff_region; - struct diff_io *diff_io; -}; - -static inline void chunk_state_set(struct chunk *chunk, int st) -{ - atomic_or(st, &chunk->state); -}; - -static inline void chunk_state_unset(struct chunk *chunk, int st) -{ - atomic_and(~st, &chunk->state); -}; - -static inline bool chunk_state_check(struct chunk *chunk, int st) -{ - return !!(atomic_read(&chunk->state) & st); -}; - -struct chunk *chunk_alloc(struct diff_area *diff_area, unsigned long number); -void chunk_free(struct chunk *chunk); - -int chunk_schedule_storing(struct chunk *chunk, bool is_nowait); -void chunk_diff_buffer_release(struct chunk *chunk); -void chunk_store_failed(struct chunk *chunk, int error); - -void chunk_schedule_caching(struct chunk *chunk); - -/* Asynchronous operations are used to implement the COW algorithm. */ -int chunk_async_store_diff(struct chunk *chunk, bool is_nowait); -int chunk_async_load_orig(struct chunk *chunk, const bool is_nowait); - -/* Synchronous operations are used to implement reading and writing to the snapshot image. */ -int chunk_load_orig(struct chunk *chunk); -int chunk_load_diff(struct chunk *chunk); -#endif /* __BLK_SNAP_CHUNK_H */ diff --git a/module/diff_area.c b/module/diff_area.c deleted file mode 100644 index 7d8cc7b8..00000000 --- a/module/diff_area.c +++ /dev/null @@ -1,724 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#define pr_fmt(fmt) KBUILD_MODNAME "-diff-area: " fmt - -#ifdef HAVE_GENHD_H -#include -#endif -#include -#include -#ifdef STANDALONE_BDEVFILTER -#include "blksnap.h" -#else -#include -#endif -#include "memory_checker.h" -#include "chunk.h" -#include "diff_area.h" -#include "diff_buffer.h" -#include "diff_storage.h" -#include "diff_io.h" -#include "log.h" - -extern int chunk_minimum_shift; -extern int chunk_maximum_count; -extern int chunk_maximum_in_cache; - -#ifndef HAVE_BDEV_NR_SECTORS -static inline sector_t bdev_nr_sectors(struct block_device *bdev) -{ - return i_size_read(bdev->bd_inode) >> 9; -}; -#endif - -static inline unsigned long chunk_number(struct diff_area *diff_area, - sector_t sector) -{ - return (unsigned long)(sector >> - (diff_area->chunk_shift - SECTOR_SHIFT)); -}; - -static inline sector_t chunk_sector(struct chunk *chunk) -{ - return (sector_t)(chunk->number) - << (chunk->diff_area->chunk_shift - SECTOR_SHIFT); -} - -static inline void recalculate_last_chunk_size(struct chunk *chunk) -{ - sector_t capacity; - - capacity = bdev_nr_sectors(chunk->diff_area->orig_bdev); - if (capacity > round_down(capacity, chunk->sector_count)) - chunk->sector_count = - capacity - round_down(capacity, chunk->sector_count); -} - -static inline unsigned long long count_by_shift(sector_t capacity, - unsigned long long shift) -{ - unsigned long long shift_sector = (shift - SECTOR_SHIFT); - - return round_up(capacity, (1ull << shift_sector)) >> shift_sector; -} - -static void diff_area_calculate_chunk_size(struct diff_area *diff_area) -{ - unsigned long long shift = chunk_minimum_shift; - unsigned long long count; - sector_t capacity; - sector_t min_io_sect; - - min_io_sect = (sector_t)(bdev_io_min(diff_area->orig_bdev) >> - SECTOR_SHIFT); - capacity = bdev_nr_sectors(diff_area->orig_bdev); - pr_debug("Minimal IO block %llu sectors\n", min_io_sect); - pr_debug("Device capacity %llu sectors\n", capacity); - - count = count_by_shift(capacity, shift); - pr_debug("Chunks count %llu\n", count); - while ((count > chunk_maximum_count) || - ((1ull << (shift - SECTOR_SHIFT)) < min_io_sect)) { - shift = shift + 1ull; - count = count_by_shift(capacity, shift); - pr_debug("Chunks count %llu\n", count); - } - - diff_area->chunk_shift = shift; - diff_area->chunk_count = count; - - pr_debug("The optimal chunk size was calculated as %llu bytes for device [%d:%d]\n", - (1ull << diff_area->chunk_shift), - MAJOR(diff_area->orig_bdev->bd_dev), - MINOR(diff_area->orig_bdev->bd_dev)); -} - -void diff_area_free(struct kref *kref) -{ - unsigned long inx = 0; - u64 start_waiting; - struct chunk *chunk; - struct diff_area *diff_area = - container_of(kref, struct diff_area, kref); - - might_sleep(); - start_waiting = jiffies_64; - while (atomic_read(&diff_area->pending_io_count)) { - schedule_timeout_interruptible(1); - if (jiffies_64 > (start_waiting + HZ)) { - start_waiting = jiffies_64; - inx++; - pr_warn("Waiting for pending I/O to complete\n"); - if (inx > 5) { - pr_err("Failed to complete pending I/O\n"); - break; - } - } - } - - flush_work(&diff_area->cache_release_work); - xa_for_each(&diff_area->chunk_map, inx, chunk) - chunk_free(chunk); - xa_destroy(&diff_area->chunk_map); - - if (diff_area->orig_bdev) { - blkdev_put(diff_area->orig_bdev, FMODE_READ | FMODE_WRITE); - diff_area->orig_bdev = NULL; - } - - /* Clean up free_diff_buffers */ - diff_buffer_cleanup(diff_area); - - kfree(diff_area); - memory_object_dec(memory_object_diff_area); -} - -static inline struct chunk * -get_chunk_from_cache_and_write_lock(spinlock_t *caches_lock, - struct list_head *cache_queue, - atomic_t *cache_count) -{ - struct chunk *iter; - struct chunk *chunk = NULL; - - spin_lock(caches_lock); - list_for_each_entry(iter, cache_queue, cache_link) { - if (!down_trylock(&iter->lock)) { - chunk = iter; - break; - } - /* - * If it is not possible to lock a chunk for writing, - * then it is currently in use, and we try to clean up the - * next chunk. - */ - } - if (likely(chunk)) { - atomic_dec(cache_count); - list_del_init(&chunk->cache_link); - } - spin_unlock(caches_lock); - - return chunk; -} - -static struct chunk * -diff_area_get_chunk_from_cache_and_write_lock(struct diff_area *diff_area) -{ - struct chunk *chunk; - - if (atomic_read(&diff_area->read_cache_count) > - chunk_maximum_in_cache) { - chunk = get_chunk_from_cache_and_write_lock( - &diff_area->caches_lock, &diff_area->read_cache_queue, - &diff_area->read_cache_count); - if (chunk) - return chunk; - } - - if (atomic_read(&diff_area->write_cache_count) > - chunk_maximum_in_cache) { - chunk = get_chunk_from_cache_and_write_lock( - &diff_area->caches_lock, &diff_area->write_cache_queue, - &diff_area->write_cache_count); - if (chunk) - return chunk; - } - - return NULL; -} - -static void diff_area_cache_release(struct diff_area *diff_area) -{ - struct chunk *chunk; - - while (!diff_area_is_corrupted(diff_area) && - (chunk = diff_area_get_chunk_from_cache_and_write_lock( - diff_area))) { - /* - * There cannot be a chunk in the cache whose buffer is - * not ready. - */ - if (WARN(!chunk_state_check(chunk, CHUNK_ST_BUFFER_READY), - "Cannot release empty buffer for chunk #%ld", - chunk->number)) { - up(&chunk->lock); - continue; - } - - if (chunk_state_check(chunk, CHUNK_ST_DIRTY)) { - int ret; - - ret = chunk_schedule_storing(chunk, false); - if (ret) - chunk_store_failed(chunk, ret); - } else { - chunk_diff_buffer_release(chunk); - up(&chunk->lock); - } - } -} - -static void diff_area_cache_release_work(struct work_struct *work) -{ - struct diff_area *diff_area = - container_of(work, struct diff_area, cache_release_work); - - diff_area_cache_release(diff_area); -} - -struct diff_area *diff_area_new(dev_t dev_id, struct diff_storage *diff_storage) -{ - int ret = 0; - struct diff_area *diff_area = NULL; - struct block_device *bdev; - unsigned long number; - struct chunk *chunk; - - pr_debug("Open device [%u:%u]\n", MAJOR(dev_id), MINOR(dev_id)); - - bdev = blkdev_get_by_dev(dev_id, FMODE_READ | FMODE_WRITE, NULL); - if (IS_ERR(bdev)) { - int err = PTR_ERR(bdev); - - pr_err("Failed to open device. errno=%d\n", abs(err)); - return ERR_PTR(err); - } - - diff_area = kzalloc(sizeof(struct diff_area), GFP_KERNEL); - if (!diff_area) { - blkdev_put(bdev, FMODE_READ | FMODE_WRITE); - return ERR_PTR(-ENOMEM); - } - memory_object_inc(memory_object_diff_area); - - diff_area->orig_bdev = bdev; - diff_area->diff_storage = diff_storage; - - diff_area_calculate_chunk_size(diff_area); - pr_debug("Chunk size %llu in bytes\n", 1ull << diff_area->chunk_shift); - pr_debug("Chunk count %lu\n", diff_area->chunk_count); - - kref_init(&diff_area->kref); - xa_init(&diff_area->chunk_map); - - if (!diff_storage->capacity) { -#ifdef BLK_SNAP_ALLOW_DIFF_STORAGE_IN_MEMORY - diff_area->in_memory = true; - pr_debug("Difference storage is empty.\n"); - pr_debug("Only the memory cache will be used to store the snapshots difference.\n"); -#else - pr_err("Difference storage is empty.\n"); - pr_err("In-memory difference storage is not supported"); - return ERR_PTR(-EFAULT); -#endif - } - - spin_lock_init(&diff_area->caches_lock); - INIT_LIST_HEAD(&diff_area->read_cache_queue); - atomic_set(&diff_area->read_cache_count, 0); - INIT_LIST_HEAD(&diff_area->write_cache_queue); - atomic_set(&diff_area->write_cache_count, 0); - INIT_WORK(&diff_area->cache_release_work, diff_area_cache_release_work); - - spin_lock_init(&diff_area->free_diff_buffers_lock); - INIT_LIST_HEAD(&diff_area->free_diff_buffers); - atomic_set(&diff_area->free_diff_buffers_count, 0); - - diff_area->corrupt_flag = 0; - atomic_set(&diff_area->pending_io_count, 0); - - /* - * Allocating all chunks in advance allows to avoid doing this in - * the process of filtering bio. - * In addition, the chunk structure has an rw semaphore that allows - * to lock data of a single chunk. - * Different threads can read, write, or dump their data to diff storage - * independently of each other, provided that different chunks are used. - */ - for (number = 0; number < diff_area->chunk_count; number++) { - chunk = chunk_alloc(diff_area, number); - if (!chunk) { - pr_err("Failed allocate chunk\n"); - ret = -ENOMEM; - break; - } - chunk->sector_count = diff_area_chunk_sectors(diff_area); - - ret = xa_insert(&diff_area->chunk_map, number, chunk, - GFP_KERNEL); - if (ret) { - pr_err("Failed insert chunk to chunk map\n"); - chunk_free(chunk); - break; - } - } - if (ret) { - diff_area_put(diff_area); - return ERR_PTR(ret); - } - - recalculate_last_chunk_size(chunk); - - return diff_area; -} - -static void diff_area_take_chunk_from_cache(struct diff_area *diff_area, - struct chunk *chunk) -{ - spin_lock(&diff_area->caches_lock); - if (!list_is_first(&chunk->cache_link, &chunk->cache_link)) { - list_del_init(&chunk->cache_link); - - if (chunk_state_check(chunk, CHUNK_ST_DIRTY)) - atomic_dec(&diff_area->write_cache_count); - else - atomic_dec(&diff_area->read_cache_count); - } - spin_unlock(&diff_area->caches_lock); -} - -/* - * Implements the copy-on-write mechanism. - */ -int diff_area_copy(struct diff_area *diff_area, sector_t sector, sector_t count, - const bool is_nowait) -{ - int ret = 0; - sector_t offset; - struct chunk *chunk; - struct diff_buffer *diff_buffer; - sector_t area_sect_first; - sector_t chunk_sectors = diff_area_chunk_sectors(diff_area); - - area_sect_first = round_down(sector, chunk_sectors); - for (offset = area_sect_first; offset < (sector + count); - offset += chunk_sectors) { - chunk = xa_load(&diff_area->chunk_map, - chunk_number(diff_area, offset)); - if (!chunk) { - diff_area_set_corrupted(diff_area, -EINVAL); - return -EINVAL; - } - WARN_ON(chunk_number(diff_area, offset) != chunk->number); - if (is_nowait) { - if (down_trylock(&chunk->lock)) - return -EAGAIN; - } else { - ret = down_killable(&chunk->lock); - if (unlikely(ret)) - return ret; - } - - if (chunk_state_check(chunk, CHUNK_ST_FAILED | CHUNK_ST_DIRTY | - CHUNK_ST_STORE_READY)) { - /* - * The chunk has already been: - * - Failed, when the snapshot is corrupted - * - Overwritten in the snapshot image - * - Already stored in the diff storage - */ - up(&chunk->lock); - continue; - } - - if (unlikely(chunk_state_check( - chunk, CHUNK_ST_LOADING | CHUNK_ST_STORING))) { - pr_err("Invalid chunk state\n"); - ret = -EFAULT; - goto fail_unlock_chunk; - } - - if (chunk_state_check(chunk, CHUNK_ST_BUFFER_READY)) { - diff_area_take_chunk_from_cache(diff_area, chunk); - /* - * The chunk has already been read, but now we need - * to store it to diff_storage. - */ - ret = chunk_schedule_storing(chunk, is_nowait); - if (unlikely(ret)) - goto fail_unlock_chunk; - } else { - diff_buffer = - diff_buffer_take(chunk->diff_area, is_nowait); - if (IS_ERR(diff_buffer)) { - ret = PTR_ERR(diff_buffer); - goto fail_unlock_chunk; - } - WARN(chunk->diff_buffer, "Chunks buffer has been lost"); - chunk->diff_buffer = diff_buffer; - - ret = chunk_async_load_orig(chunk, is_nowait); - if (unlikely(ret)) - goto fail_unlock_chunk; - } - } - - return ret; -fail_unlock_chunk: - WARN_ON(!chunk); - chunk_store_failed(chunk, ret); - return ret; -} - -int diff_area_wait(struct diff_area *diff_area, sector_t sector, sector_t count, - const bool is_nowait) -{ - int ret = 0; - sector_t offset; - struct chunk *chunk; - sector_t area_sect_first; - sector_t chunk_sectors = diff_area_chunk_sectors(diff_area); - - area_sect_first = round_down(sector, chunk_sectors); - for (offset = area_sect_first; offset < (sector + count); - offset += chunk_sectors) { - chunk = xa_load(&diff_area->chunk_map, - chunk_number(diff_area, offset)); - if (!chunk) { - diff_area_set_corrupted(diff_area, -EINVAL); - return -EINVAL; - } - WARN_ON(chunk_number(diff_area, offset) != chunk->number); - if (is_nowait) { - if (down_trylock(&chunk->lock)) - return -EAGAIN; - } else { - ret = down_killable(&chunk->lock); - if (unlikely(ret)) - return ret; - } - - if (chunk_state_check(chunk, CHUNK_ST_FAILED)) { - /* - * The chunk has already been: - * - Failed, when the snapshot is corrupted - * - Overwritten in the snapshot image - * - Already stored in the diff storage - */ - up(&chunk->lock); - ret = -EFAULT; - break; - } - - if (chunk_state_check(chunk, CHUNK_ST_BUFFER_READY | - CHUNK_ST_DIRTY | CHUNK_ST_STORE_READY)) { - /* - * The chunk has already been: - * - Read - * - Overwritten in the snapshot image - * - Already stored in the diff storage - */ - up(&chunk->lock); - continue; - } - } - - return ret; -} - -static inline void diff_area_image_put_chunk(struct chunk *chunk, bool is_write) -{ - if (is_write) { - /* - * Since the chunk was taken to perform writing, - * we mark it as dirty. - */ - chunk_state_set(chunk, CHUNK_ST_DIRTY); - } - - chunk_schedule_caching(chunk); -} - -void diff_area_image_ctx_done(struct diff_area_image_ctx *io_ctx) -{ - if (!io_ctx->chunk) - return; - - diff_area_image_put_chunk(io_ctx->chunk, io_ctx->is_write); -} - -static int diff_area_load_chunk_from_storage(struct diff_area *diff_area, - struct chunk *chunk) -{ - struct diff_buffer *diff_buffer; - - diff_buffer = diff_buffer_take(diff_area, false); - if (IS_ERR(diff_buffer)) - return PTR_ERR(diff_buffer); - - WARN_ON(chunk->diff_buffer); - chunk->diff_buffer = diff_buffer; - - if (chunk_state_check(chunk, CHUNK_ST_STORE_READY)) - return chunk_load_diff(chunk); - - return chunk_load_orig(chunk); -} - -static struct chunk * -diff_area_image_context_get_chunk(struct diff_area_image_ctx *io_ctx, - sector_t sector) -{ - int ret; - struct chunk *chunk; - struct diff_area *diff_area = io_ctx->diff_area; - unsigned long new_chunk_number = chunk_number(diff_area, sector); - - chunk = io_ctx->chunk; - if (chunk) { - if (chunk->number == new_chunk_number) - return chunk; - - /* - * If the sector falls into a new chunk, then we release - * the old chunk. - */ - diff_area_image_put_chunk(chunk, io_ctx->is_write); - io_ctx->chunk = NULL; - } - - /* Take a next chunk. */ - chunk = xa_load(&diff_area->chunk_map, new_chunk_number); - if (unlikely(!chunk)) - return ERR_PTR(-EINVAL); - - ret = down_killable(&chunk->lock); - if (ret) - return ERR_PTR(ret); - - if (unlikely(chunk_state_check(chunk, CHUNK_ST_FAILED))) { - pr_err("Chunk #%ld corrupted\n", chunk->number); - - pr_debug("new_chunk_number=%ld\n", new_chunk_number); - pr_debug("sector=%llu\n", sector); - pr_debug("Chunk size %llu in bytes\n", - (1ull << diff_area->chunk_shift)); - pr_debug("Chunk count %lu\n", diff_area->chunk_count); - - ret = -EIO; - goto fail_unlock_chunk; - } - - /* - * If there is already data in the buffer, then nothing needs to be loaded. - * Otherwise, the chunk needs to be loaded from the original device or - * from the difference storage. - */ - if (!chunk_state_check(chunk, CHUNK_ST_BUFFER_READY)) { - ret = diff_area_load_chunk_from_storage(diff_area, chunk); - if (unlikely(ret)) - goto fail_unlock_chunk; - - /* Set the flag that the buffer contains the required data. */ - chunk_state_set(chunk, CHUNK_ST_BUFFER_READY); - } else - diff_area_take_chunk_from_cache(diff_area, chunk); - - io_ctx->chunk = chunk; - return chunk; - -fail_unlock_chunk: - pr_err("Failed to load chunk #%ld\n", chunk->number); - up(&chunk->lock); - return ERR_PTR(ret); -} - -static inline sector_t diff_area_chunk_start(struct diff_area *diff_area, - struct chunk *chunk) -{ - return (sector_t)(chunk->number) << diff_area->chunk_shift; -} - -/* - * Implements copying data from the chunk to bio_vec when reading or from - * bio_vec to the chunk when writing. - */ -blk_status_t diff_area_image_io(struct diff_area_image_ctx *io_ctx, - const struct bio_vec *bvec, sector_t *pos) -{ - unsigned int bv_len = bvec->bv_len; - struct iov_iter iter; - - iov_iter_bvec(&iter, io_ctx->is_write ? WRITE : READ, bvec, 1, bv_len); - - while (bv_len) { - struct diff_buffer_iter diff_buffer_iter; - struct chunk *chunk; - size_t buff_offset; - - chunk = diff_area_image_context_get_chunk(io_ctx, *pos); - if (IS_ERR(chunk)) - return BLK_STS_IOERR; - - buff_offset = (size_t)(*pos - chunk_sector(chunk)) - << SECTOR_SHIFT; - while (bv_len && - diff_buffer_iter_get(chunk->diff_buffer, buff_offset, - &diff_buffer_iter)) { - size_t sz; - - if (io_ctx->is_write) - sz = copy_page_from_iter( - diff_buffer_iter.page, - diff_buffer_iter.offset, - diff_buffer_iter.bytes, - &iter); - else - sz = copy_page_to_iter( - diff_buffer_iter.page, - diff_buffer_iter.offset, - diff_buffer_iter.bytes, - &iter); - if (!sz) - return BLK_STS_IOERR; - - buff_offset += sz; - *pos += (sz >> SECTOR_SHIFT); - bv_len -= sz; - } - } - - return BLK_STS_OK; -} - -static inline void diff_area_event_corrupted(struct diff_area *diff_area, - int err_code) -{ - struct blk_snap_event_corrupted data = { - .orig_dev_id.mj = MAJOR(diff_area->orig_bdev->bd_dev), - .orig_dev_id.mn = MINOR(diff_area->orig_bdev->bd_dev), - .err_code = abs(err_code), - }; - - event_gen(&diff_area->diff_storage->event_queue, GFP_NOIO, - blk_snap_event_code_corrupted, &data, - sizeof(struct blk_snap_event_corrupted)); -} - -void diff_area_set_corrupted(struct diff_area *diff_area, int err_code) -{ - if (test_and_set_bit(0, &diff_area->corrupt_flag)) - return; - - diff_area_event_corrupted(diff_area, err_code); - - pr_err("Set snapshot device is corrupted for [%u:%u] with error code %d\n", - MAJOR(diff_area->orig_bdev->bd_dev), - MINOR(diff_area->orig_bdev->bd_dev), abs(err_code)); -} - -void diff_area_throttling_io(struct diff_area *diff_area) -{ - u64 start_waiting; - - start_waiting = jiffies_64; - while (atomic_read(&diff_area->pending_io_count)) { - schedule_timeout_interruptible(0); - if (jiffies_64 > (start_waiting + HZ / 10)) - break; - } -} - -#ifdef BLK_SNAP_DEBUG_SECTOR_STATE -int diff_area_get_sector_state(struct diff_area *diff_area, sector_t sector, - unsigned int *chunk_state) -{ - struct chunk *chunk; - sector_t chunk_sectors = diff_area_chunk_sectors(diff_area); - sector_t offset = round_down(sector, chunk_sectors); - - chunk = xa_load(&diff_area->chunk_map, chunk_number(diff_area, offset)); - if (!chunk) - return -EINVAL; - - WARN_ON(chunk_number(diff_area, offset) != chunk->number); - down(&chunk->lock); - *chunk_state = atomic_read(&chunk->state); - up(&chunk->lock); - - return 0; -} - -int diff_area_get_sector_image(struct diff_area *diff_area, sector_t pos, - void *buf) -{ - struct chunk *chunk; - struct diff_area_image_ctx io_ctx; - struct diff_buffer_iter diff_buffer_iter; - - diff_area_image_ctx_init(&io_ctx, diff_area, false); - chunk = diff_area_image_context_get_chunk(&io_ctx, pos); - if (IS_ERR(chunk)) - return PTR_ERR(chunk); - - diff_buffer_iter_get(chunk->diff_buffer, pos - chunk_sector(chunk), - &diff_buffer_iter); - memcpy(buf, - page_address(diff_buffer_iter.page) + diff_buffer_iter.offset, - SECTOR_SIZE); - - diff_area_image_ctx_done(&io_ctx); - return 0; -} - -#endif diff --git a/module/diff_area.h b/module/diff_area.h deleted file mode 100644 index 7a419fef..00000000 --- a/module/diff_area.h +++ /dev/null @@ -1,188 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef __BLK_SNAP_DIFF_AREA_H -#define __BLK_SNAP_DIFF_AREA_H - -#include -#include -#include -#include -#include -#include -#include -#include "event_queue.h" - -struct diff_storage; -struct chunk; - -/** - * struct diff_area - Discribes the difference area for one original device. - * @kref: - * The reference counter. The &struct diff_area can be shared between - * the &struct tracker and &struct snapimage. - * @orig_bdev: - * A pointer to the structure of an opened block device. - * @diff_storage: - * Pointer to difference storage for storing difference data. - * @chunk_shift: - * Power of 2 used to specify the chunk size. This allows to set different chunk sizes for - * huge and small block devices. - * @chunk_count: - * Count of chunks. The number of chunks into which the block device - * is divided. - * @chunk_map: - * A map of chunks. - * @in_memory: - * A sign that difference storage is not prepared and all differences are - * stored in RAM. - * @caches_lock: - * This spinlock guarantees consistency of the linked lists of chunk - * caches. - * @read_cache_queue: - * Queue for the read cache. - * @read_cache_count: - * The number of chunks in the read cache. - * @write_cache_queue: - * Queue for the write cache. - * @write_cache_count: - * The number of chunks in the write cache. - * @cache_release_work: - * The workqueue work item. This worker limits the number of chunks - * that store their data in RAM. - * @free_diff_buffers_lock: - * This spinlock guarantees consistency of the linked lists of - * free difference buffers. - * @free_diff_buffers: - * Linked list of free difference buffers allows to reduce the number - * of buffer allocation and release operations. - * @free_diff_buffers_count: - * The number of free difference buffers in the linked list. - * @corrupt_flag: - * The flag is set if an error occurred in the operation of the data - * saving mechanism in the diff area. In this case, an error will be - * generated when reading from the snapshot image. - * @pending_io_count: - * Counter of incomplete I/O operations. Allows to wait for all I/O - * operations to be completed before releasing this structure. - * - * The &struct diff_area is created for each block device in the snapshot. - * It is used to save the differences between the original block device and - * the snapshot image. That is, when writing data to the original device, - * the differences are copied as chunks to the difference storage. - * Reading and writing from the snapshot image is also performed using - * &struct diff_area. - * - * The xarray has a limit on the maximum size. This can be especially - * noticeable on 32-bit systems. This creates a limit in the size of - * supported disks. - * - * For example, for a 256 TiB disk with a block size of 65536 bytes, the - * number of elements in the chunk map will be equal to 2 with a power of 32. - * Therefore, the number of chunks into which the block device is divided is - * limited. - * - * To provide high performance, a read cache and a write cache for chunks are - * used. The cache algorithm is the simplest. If the data of the chunk was - * read to the difference buffer, then the buffer is not released immediately, - * but is placed at the end of the queue. The worker thread checks the number - * of chunks in the queue and releases a difference buffer for the first chunk - * in the queue, but only if the binary semaphore of the chunk is not locked. - * If the read thread accesses the chunk from the cache again, it returns - * back to the end of the queue. - * - * The linked list of difference buffers allows to have a certain number of - * "hot" buffers. This allows to reduce the number of allocations and releases - * of memory. - * - * - */ -struct diff_area { - struct kref kref; - - struct block_device *orig_bdev; - struct diff_storage *diff_storage; - - unsigned long long chunk_shift; - unsigned long chunk_count; - struct xarray chunk_map; -#ifdef BLK_SNAP_ALLOW_DIFF_STORAGE_IN_MEMORY - bool in_memory; -#endif - spinlock_t caches_lock; - struct list_head read_cache_queue; - atomic_t read_cache_count; - struct list_head write_cache_queue; - atomic_t write_cache_count; - struct work_struct cache_release_work; - - spinlock_t free_diff_buffers_lock; - struct list_head free_diff_buffers; - atomic_t free_diff_buffers_count; - - unsigned long corrupt_flag; - atomic_t pending_io_count; -}; - -struct diff_area *diff_area_new(dev_t dev_id, - struct diff_storage *diff_storage); -void diff_area_free(struct kref *kref); -static inline void diff_area_get(struct diff_area *diff_area) -{ - kref_get(&diff_area->kref); -}; -static inline void diff_area_put(struct diff_area *diff_area) -{ - if (likely(diff_area)) - kref_put(&diff_area->kref, diff_area_free); -}; -void diff_area_set_corrupted(struct diff_area *diff_area, int err_code); -static inline bool diff_area_is_corrupted(struct diff_area *diff_area) -{ - return !!diff_area->corrupt_flag; -}; -static inline sector_t diff_area_chunk_sectors(struct diff_area *diff_area) -{ - return (sector_t)(1ull << (diff_area->chunk_shift - SECTOR_SHIFT)); -}; -int diff_area_copy(struct diff_area *diff_area, sector_t sector, sector_t count, - const bool is_nowait); - -int diff_area_wait(struct diff_area *diff_area, sector_t sector, sector_t count, - const bool is_nowait); -/** - * struct diff_area_image_ctx - The context for processing an io request to - * the snapshot image. - * @diff_area: - * Pointer to &struct diff_area for the current snapshot image. - * @is_write: - * Distinguishes between the behavior of reading or writing when - * processing a request. - * @chunk: - * Current chunk. - */ -struct diff_area_image_ctx { - struct diff_area *diff_area; - bool is_write; - struct chunk *chunk; -}; - -static inline void diff_area_image_ctx_init(struct diff_area_image_ctx *io_ctx, - struct diff_area *diff_area, - bool is_write) -{ - io_ctx->diff_area = diff_area; - io_ctx->is_write = is_write; - io_ctx->chunk = NULL; -}; -void diff_area_image_ctx_done(struct diff_area_image_ctx *io_ctx); -blk_status_t diff_area_image_io(struct diff_area_image_ctx *io_ctx, - const struct bio_vec *bvec, sector_t *pos); - -void diff_area_throttling_io(struct diff_area *diff_area); - -#ifdef BLK_SNAP_DEBUG_SECTOR_STATE -int diff_area_get_sector_state(struct diff_area *diff_area, sector_t sector, - unsigned int *chunk_state); -int diff_area_get_sector_image(struct diff_area *diff_area, sector_t pos, - void *buf); -#endif -#endif /* __BLK_SNAP_DIFF_AREA_H */ diff --git a/module/diff_buffer.c b/module/diff_buffer.c deleted file mode 100644 index 5a1f1188..00000000 --- a/module/diff_buffer.c +++ /dev/null @@ -1,141 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#define pr_fmt(fmt) KBUILD_MODNAME "-diff-buffer: " fmt - -#include "memory_checker.h" -#include "diff_buffer.h" -#include "diff_area.h" -#include "log.h" - -extern int free_diff_buffer_pool_size; - -static void diff_buffer_free(struct diff_buffer *diff_buffer) -{ - size_t inx = 0; - - if (unlikely(!diff_buffer)) - return; - - for (inx = 0; inx < diff_buffer->page_count; inx++) { - struct page *page = diff_buffer->pages[inx]; - - if (page) { - __free_page(page); - memory_object_dec(memory_object_page); - } - } - - kfree(diff_buffer); - memory_object_dec(memory_object_diff_buffer); -} - -static struct diff_buffer * -diff_buffer_new(size_t page_count, size_t buffer_size, gfp_t gfp_mask) -{ - struct diff_buffer *diff_buffer; - size_t inx = 0; - struct page *page; - - if (unlikely(page_count <= 0)) - return NULL; - - /* - * In case of overflow, it is better to get a null pointer - * than a pointer to some memory area. Therefore + 1. - */ - diff_buffer = kzalloc(sizeof(struct diff_buffer) + - (page_count + 1) * sizeof(struct page *), - gfp_mask); - if (!diff_buffer) - return NULL; - memory_object_inc(memory_object_diff_buffer); - - INIT_LIST_HEAD(&diff_buffer->link); - diff_buffer->size = buffer_size; - diff_buffer->page_count = page_count; - - for (inx = 0; inx < page_count; inx++) { - page = alloc_page(gfp_mask); - if (!page) - goto fail; - memory_object_inc(memory_object_page); - - diff_buffer->pages[inx] = page; - } - return diff_buffer; -fail: - diff_buffer_free(diff_buffer); - return NULL; -} - -struct diff_buffer *diff_buffer_take(struct diff_area *diff_area, - const bool is_nowait) -{ - struct diff_buffer *diff_buffer = NULL; - sector_t chunk_sectors; - size_t page_count; - size_t buffer_size; - - spin_lock(&diff_area->free_diff_buffers_lock); - diff_buffer = list_first_entry_or_null(&diff_area->free_diff_buffers, - struct diff_buffer, link); - if (diff_buffer) { - list_del(&diff_buffer->link); - atomic_dec(&diff_area->free_diff_buffers_count); - } - spin_unlock(&diff_area->free_diff_buffers_lock); - - /* Return free buffer if it was found in a pool */ - if (diff_buffer) - return diff_buffer; - - /* Allocate new buffer */ - chunk_sectors = diff_area_chunk_sectors(diff_area); - page_count = round_up(chunk_sectors, PAGE_SECTORS) / PAGE_SECTORS; - buffer_size = chunk_sectors << SECTOR_SHIFT; - - diff_buffer = - diff_buffer_new(page_count, buffer_size, - is_nowait ? (GFP_NOIO | GFP_NOWAIT) : GFP_NOIO); - if (unlikely(!diff_buffer)) { - if (is_nowait) - return ERR_PTR(-EAGAIN); - else - return ERR_PTR(-ENOMEM); - } - - return diff_buffer; -} - -void diff_buffer_release(struct diff_area *diff_area, - struct diff_buffer *diff_buffer) -{ - if (atomic_read(&diff_area->free_diff_buffers_count) > - free_diff_buffer_pool_size) { - diff_buffer_free(diff_buffer); - return; - } - spin_lock(&diff_area->free_diff_buffers_lock); - list_add_tail(&diff_buffer->link, &diff_area->free_diff_buffers); - atomic_inc(&diff_area->free_diff_buffers_count); - spin_unlock(&diff_area->free_diff_buffers_lock); -} - -void diff_buffer_cleanup(struct diff_area *diff_area) -{ - struct diff_buffer *diff_buffer = NULL; - - do { - spin_lock(&diff_area->free_diff_buffers_lock); - diff_buffer = - list_first_entry_or_null(&diff_area->free_diff_buffers, - struct diff_buffer, link); - if (diff_buffer) { - list_del(&diff_buffer->link); - atomic_dec(&diff_area->free_diff_buffers_count); - } - spin_unlock(&diff_area->free_diff_buffers_lock); - - if (diff_buffer) - diff_buffer_free(diff_buffer); - } while (diff_buffer); -} diff --git a/module/diff_buffer.h b/module/diff_buffer.h deleted file mode 100644 index 84d401e6..00000000 --- a/module/diff_buffer.h +++ /dev/null @@ -1,79 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef __BLK_SNAP_DIFF_BUFFER_H -#define __BLK_SNAP_DIFF_BUFFER_H - -#include -#include -#include -#include - -struct diff_area; - -/** - * struct diff_buffer - Difference buffer. - * @link: - * The list header allows to create a pool of the diff_buffer structures. - * @size: - * Count of bytes in the buffer. - * @page_count: - * The number of pages reserved for the buffer. - * @pages: - * An array of pointers to pages. - * - * Describes the memory buffer for a chunk in the memory. - */ -struct diff_buffer { - struct list_head link; - size_t size; - size_t page_count; - struct page *pages[0]; -}; - -/** - * struct diff_buffer_iter - Iterator for &struct diff_buffer. - * @page: - * A pointer to the current page. - * @offset: - * The offset in bytes in the current page. - * @bytes: - * The number of bytes that can be read or written from the current page. - * - * It is convenient to use when copying data from or to &struct bio_vec. - */ -struct diff_buffer_iter { - struct page *page; - size_t offset; - size_t bytes; -}; - -#ifndef PAGE_SECTORS -#define PAGE_SECTORS (1 << (PAGE_SHIFT - SECTOR_SHIFT)) -#endif - -static inline bool diff_buffer_iter_get(struct diff_buffer *diff_buffer, - size_t buff_offset, - struct diff_buffer_iter *iter) -{ - if (diff_buffer->size <= buff_offset) - return false; - - iter->page = diff_buffer->pages[buff_offset >> PAGE_SHIFT]; - iter->offset = (size_t)(buff_offset & (PAGE_SIZE - 1)); - /* - * The size cannot exceed the size of the page, taking into account - * the offset in this page. - * But at the same time it is unacceptable to go beyond the allocated - * buffer. - */ - iter->bytes = min_t(size_t, (PAGE_SIZE - iter->offset), - (diff_buffer->size - buff_offset)); - - return true; -}; - -struct diff_buffer *diff_buffer_take(struct diff_area *diff_area, - const bool is_nowait); -void diff_buffer_release(struct diff_area *diff_area, - struct diff_buffer *diff_buffer); -void diff_buffer_cleanup(struct diff_area *diff_area); -#endif /* __BLK_SNAP_DIFF_BUFFER_H */ diff --git a/module/diff_io.c b/module/diff_io.c deleted file mode 100644 index e8607185..00000000 --- a/module/diff_io.c +++ /dev/null @@ -1,226 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#define pr_fmt(fmt) KBUILD_MODNAME "-diff-io: " fmt - -#ifdef HAVE_GENHD_H -#include -#endif -#include -#include -#include "memory_checker.h" -#include "diff_io.h" -#include "diff_buffer.h" -#include "log.h" - -#ifdef STANDALONE_BDEVFILTER -#ifndef PAGE_SECTORS -#define PAGE_SECTORS (1 << (PAGE_SHIFT - SECTOR_SHIFT)) -#endif -#endif - -struct bio_set diff_io_bioset; - -int diff_io_init(void) -{ - return bioset_init(&diff_io_bioset, 64, 0, - BIOSET_NEED_BVECS | BIOSET_NEED_RESCUER); -} - -void diff_io_done(void) -{ - bioset_exit(&diff_io_bioset); -} - -static void diff_io_notify_cb(struct work_struct *work) -{ - struct diff_io_async *async = - container_of(work, struct diff_io_async, work); - - might_sleep(); - async->notify_cb(async->ctx); -} - -#ifdef STANDALONE_BDEVFILTER -void diff_io_endio(struct bio *bio) -#else -static void diff_io_endio(struct bio *bio) -#endif -{ - struct diff_io *diff_io = bio->bi_private; - - if (bio->bi_status != BLK_STS_OK) - diff_io->error = -EIO; - - if (atomic_dec_and_test(&diff_io->bio_count)) { - if (diff_io->is_sync_io) - complete(&diff_io->notify.sync.completion); - else - queue_work(system_wq, &diff_io->notify.async.work); - } - - bio_put(bio); -} - -static inline struct diff_io *diff_io_new(bool is_write, bool is_nowait) -{ - struct diff_io *diff_io; - gfp_t gfp_mask = is_nowait ? (GFP_NOIO | GFP_NOWAIT) : GFP_NOIO; - - diff_io = kzalloc(sizeof(struct diff_io), gfp_mask); - if (unlikely(!diff_io)) - return NULL; - memory_object_inc(memory_object_diff_io); - - diff_io->error = 0; - diff_io->is_write = is_write; - atomic_set(&diff_io->bio_count, 0); - - return diff_io; -} - -struct diff_io *diff_io_new_sync(bool is_write) -{ - struct diff_io *diff_io; - - diff_io = diff_io_new(is_write, false); - if (unlikely(!diff_io)) - return NULL; - - diff_io->is_sync_io = true; - init_completion(&diff_io->notify.sync.completion); - return diff_io; -} - -struct diff_io *diff_io_new_async(bool is_write, bool is_nowait, - void (*notify_cb)(void *ctx), void *ctx) -{ - struct diff_io *diff_io; - - diff_io = diff_io_new(is_write, is_nowait); - if (unlikely(!diff_io)) - return NULL; - - diff_io->is_sync_io = false; - INIT_WORK(&diff_io->notify.async.work, diff_io_notify_cb); - diff_io->notify.async.ctx = ctx; - diff_io->notify.async.notify_cb = notify_cb; - return diff_io; -} - -static inline bool check_page_aligned(sector_t sector) -{ - return !(sector & ((1ull << (PAGE_SHIFT - SECTOR_SHIFT)) - 1)); -} - -static inline unsigned short calc_page_count(sector_t sectors) -{ - return round_up(sectors, PAGE_SECTORS) / PAGE_SECTORS; -} - -#ifdef HAVE_BIO_MAX_PAGES -static inline unsigned int bio_max_segs(unsigned int nr_segs) -{ - return min(nr_segs, (unsigned int)BIO_MAX_PAGES); -} -#endif - -/* - * diff_io_do() - Perform an I/O operation. - * - * Returns zero if successful. Failure is possible if the is_nowait flag is set - * and a failure was occured when allocating memory. In this case, the error - * code -EAGAIN is returned. The error code -EINVAL means that the input - * arguments are incorrect. - */ -int diff_io_do(struct diff_io *diff_io, struct diff_region *diff_region, - struct diff_buffer *diff_buffer, const bool is_nowait) -{ - struct bio *bio; - struct bio_list bio_list_head = BIO_EMPTY_LIST; - struct page **current_page_ptr; - sector_t processed = 0; - gfp_t gfp = GFP_NOIO | (is_nowait ? GFP_NOWAIT : 0); - unsigned int opf = diff_io->is_write ? REQ_OP_WRITE : REQ_OP_READ; - unsigned op_flags = REQ_SYNC | (diff_io->is_write ? REQ_FUA : 0); - - if (unlikely(!check_page_aligned(diff_region->sector))) { - pr_err("Difference storage block should be aligned to PAGE_SIZE\n"); - return -EINVAL; - } - - if (unlikely(calc_page_count(diff_region->count) > - diff_buffer->page_count)) { - pr_err("The difference storage block is larger than the buffer size\n"); - return -EINVAL; - } - - /* Append bio with datas to bio_list */ - current_page_ptr = diff_buffer->pages; - while (processed < diff_region->count) { - sector_t offset = 0; - sector_t portion; - unsigned short nr_iovecs; - - portion = diff_region->count - processed; - nr_iovecs = calc_page_count(portion); - - if (nr_iovecs > bio_max_segs(nr_iovecs)) { - nr_iovecs = bio_max_segs(nr_iovecs); - portion = nr_iovecs * PAGE_SECTORS; - } - -#ifdef HAVE_BDEV_BIO_ALLOC - bio = bio_alloc_bioset(diff_region->bdev, nr_iovecs, - opf | op_flags, gfp, &diff_io_bioset); -#else - bio = bio_alloc_bioset(gfp, nr_iovecs, &diff_io_bioset); -#endif - if (unlikely(!bio)) - goto fail; - -#ifndef STANDALONE_BDEVFILTER - bio_set_flag(bio, BIO_FILTERED); -#endif - bio->bi_end_io = diff_io_endio; - bio->bi_private = diff_io; - bio_set_dev(bio, diff_region->bdev); - bio->bi_iter.bi_sector = diff_region->sector + processed; - -#ifndef HAVE_BDEV_BIO_ALLOC - bio_set_op_attrs(bio, opf, op_flags); -#endif - - while (offset < portion) { - sector_t bvec_len_sect; - unsigned int bvec_len; - - bvec_len_sect = min_t(sector_t, PAGE_SECTORS, - portion - offset); - bvec_len = - (unsigned int)(bvec_len_sect << SECTOR_SHIFT); - - /* All pages offset aligned to PAGE_SIZE */ - __bio_add_page(bio, *current_page_ptr, bvec_len, 0); - - current_page_ptr++; - offset += bvec_len_sect; - } - - bio_list_add(&bio_list_head, bio); - atomic_inc(&diff_io->bio_count); - - processed += offset; - } - - /* sumbit all bios */ - while ((bio = bio_list_pop(&bio_list_head))) - submit_bio_noacct(bio); - - if (diff_io->is_sync_io) - wait_for_completion_io(&diff_io->notify.sync.completion); - - return 0; -fail: - while ((bio = bio_list_pop(&bio_list_head))) - bio_put(bio); - return -EAGAIN; -} diff --git a/module/diff_io.h b/module/diff_io.h deleted file mode 100644 index 0eb1468f..00000000 --- a/module/diff_io.h +++ /dev/null @@ -1,123 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef __BLK_SNAP_DIFF_IO_H -#define __BLK_SNAP_DIFF_IO_H - -#include -#include - -struct diff_buffer; - -/** - * struct diff_region - Describes the location of the chunks data on - * difference storage. - * @bdev: - * The target block device. - * @sector: - * The sector offset of the region's first sector. - * @count: - * The count of sectors in the region. - */ -struct diff_region { - struct block_device *bdev; - sector_t sector; - sector_t count; -}; - -/** - * struct diff_io_sync - Structure for notification about completion of - * synchronous I/O. - * @completion: - * Indicates that the request has been processed. - * - * Allows to wait for completion of the I/O operation in the - * current thread. - */ -struct diff_io_sync { - struct completion completion; -}; - -/** - * struct diff_io_async - Structure for notification about completion of - * asynchronous I/O. - * @work: - * The &struct work_struct allows to schedule execution of an I/O operation - * in a separate process. - * @notify_cb: - * A pointer to the callback function that will be executed when - * the I/O execution is completed. - * @ctx: - * The context for the callback function ¬ify_cb. - * - * Allows to schedule execution of an I/O operation. - */ -struct diff_io_async { - struct work_struct work; - void (*notify_cb)(void *ctx); - void *ctx; -}; - -/** - * struct diff_io - Structure for I/O maintenance. - * @error: - * Zero if the I/O operation is successful, or an error code if it fails. - * @bio_count: - * The count of I/O units in request. - * @is_write: - * Indicates that a write operation is being performed. - * @is_sync_io: - * Indicates that the operation is being performed synchronously. - * @notify: - * This union may contain the diff_io_sync or diff_io_async structure - * for synchronous or asynchronous request. - * - * The request to perform an I/O operation is executed for a region of sectors. - * Such a region may contain several bios. It is necessary to notify about the - * completion of processing of all bios. The diff_io structure allows to do it. - */ -struct diff_io { - int error; - atomic_t bio_count; - bool is_write; - bool is_sync_io; - union { - struct diff_io_sync sync; - struct diff_io_async async; - } notify; -}; - -int diff_io_init(void); -void diff_io_done(void); - -static inline void diff_io_free(struct diff_io *diff_io) -{ - kfree(diff_io); - if (diff_io) - memory_object_dec(memory_object_diff_io); -} - -struct diff_io *diff_io_new_sync(bool is_write); -static inline struct diff_io *diff_io_new_sync_read(void) -{ - return diff_io_new_sync(false); -}; -static inline struct diff_io *diff_io_new_sync_write(void) -{ - return diff_io_new_sync(true); -}; - -struct diff_io *diff_io_new_async(bool is_write, bool is_nowait, - void (*notify_cb)(void *ctx), void *ctx); -static inline struct diff_io * -diff_io_new_async_read(void (*notify_cb)(void *ctx), void *ctx, bool is_nowait) -{ - return diff_io_new_async(false, is_nowait, notify_cb, ctx); -}; -static inline struct diff_io * -diff_io_new_async_write(void (*notify_cb)(void *ctx), void *ctx, bool is_nowait) -{ - return diff_io_new_async(true, is_nowait, notify_cb, ctx); -}; - -int diff_io_do(struct diff_io *diff_io, struct diff_region *diff_region, - struct diff_buffer *diff_buffer, const bool is_nowait); -#endif /* __BLK_SNAP_DIFF_IO_H */ diff --git a/module/diff_storage.c b/module/diff_storage.c deleted file mode 100644 index d98a2ec2..00000000 --- a/module/diff_storage.c +++ /dev/null @@ -1,337 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#define pr_fmt(fmt) KBUILD_MODNAME "-diff-storage: " fmt - -#include -#include -#include -#include -#ifdef STANDALONE_BDEVFILTER -#include "blksnap.h" -#else -#include -#endif -#include "memory_checker.h" -#include "chunk.h" -#include "diff_io.h" -#include "diff_buffer.h" -#include "diff_storage.h" -#include "log.h" - -extern int diff_storage_minimum; - -#ifndef PAGE_SECTORS -#define PAGE_SECTORS (1 << (PAGE_SHIFT - SECTOR_SHIFT)) -#endif - -/** - * struct storage_bdev - Information about the opened block device. - * - * @link: - * Allows to combine structures into a linked list. - * @dev_id: - * ID of the block device. - * @bdev: - * A pointer to an open block device. - */ -struct storage_bdev { - struct list_head link; - dev_t dev_id; - struct block_device *bdev; -}; - -/** - * struct storage_block - A storage unit reserved for storing differences. - * - * @link: - * Allows to combine structures into a linked list. - * @bdev: - * A pointer to a block device. - * @sector: - * The number of the first sector of the range of allocated space for - * storing the difference. - * @count: - * The count of sectors in the range of allocated space for storing the - * difference. - * @used: - * The count of used sectors in the range of allocated space for storing - * the difference. - */ -struct storage_block { - struct list_head link; - struct block_device *bdev; - sector_t sector; - sector_t count; - sector_t used; -}; - -static inline void diff_storage_event_low(struct diff_storage *diff_storage) -{ - struct blk_snap_event_low_free_space data = { - .requested_nr_sect = diff_storage_minimum, - }; - - diff_storage->requested += data.requested_nr_sect; - pr_debug("Diff storage low free space. Portion: %llu sectors, requested: %llu\n", - data.requested_nr_sect, diff_storage->requested); - event_gen(&diff_storage->event_queue, GFP_NOIO, - blk_snap_event_code_low_free_space, &data, sizeof(data)); -} - -struct diff_storage *diff_storage_new(void) -{ - struct diff_storage *diff_storage; - - diff_storage = kzalloc(sizeof(struct diff_storage), GFP_KERNEL); - if (!diff_storage) - return NULL; - memory_object_inc(memory_object_diff_storage); - - kref_init(&diff_storage->kref); - spin_lock_init(&diff_storage->lock); - INIT_LIST_HEAD(&diff_storage->storage_bdevs); - INIT_LIST_HEAD(&diff_storage->empty_blocks); - INIT_LIST_HEAD(&diff_storage->filled_blocks); - - event_queue_init(&diff_storage->event_queue); - diff_storage_event_low(diff_storage); - - return diff_storage; -} - -static inline struct storage_block * -first_empty_storage_block(struct diff_storage *diff_storage) -{ - return list_first_entry_or_null(&diff_storage->empty_blocks, - struct storage_block, link); -}; - -static inline struct storage_block * -first_filled_storage_block(struct diff_storage *diff_storage) -{ - return list_first_entry_or_null(&diff_storage->filled_blocks, - struct storage_block, link); -}; - -static inline struct storage_bdev * -first_storage_bdev(struct diff_storage *diff_storage) -{ - return list_first_entry_or_null(&diff_storage->storage_bdevs, - struct storage_bdev, link); -}; - -void diff_storage_free(struct kref *kref) -{ - struct diff_storage *diff_storage = - container_of(kref, struct diff_storage, kref); - struct storage_block *blk; - struct storage_bdev *storage_bdev; - - while ((blk = first_empty_storage_block(diff_storage))) { - list_del(&blk->link); - kfree(blk); - memory_object_dec(memory_object_storage_block); - } - - while ((blk = first_filled_storage_block(diff_storage))) { - list_del(&blk->link); - kfree(blk); - memory_object_dec(memory_object_storage_block); - } - - while ((storage_bdev = first_storage_bdev(diff_storage))) { - blkdev_put(storage_bdev->bdev, FMODE_READ | FMODE_WRITE); - list_del(&storage_bdev->link); - kfree(storage_bdev); - memory_object_dec(memory_object_storage_bdev); - } - event_queue_done(&diff_storage->event_queue); - - kfree(diff_storage); - memory_object_dec(memory_object_diff_storage); -} - -static struct block_device * -diff_storage_bdev_by_id(struct diff_storage *diff_storage, dev_t dev_id) -{ - struct block_device *bdev = NULL; - struct storage_bdev *storage_bdev; - - spin_lock(&diff_storage->lock); - list_for_each_entry(storage_bdev, &diff_storage->storage_bdevs, link) { - if (storage_bdev->dev_id == dev_id) { - bdev = storage_bdev->bdev; - break; - } - } - spin_unlock(&diff_storage->lock); - - return bdev; -} - -static inline struct block_device * -diff_storage_add_storage_bdev(struct diff_storage *diff_storage, dev_t dev_id) -{ - struct block_device *bdev; - struct storage_bdev *storage_bdev; - - bdev = blkdev_get_by_dev(dev_id, FMODE_READ | FMODE_WRITE, NULL); - if (IS_ERR(bdev)) { - pr_err("Failed to open device. errno=%d\n", - abs((int)PTR_ERR(bdev))); - return bdev; - } - - storage_bdev = kzalloc(sizeof(struct storage_bdev), GFP_KERNEL); - if (!storage_bdev) { - blkdev_put(bdev, FMODE_READ | FMODE_WRITE); - return ERR_PTR(-ENOMEM); - } - memory_object_inc(memory_object_storage_bdev); - - storage_bdev->bdev = bdev; - storage_bdev->dev_id = dev_id; - INIT_LIST_HEAD(&storage_bdev->link); - - spin_lock(&diff_storage->lock); - list_add_tail(&storage_bdev->link, &diff_storage->storage_bdevs); - spin_unlock(&diff_storage->lock); - - return bdev; -} - -static inline int diff_storage_add_range(struct diff_storage *diff_storage, - struct block_device *bdev, - sector_t sector, sector_t count) -{ - struct storage_block *storage_block; - - pr_debug("Add range to diff storage: [%u:%u] %llu:%llu\n", - MAJOR(bdev->bd_dev), MINOR(bdev->bd_dev), sector, count); - - storage_block = kzalloc(sizeof(struct storage_block), GFP_KERNEL); - if (!storage_block) - return -ENOMEM; - memory_object_inc(memory_object_storage_block); - - INIT_LIST_HEAD(&storage_block->link); - storage_block->bdev = bdev; - storage_block->sector = sector; - storage_block->count = count; - - spin_lock(&diff_storage->lock); - list_add_tail(&storage_block->link, &diff_storage->empty_blocks); - - diff_storage->capacity += count; - spin_unlock(&diff_storage->lock); - - return 0; -} - -int diff_storage_append_block(struct diff_storage *diff_storage, dev_t dev_id, - struct blk_snap_block_range __user *ranges, - unsigned int range_count) -{ - int ret; - int inx; - struct block_device *bdev; - struct blk_snap_block_range range; - const unsigned long range_size = sizeof(struct blk_snap_block_range); - - pr_debug("Append %u blocks\n", range_count); - - bdev = diff_storage_bdev_by_id(diff_storage, dev_id); - if (!bdev) { - bdev = diff_storage_add_storage_bdev(diff_storage, dev_id); - if (IS_ERR(bdev)) - return PTR_ERR(bdev); - } - - for (inx = 0; inx < range_count; inx++) { - if (unlikely(copy_from_user(&range, ranges+inx, range_size))) - return -EINVAL; - - ret = diff_storage_add_range(diff_storage, bdev, - range.sector_offset, - range.sector_count); - if (unlikely(ret)) - return ret; - } - - if (atomic_read(&diff_storage->low_space_flag) && - (diff_storage->capacity >= diff_storage->requested)) - atomic_set(&diff_storage->low_space_flag, 0); - - return 0; -} - -static inline bool is_halffull(const sector_t sectors_left) -{ - return sectors_left <= ((diff_storage_minimum >> 1) & ~(PAGE_SECTORS - 1)); -} - -struct diff_region *diff_storage_new_region(struct diff_storage *diff_storage, - sector_t count) -{ - int ret = 0; - struct diff_region *diff_region; - sector_t sectors_left; - - if (atomic_read(&diff_storage->overflow_flag)) - return ERR_PTR(-ENOSPC); - - diff_region = kzalloc(sizeof(struct diff_region), GFP_NOIO); - if (!diff_region) - return ERR_PTR(-ENOMEM); - memory_object_inc(memory_object_diff_region); - - spin_lock(&diff_storage->lock); - do { - struct storage_block *storage_block; - sector_t available; - - storage_block = first_empty_storage_block(diff_storage); - if (unlikely(!storage_block)) { - atomic_inc(&diff_storage->overflow_flag); - ret = -ENOSPC; - break; - } - - available = storage_block->count - storage_block->used; - if (likely(available >= count)) { - diff_region->bdev = storage_block->bdev; - diff_region->sector = - storage_block->sector + storage_block->used; - diff_region->count = count; - - storage_block->used += count; - diff_storage->filled += count; - break; - } - - list_del(&storage_block->link); - list_add_tail(&storage_block->link, - &diff_storage->filled_blocks); - /* - * If there is still free space in the storage block, but - * it is not enough to store a piece, then such a block is - * considered used. - * We believe that the storage blocks are large enough - * to accommodate several pieces entirely. - */ - diff_storage->filled += available; - } while (1); - sectors_left = diff_storage->requested - diff_storage->filled; - spin_unlock(&diff_storage->lock); - - if (ret) { - pr_err("Cannot get empty storage block\n"); - diff_storage_free_region(diff_region); - return ERR_PTR(ret); - } - - if (is_halffull(sectors_left) && - (atomic_inc_return(&diff_storage->low_space_flag) == 1)) - diff_storage_event_low(diff_storage); - - return diff_region; -} diff --git a/module/diff_storage.h b/module/diff_storage.h deleted file mode 100644 index 408c109a..00000000 --- a/module/diff_storage.h +++ /dev/null @@ -1,95 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef __BLK_SNAP_DIFF_STORAGE_H -#define __BLK_SNAP_DIFF_STORAGE_H - -#include "event_queue.h" - -struct blk_snap_block_range; -struct diff_region; - -/** - * struct diff_storage - Difference storage. - * - * @kref: - * The reference counter. - * @lock: - * Spinlock allows to guarantee the safety of linked lists. - * @storage_bdevs: - * List of opened block devices. Blocks for storing snapshot data can be - * located on different block devices. So, all opened block devices are - * located in this list. Blocks on opened block devices are allocated for - * storing the chunks data. - * @empty_blocks: - * List of empty blocks on storage. This list can be updated while - * holding a snapshot. This allows us to dynamically increase the - * storage size for these snapshots. - * @filled_blocks: - * List of filled blocks. When the blocks from the list of empty blocks are filled, - * we move them to the list of filled blocks. - * @capacity: - * Total amount of available storage space. - * @filled: - * The number of sectors already filled in. - * @requested: - * The number of sectors already requested from user space. - * @low_space_flag: - * The flag is set if the number of free regions available in the - * difference storage is less than the allowed minimum. - * @overflow_flag: - * The request for a free region failed due to the absence of free - * regions in the difference storage. - * @event_queue: - * A queue of events to pass events to user space. Diff storage and its - * owner can notify its snapshot about events like snapshot overflow, - * low free space and snapshot terminated. - * - * The difference storage manages the regions of block devices that are used - * to store the data of the original block devices in the snapshot. - * The difference storage is created one per snapshot and is used to store - * data from all the original snapshot block devices. At the same time, the - * difference storage itself can contain regions on various block devices. - */ -struct diff_storage { - struct kref kref; - spinlock_t lock; - - struct list_head storage_bdevs; - struct list_head empty_blocks; - struct list_head filled_blocks; - - sector_t capacity; - sector_t filled; - sector_t requested; - - atomic_t low_space_flag; - atomic_t overflow_flag; - - struct event_queue event_queue; -}; - -struct diff_storage *diff_storage_new(void); -void diff_storage_free(struct kref *kref); - -static inline void diff_storage_get(struct diff_storage *diff_storage) -{ - kref_get(&diff_storage->kref); -}; -static inline void diff_storage_put(struct diff_storage *diff_storage) -{ - if (likely(diff_storage)) - kref_put(&diff_storage->kref, diff_storage_free); -}; - -int diff_storage_append_block(struct diff_storage *diff_storage, dev_t dev_id, - struct blk_snap_block_range __user *ranges, - unsigned int range_count); -struct diff_region *diff_storage_new_region(struct diff_storage *diff_storage, - sector_t count); - -static inline void diff_storage_free_region(struct diff_region *region) -{ - kfree(region); - if (region) - memory_object_dec(memory_object_diff_region); -} -#endif /* __BLK_SNAP_DIFF_STORAGE_H */ diff --git a/module/event_queue.c b/module/event_queue.c deleted file mode 100644 index c3872dd7..00000000 --- a/module/event_queue.c +++ /dev/null @@ -1,89 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#define pr_fmt(fmt) KBUILD_MODNAME "-event_queue: " fmt - -#include -#include -#include "memory_checker.h" -#include "event_queue.h" -#include "log.h" - -void event_queue_init(struct event_queue *event_queue) -{ - INIT_LIST_HEAD(&event_queue->list); - spin_lock_init(&event_queue->lock); - init_waitqueue_head(&event_queue->wq_head); -} - -void event_queue_done(struct event_queue *event_queue) -{ - struct event *event; - - spin_lock(&event_queue->lock); - while (!list_empty(&event_queue->list)) { - event = list_first_entry(&event_queue->list, struct event, - link); - list_del(&event->link); - event_free(event); - } - spin_unlock(&event_queue->lock); -} - -int event_gen(struct event_queue *event_queue, gfp_t flags, int code, - const void *data, int data_size) -{ - struct event *event; - - event = kzalloc(sizeof(struct event) + data_size, flags); - if (!event) - return -ENOMEM; - memory_object_inc(memory_object_event); - - event->time = ktime_get(); - event->code = code; - event->data_size = data_size; - memcpy(event->data, data, data_size); - - pr_debug("Generate event: time=%lld code=%d data_size=%d\n", - event->time, event->code, event->data_size); - - spin_lock(&event_queue->lock); - list_add_tail(&event->link, &event_queue->list); - spin_unlock(&event_queue->lock); - - wake_up(&event_queue->wq_head); - return 0; -} - -struct event *event_wait(struct event_queue *event_queue, - unsigned long timeout_ms) -{ - int ret; - - ret = wait_event_interruptible_timeout(event_queue->wq_head, - !list_empty(&event_queue->list), - timeout_ms); - - if (ret > 0) { - struct event *event; - - spin_lock(&event_queue->lock); - event = list_first_entry(&event_queue->list, struct event, - link); - list_del(&event->link); - spin_unlock(&event_queue->lock); - - pr_debug("Event received: time=%lld code=%d\n", event->time, - event->code); - return event; - } - if (ret == 0) - return ERR_PTR(-ENOENT); - - if (ret == -ERESTARTSYS) { - pr_debug("event waiting interrupted\n"); - return ERR_PTR(-EINTR); - } - - pr_err("Failed to wait event. errno=%d\n", abs(ret)); - return ERR_PTR(ret); -} diff --git a/module/event_queue.h b/module/event_queue.h deleted file mode 100644 index a10e27d4..00000000 --- a/module/event_queue.h +++ /dev/null @@ -1,65 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef __BLK_SNAP_EVENT_QUEUE_H -#define __BLK_SNAP_EVENT_QUEUE_H - -#include -#include -#include -#include -#include - -/** - * struct event - An event to be passed to the user space. - * @link: - * The list header allows to combine events from the queue. - * @time: - * A timestamp indicates when an event occurred. - * @code: - * Event code. - * @data_size: - * The number of bytes in the event data array. - * @data: - * An array of event data. - * - * Events can be different, so they contain different data. The size of the - * data array is not defined exactly, but it has limitations. The size of - * the event structure may exceed the PAGE_SIZE. - */ -struct event { - struct list_head link; - ktime_t time; - int code; - int data_size; - char data[1]; /* up to PAGE_SIZE - sizeof(struct blk_snap_snapshot_event) */ -}; - -/** - * struct event_queue - A queue of &struct event. - * @list: - * Linked list for storing events. - * @lock: - * Spinlock allows to guarantee safety of the linked list. - * @wq_head: - * A wait queue allows to put a user thread in a waiting state until - * an event appears in the linked list. - */ -struct event_queue { - struct list_head list; - spinlock_t lock; - struct wait_queue_head wq_head; -}; - -void event_queue_init(struct event_queue *event_queue); -void event_queue_done(struct event_queue *event_queue); - -int event_gen(struct event_queue *event_queue, gfp_t flags, int code, - const void *data, int data_size); -struct event *event_wait(struct event_queue *event_queue, - unsigned long timeout_ms); -static inline void event_free(struct event *event) -{ - kfree(event); - if (event) - memory_object_dec(memory_object_event); -}; -#endif /* __BLK_SNAP_EVENT_QUEUE_H */ diff --git a/module/log.c b/module/log.c deleted file mode 100644 index 8e58018d..00000000 --- a/module/log.c +++ /dev/null @@ -1,395 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#define pr_fmt(fmt) KBUILD_MODNAME "-log: " fmt -#include -#include -#include -#include -#include -#ifdef STANDALONE_BDEVFILTER -#include "blksnap.h" -#else -#include -#endif -#include "version.h" -#include "memory_checker.h" -#include "log.h" - -#ifdef BLK_SNAP_FILELOG - -struct log_request_header { - struct timespec64 time; - pid_t pid; - unsigned level; - size_t size; -}; - -#define LOG_REQUEST_BUFFER_SIZE \ - (512 - sizeof(struct list_head) - sizeof(struct log_request_header)) - -struct log_request { - struct list_head link; - struct log_request_header header; - char buffer[LOG_REQUEST_BUFFER_SIZE]; -}; - -#define LOG_REQUEST_POOL_SIZE 128 -struct log_request log_requests_pool[LOG_REQUEST_POOL_SIZE]; - -static int log_level = -1; -static char *log_filepath = NULL; -static int log_tz_minuteswest = 0; - -static LIST_HEAD(log_free_requests); -static DEFINE_SPINLOCK(log_free_requests_lock); - -static LIST_HEAD(log_active_requests); -static DEFINE_SPINLOCK(log_active_requests_lock); - -static DECLARE_WAIT_QUEUE_HEAD(log_request_event_add); -static struct task_struct* log_task = NULL; -static atomic_t log_missed_counter = {0}; - -static inline const char* get_module_name(void) -{ -#ifdef MODULE - return THIS_MODULE->name; -#else - return "kernel"; -#endif -} - -void log_init(void) -{ - size_t inx; - - INIT_LIST_HEAD(&log_free_requests); - for (inx=0; inx < LOG_REQUEST_POOL_SIZE; inx++) { - struct log_request *rq = &log_requests_pool[inx]; - - INIT_LIST_HEAD(&rq->link); - list_add_tail(&rq->link, &log_free_requests); - } -} - -static inline void done_task(void) -{ - if (!log_task) - return; - - kthread_stop(log_task); - log_task = NULL; -} - -static inline void done_filepath(void) -{ - if (!log_filepath) - return; - - kfree(log_filepath); - memory_object_dec(memory_object_log_filepath); - log_filepath = NULL; -} - -void log_done(void) -{ - log_level = -1; - - done_task(); - done_filepath(); -} - -static inline struct log_request *log_request_new(void) -{ - struct log_request *rq; - - spin_lock(&log_free_requests_lock); - rq = list_first_entry_or_null(&log_free_requests, - struct log_request, link); - if (rq) - list_del(&rq->link); - else - atomic_inc(&log_missed_counter); - spin_unlock(&log_free_requests_lock); - - return rq; -} - - -static inline void log_request_free(struct log_request *rq) -{ - INIT_LIST_HEAD(&rq->link); - - spin_lock(&log_free_requests_lock); - list_add_tail(&rq->link, &log_free_requests); - spin_unlock(&log_free_requests_lock); -} - -static inline void log_request_push(struct log_request *rq) -{ - INIT_LIST_HEAD(&rq->link); - - spin_lock(&log_active_requests_lock); - list_add_tail(&rq->link, &log_active_requests); - spin_unlock(&log_active_requests_lock); -} - -static inline struct log_request *log_request_get(void) -{ - struct log_request *rq; - - spin_lock(&log_active_requests_lock); - rq = list_first_entry_or_null(&log_active_requests, - struct log_request, link); - if (rq) - list_del(&rq->link); - spin_unlock(&log_active_requests_lock); - - return rq; -} - -static inline bool log_request_is_ready(void) -{ - bool ret; - - spin_lock(&log_active_requests_lock); - ret = !list_empty(&log_active_requests); - spin_unlock(&log_active_requests_lock); - - return ret; -} - -#define MAX_PREFIX_SIZE 256 -static const char *log_level_text[] = {"EMERG :","ALERT :","CRIT :","ERR :","WRN :","","",""}; - -static inline void log_request_write(struct file* filp, const struct log_request* rq) -{ - int size; - struct tm time; - char prefix_buf[MAX_PREFIX_SIZE]; - - if (!filp) - return; - if (!rq) - return; - - time64_to_tm(rq->header.time.tv_sec, (-sys_tz.tz_minuteswest + log_tz_minuteswest) * 60, &time); - - size = snprintf(prefix_buf, MAX_PREFIX_SIZE, - "[%02d.%02d.%04ld %02d:%02d:%02d-%06ld] <%d> | %s", - time.tm_mday, time.tm_mon + 1, time.tm_year + 1900, - time.tm_hour, time.tm_min, time.tm_sec, - rq->header.time.tv_nsec / 1000, - rq->header.pid, log_level_text[rq->header.level] - ); - - kernel_write(filp, prefix_buf, size, &filp->f_pos); - kernel_write(filp, rq->buffer, rq->header.size, &filp->f_pos); -} - -static inline bool log_waiting(void) -{ - int ret; - - ret = wait_event_interruptible_timeout(log_request_event_add, - log_request_is_ready() || kthread_should_stop(), 10 * HZ); - - return (ret > 0); -} - -static inline void log_request_fill(struct log_request *rq, const int level, - const char *fmt, va_list args) -{ - ktime_get_real_ts64(&rq->header.time); - rq->header.pid = get_current()->pid; - rq->header.level = level; - rq->header.size = vscnprintf(rq->buffer, LOG_REQUEST_BUFFER_SIZE, fmt, args); -} - -static inline void log_vprintk_direct(struct file* filp, const int level, const char *fmt, va_list args) -{ - struct log_request rq = {0}; - - log_request_fill(&rq, level, fmt, args); - log_request_write(filp, &rq); -} - -static inline void log_printk_direct(struct file* filp, const int level, const char *fmt, ...) -{ - va_list args; - - va_start(args, fmt); - log_vprintk_direct(filp, level, fmt, args); - va_end(args); -} - -static inline struct file* log_reopen(struct file* filp) -{ - if (filp) - return filp; - - if (!log_filepath) - return NULL; - - filp = filp_open(log_filepath, O_WRONLY | O_APPEND | O_CREAT, 0644); - if (IS_ERR(filp)) { - printk(KERN_ERR pr_fmt("Failed to open file %s\n"), log_filepath); - done_filepath(); - return NULL; - } - return filp; -} - -static inline struct file* log_close(struct file* filp) -{ - if (filp) { - filp_close(filp, NULL); - filp = NULL; - } - - return filp; -} - -int log_processor(void *data) -{ - int ret = 0; - struct log_request *rq; - struct file* filp = NULL; - int missed; - - while (!kthread_should_stop()) { - missed = atomic_read(&log_missed_counter); - if (missed) { - atomic_sub(missed, &log_missed_counter); - - filp = log_reopen(filp); - log_printk_direct(filp, LOGLEVEL_INFO, - "Missed %d messages\n", missed); - } - - rq = log_request_get(); - if (rq) { - filp = log_reopen(filp); - log_request_write(filp, rq); - log_request_free(rq); - if (!filp) - break; - } else - if (!log_waiting()) - filp = log_close(filp); - } - - filp = log_reopen(filp); - while ((rq = log_request_get())) { - log_request_write(filp, rq); - log_request_free(rq); - } - - log_printk_direct(filp, LOGLEVEL_INFO, "Stop log for module %s\n\n", - get_module_name()); - filp = log_close(filp); - - return ret; -} - -int log_restart(int level, char *filepath, int tz_minuteswest) -{ - int ret = 0; - struct file* filp; - struct task_struct* task; - - if ((level < 0) && !filepath){ - /* - * Disable logging - */ - log_done(); - return 0; - } - - if (!filepath) - return -EINVAL; - - if (log_filepath && (strcmp(filepath, log_filepath) == 0)) { - if (level == log_level) { - /* - * If the request is executed for the same parameters - * that are already set for logging, then logging is - * not restarted and an error code EALREADY is returned. - */ - ret = -EALREADY; - } else if (level >= 0) { - /* - * If only the logging level changes, then - * there is no need to restart logging. - */ - log_level = level; - ret = 0; - } - goto fail; - } - - log_done(); - log_init(); - - filp = filp_open(filepath, O_WRONLY | O_APPEND | O_CREAT, 0644); - if (IS_ERR(filp)) { - printk(KERN_ERR pr_fmt("Failed to open file %s\n"), filepath); - ret = PTR_ERR(filp); - goto fail; - } - - ret = kernel_write(filp, "\n", 1, &filp->f_pos); - filp_close(filp, NULL); - if (ret < 0) { - printk(KERN_ERR pr_fmt("Cannot write file %s\n"), filepath); - goto fail; - } - - task = kthread_create(log_processor, NULL, "blksnaplog"); - if (IS_ERR(task)) { - ret = PTR_ERR(task); - goto fail; - } - - log_task = task; - log_filepath = filepath; - log_level = level <= LOGLEVEL_DEBUG ? level : LOGLEVEL_DEBUG; - log_tz_minuteswest = tz_minuteswest; - - log_printk_direct(filp, LOGLEVEL_INFO, - "Start log for module %s version %s loglevel %d\n", - get_module_name(), VERSION_STR, log_level); - - wake_up_process(log_task); - - return 0; -fail: - kfree(filepath); - memory_object_dec(memory_object_log_filepath); - return ret; -} - -static void log_vprintk(const int level, const char *fmt, va_list args) -{ - struct log_request *rq; - - rq = log_request_new(); - if (!rq) - return; - - log_request_fill(rq, level, fmt, args); - log_request_push(rq); - wake_up(&log_request_event_add); -} - -void log_printk(const int level, const char *fmt, ...) -{ - if (level <= log_level) { - va_list args; - - va_start(args, fmt); - log_vprintk(level, fmt, args); - va_end(args); - } -} - -#endif /* BLK_SNAP_FILELOG */ diff --git a/module/log.h b/module/log.h deleted file mode 100644 index 15593ff9..00000000 --- a/module/log.h +++ /dev/null @@ -1,94 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef __BLK_SNAP_LOG_H -#define __BLK_SNAP_LOG_H - -#ifdef BLK_SNAP_FILELOG - -void log_init(void); -void log_done(void); -int log_restart(int level, char *filepath, int tz_minuteswest); -void log_printk(const int level, const char *fmt, ...); - - -#undef pr_emerg -#define pr_emerg(fmt, ...) \ -({ \ - log_printk(LOGLEVEL_EMERG, fmt, ##__VA_ARGS__); \ - printk(KERN_EMERG pr_fmt(fmt), ##__VA_ARGS__) \ -}) - -#undef pr_alert -#define pr_alert(fmt, ...) \ -({ \ - log_printk(LOGLEVEL_ALERT, fmt, ##__VA_ARGS__); \ - printk(KERN_ALERT pr_fmt(fmt), ##__VA_ARGS__) \ -}) - -#undef pr_crit -#define pr_crit(fmt, ...) \ -({ \ - log_printk(LOGLEVEL_CRIT, fmt, ##__VA_ARGS__); \ - printk(KERN_CRIT pr_fmt(fmt), ##__VA_ARGS__); \ -}) - -#undef pr_err -#define pr_err(fmt, ...) \ -({ \ - log_printk(LOGLEVEL_ERR, fmt, ##__VA_ARGS__); \ - printk(KERN_ERR pr_fmt(fmt), ##__VA_ARGS__); \ -}) - -#undef pr_warn -#define pr_warn(fmt, ...) \ -({ \ - log_printk(LOGLEVEL_WARNING, fmt, ##__VA_ARGS__); \ - printk(KERN_WARNING pr_fmt(fmt), ##__VA_ARGS__); \ -}) - -#undef pr_notice -#define pr_notice(fmt, ...) \ -({ \ - log_printk(LOGLEVEL_NOTICE, fmt, ##__VA_ARGS__); \ - printk(KERN_NOTICE pr_fmt(fmt), ##__VA_ARGS__) \ -}) -#undef pr_info -#define pr_info(fmt, ...) \ -({ \ - log_printk(LOGLEVEL_INFO, fmt, ##__VA_ARGS__); \ - printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__); \ -}) - -#undef pr_debug -#if defined(BLK_SNAP_DEBUGLOG) || defined(DEBUG) -#define pr_debug(fmt, ...) \ -({ \ - log_printk(LOGLEVEL_DEBUG, fmt, ##__VA_ARGS__); \ - printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__); \ -}) -#else -#define pr_debug(fmt, ...) \ -({ \ - log_printk(LOGLEVEL_DEBUG, fmt, ##__VA_ARGS__); \ - no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__); \ -}) -#endif - -#elif defined(BLK_SNAP_DEBUGLOG) - -#undef pr_debug -#if defined(BLK_SNAP_DEBUGLOG) || defined(DEBUG) -#define pr_debug(fmt, ...) \ - printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__) -#else -#define pr_debug(fmt, ...) \ - no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__) -#endif - -#else -static inline void log_init(void) -{}; -static inline void log_done(void) -{}; -#endif /* BLK_SNAP_FILELOG */ - -#endif /* __BLK_SNAP_LOG_H */ diff --git a/module/main.c b/module/main.c deleted file mode 100644 index 7ec1b5fc..00000000 --- a/module/main.c +++ /dev/null @@ -1,735 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#ifdef STANDALONE_BDEVFILTER -#include "blksnap.h" -#else -#include -#endif -#include "memory_checker.h" -#include "snapimage.h" -#include "snapshot.h" -#include "tracker.h" -#include "diff_io.h" -#include "version.h" -#include "log.h" - -static_assert(sizeof(uuid_t) == sizeof(struct blk_snap_uuid), - "Invalid size of struct blk_snap_uuid."); - -#ifdef STANDALONE_BDEVFILTER -#pragma message("Standalone bdevfilter") -#endif -#ifdef HAVE_QC_SUBMIT_BIO_NOACCT -#pragma message("The blk_qc_t submit_bio_noacct(struct bio *) function was found.") -#endif -#ifdef HAVE_VOID_SUBMIT_BIO_NOACCT -#pragma message("The void submit_bio_noacct(struct bio *) function was found.") -#endif -#ifdef HAVE_SUPER_BLOCK_FREEZE -#pragma message("The freeze_bdev() and thaw_bdev() have struct super_block.") -#endif -#ifdef HAVE_BI_BDEV -#pragma message("The struct bio have pointer to struct block_device.") -#endif -#ifdef HAVE_BI_BDISK -#pragma message("The struct bio have pointer to struct gendisk.") -#endif -#ifdef HAVE_BDEV_NR_SECTORS -#pragma message("The bdev_nr_sectors() function was found.") -#endif -#ifdef HAVE_BLK_ALLOC_DISK -#pragma message("The blk_alloc_disk() function was found.") -#endif -#ifdef HAVE_BIO_MAX_PAGES -#pragma message("The BIO_MAX_PAGES define was found.") -#endif -#ifdef HAVE_ADD_DISK_RESULT -#pragma message("The function add_disk() has a return code.") -#endif -#ifdef HAVE_GENHD_H -#pragma message("The header file 'genhd.h' was found.") -#endif -#ifdef HAVE_BDEV_BIO_ALLOC -#pragma message("The function bio_alloc_bioset() has a parameter bdev.") -#endif -#ifdef HAVE_BLK_CLEANUP_DISK -#pragma message("The function blk_cleanup_disk() was found.") -#endif - -/* - * The power of 2 for minimum tracking block size. - * If we make the tracking block size small, we will get detailed information - * about the changes, but the size of the change tracker table will be too - * large, which will lead to inefficient memory usage. - */ -int tracking_block_minimum_shift = 16; - -/* - * The maximum number of tracking blocks. - * A table is created to store information about the status of all tracking - * blocks in RAM. So, if the size of the tracking block is small, then the size - * of the table turns out to be large and memory is consumed inefficiently. - * As the size of the block device grows, the size of the tracking block - * size should also grow. For this purpose, the limit of the maximum - * number of block size is set. - */ -int tracking_block_maximum_count = 2097152; - -/* - * The power of 2 for minimum chunk size. - * The size of the chunk depends on how much data will be copied to the - * difference storage when at least one sector of the block device is changed. - * If the size is small, then small I/O units will be generated, which will - * reduce performance. Too large a chunk size will lead to inefficient use of - * the difference storage. - */ -int chunk_minimum_shift = 18; - -/* - * The maximum number of chunks. - * To store information about the state of all the chunks, a table is created - * in RAM. So, if the size of the chunk is small, then the size of the table - * turns out to be large and memory is consumed inefficiently. - * As the size of the block device grows, the size of the chunk should also - * grow. For this purpose, the maximum number of chunks is set. - */ -int chunk_maximum_count = 2097152; - -/* - * The maximum number of chunks in memory cache. - * Since reading and writing to snapshots is performed in large chunks, - * a cache is implemented to optimize reading small portions of data - * from the snapshot image. As the number of chunks in the cache - * increases, memory consumption also increases. - * The minimum recommended value is four. - */ -int chunk_maximum_in_cache = 32; - -/* - * The size of the pool of preallocated difference buffers. - * A buffer can be allocated for each chunk. After use, this buffer is not - * released immediately, but is sent to the pool of free buffers. - * However, if there are too many free buffers in the pool, then these free - * buffers will be released immediately. - */ -int free_diff_buffer_pool_size = 128; - -/* - * The minimum allowable size of the difference storage in sectors. - * The difference storage is a part of the disk space allocated for storing - * snapshot data. If there is less free space in the storage than the minimum, - * an event is generated about the lack of free space. - */ -int diff_storage_minimum = 2097152; - -#ifdef STANDALONE_BDEVFILTER -static const struct blk_snap_version version = { - .major = VERSION_MAJOR, - .minor = VERSION_MINOR, - .revision = VERSION_REVISION, - .build = VERSION_BUILD, -}; -#else -#define VERSION_STR "1.0.0.0" -static const struct blk_snap_version version = { - .major = 1, - .minor = 0, - .revision = 0, - .build = 0, -}; -#endif - -static int ioctl_version(unsigned long arg) -{ - if (copy_to_user((void *)arg, &version, sizeof(version))) { - pr_err("Unable to get version: invalid user buffer\n"); - return -ENODATA; - } - - return 0; -} - -static int ioctl_tracker_remove(unsigned long arg) -{ - struct blk_snap_tracker_remove karg; - - if (copy_from_user(&karg, (void *)arg, sizeof(karg)) != 0) { - pr_err("Unable to remove device from tracking: invalid user buffer\n"); - return -ENODATA; - } - return tracker_remove(MKDEV(karg.dev_id.mj, karg.dev_id.mn)); -} - -static int ioctl_tracker_collect(unsigned long arg) -{ - int res; - struct blk_snap_tracker_collect karg; - struct blk_snap_cbt_info *cbt_info = NULL; - - pr_debug("Collecting tracking devices\n"); - - if (copy_from_user(&karg, (void *)arg, sizeof(karg))) { - pr_err("Unable to collect tracking devices: invalid user buffer\n"); - return -ENODATA; - } - - if (!karg.cbt_info_array) { - /* - * If the buffer is empty, this is a request to determine - * the number of trackers. - */ - res = tracker_collect(0, NULL, &karg.count); - if (res) { - pr_err("Failed to execute tracker_collect. errno=%d\n", - abs(res)); - return res; - } - if (copy_to_user((void *)arg, (void *)&karg, sizeof(karg))) { - pr_err("Unable to collect tracking devices: invalid user buffer for arguments\n"); - return -ENODATA; - } - return 0; - } - - cbt_info = kcalloc(karg.count, sizeof(struct blk_snap_cbt_info), - GFP_KERNEL); - if (!cbt_info) - return -ENOMEM; - memory_object_inc(memory_object_blk_snap_cbt_info); - - res = tracker_collect(karg.count, cbt_info, &karg.count); - if (res) { - pr_err("Failed to execute tracker_collect. errno=%d\n", - abs(res)); - goto fail; - } - - if (copy_to_user(karg.cbt_info_array, cbt_info, - karg.count * sizeof(struct blk_snap_cbt_info))) { - pr_err("Unable to collect tracking devices: invalid user buffer for CBT info\n"); - res = -ENODATA; - goto fail; - } - - if (copy_to_user((void *)arg, (void *)&karg, sizeof(karg))) { - pr_err("Unable to collect tracking devices: invalid user buffer for arguments\n"); - res = -ENODATA; - goto fail; - } -fail: - kfree(cbt_info); - memory_object_dec(memory_object_blk_snap_cbt_info); - - return res; -} - -static int ioctl_tracker_read_cbt_map(unsigned long arg) -{ - struct blk_snap_tracker_read_cbt_bitmap karg; - - if (copy_from_user(&karg, (void *)arg, sizeof(karg))) { - pr_err("Unable to read CBT map: invalid user buffer\n"); - return -ENODATA; - } - - return tracker_read_cbt_bitmap(MKDEV(karg.dev_id.mj, karg.dev_id.mn), - karg.offset, karg.length, - (char __user *)karg.buff); -} - -static inline int mark_dirty_blocks(dev_t dev_id, - struct blk_snap_block_range *dirty_blks, unsigned int count) -{ - int ret; - - ret = snapshot_mark_dirty_blocks(dev_id, dirty_blks, count); - if (ret == -ENODEV) - ret = tracker_mark_dirty_blocks(dev_id, dirty_blks, count); - - return ret; -} - -static int ioctl_tracker_mark_dirty_blocks(unsigned long arg) -{ - int ret = 0; - struct blk_snap_tracker_mark_dirty_blocks karg; - struct blk_snap_block_range *dirty_blks; - - if (copy_from_user(&karg, (void *)arg, sizeof(karg))) { - pr_err("Unable to mark dirty blocks: invalid user buffer\n"); - return -ENODATA; - } - - dirty_blks = kcalloc(karg.count, sizeof(struct blk_snap_block_range), - GFP_KERNEL); - if (!dirty_blks) - return -ENOMEM; - memory_object_inc(memory_object_blk_snap_block_range); - - if (!copy_from_user(dirty_blks, (void *)karg.dirty_blocks_array, - karg.count * sizeof(struct blk_snap_block_range))) - ret = mark_dirty_blocks(MKDEV(karg.dev_id.mj, karg.dev_id.mn), - dirty_blks, karg.count); - else { - pr_err("Unable to mark dirty blocks: invalid user buffer\n"); - ret = -ENODATA; - } - - kfree(dirty_blks); - memory_object_dec(memory_object_blk_snap_block_range); - - return ret; -} - -static int ioctl_snapshot_create(unsigned long arg) -{ - int ret; - struct blk_snap_snapshot_create karg; - struct blk_snap_dev *dev_id_array = NULL; - uuid_t new_id; - - if (copy_from_user(&karg, (void *)arg, sizeof(karg))) { - pr_err("Unable to create snapshot: invalid user buffer\n"); - return -ENODATA; - } - - dev_id_array = - kcalloc(karg.count, sizeof(struct blk_snap_dev), GFP_KERNEL); - if (dev_id_array == NULL) { - pr_err("Unable to create snapshot: too many devices %d\n", - karg.count); - return -ENOMEM; - } - memory_object_inc(memory_object_blk_snap_dev); - - if (copy_from_user(dev_id_array, (void *)karg.dev_id_array, - karg.count * sizeof(struct blk_snap_dev))) { - pr_err("Unable to create snapshot: invalid user buffer\n"); - ret = -ENODATA; - goto out; - } - - ret = snapshot_create(dev_id_array, karg.count, &new_id); - if (ret) - goto out; - - export_uuid(karg.id.b, &new_id); - if (copy_to_user((void *)arg, &karg, sizeof(karg))) { - pr_err("Unable to create snapshot: invalid user buffer\n"); - ret = -ENODATA; - } -out: - kfree(dev_id_array); - memory_object_dec(memory_object_blk_snap_dev); - - return ret; -} - -static int ioctl_snapshot_destroy(unsigned long arg) -{ - struct blk_snap_snapshot_destroy karg; - uuid_t id; - - if (copy_from_user(&karg, (void *)arg, sizeof(karg))) { - pr_err("Unable to destroy snapshot: invalid user buffer\n"); - return -ENODATA; - } - - import_uuid(&id, karg.id.b); - return snapshot_destroy(&id); -} - -static int ioctl_snapshot_append_storage(unsigned long arg) -{ - struct blk_snap_snapshot_append_storage karg; - uuid_t id; - - pr_debug("Append difference storage\n"); - - if (copy_from_user(&karg, (void *)arg, sizeof(karg))) { - pr_err("Unable to append difference storage: invalid user buffer\n"); - return -EINVAL; - } - - import_uuid(&id, karg.id.b); - return snapshot_append_storage(&id, karg.dev_id, karg.ranges, - karg.count); -} - -static int ioctl_snapshot_take(unsigned long arg) -{ - struct blk_snap_snapshot_take karg; - uuid_t id; - - if (copy_from_user(&karg, (void *)arg, sizeof(karg))) { - pr_err("Unable to take snapshot: invalid user buffer\n"); - return -ENODATA; - } - - import_uuid(&id, karg.id.b); - return snapshot_take(&id); -} - -static int ioctl_snapshot_wait_event(unsigned long arg) -{ - int ret = 0; - struct blk_snap_snapshot_event *karg; - uuid_t id; - struct event *event; - - karg = kzalloc(sizeof(struct blk_snap_snapshot_event), GFP_KERNEL); - if (!karg) - return -ENOMEM; - memory_object_inc(memory_object_blk_snap_snapshot_event); - - /* Copy only snapshot ID */ - if (copy_from_user(&karg->id, - &((struct blk_snap_snapshot_event *)arg)->id, - sizeof(struct blk_snap_uuid))) { - pr_err("Unable to get snapshot event. Invalid user buffer\n"); - ret = -EINVAL; - goto out; - } - - import_uuid(&id, karg->id.b); - event = snapshot_wait_event(&id, karg->timeout_ms); - if (IS_ERR(event)) { - ret = PTR_ERR(event); - goto out; - } - - pr_debug("Received event=%lld code=%d data_size=%d\n", event->time, - event->code, event->data_size); - karg->code = event->code; - karg->time_label = event->time; - - if (event->data_size > sizeof(karg->data)) { - pr_err("Event size %d is too big\n", event->data_size); - ret = -ENOSPC; - /* If we can't copy all the data, we copy only part of it. */ - } - memcpy(karg->data, event->data, event->data_size); - event_free(event); - - if (copy_to_user((void *)arg, karg, - sizeof(struct blk_snap_snapshot_event))) { - pr_err("Unable to get snapshot event. Invalid user buffer\n"); - ret = -EINVAL; - } -out: - kfree(karg); - memory_object_dec(memory_object_blk_snap_snapshot_event); - - return ret; -} - -static int ioctl_snapshot_collect(unsigned long arg) -{ - int ret; - struct blk_snap_snapshot_collect karg; - - if (copy_from_user(&karg, (void *)arg, sizeof(karg))) { - pr_err("Unable to collect available snapshots: invalid user buffer\n"); - return -ENODATA; - } - - ret = snapshot_collect(&karg.count, karg.ids); - - if (copy_to_user((void *)arg, &karg, sizeof(karg))) { - pr_err("Unable to collect available snapshots: invalid user buffer\n"); - return -ENODATA; - } - - return ret; -} - -static int ioctl_snapshot_collect_images(unsigned long arg) -{ - int ret; - struct blk_snap_snapshot_collect_images karg; - uuid_t id; - - if (copy_from_user(&karg, (void *)arg, sizeof(karg))) { - pr_err("Unable to collect snapshot images: invalid user buffer\n"); - return -ENODATA; - } - - import_uuid(&id, karg.id.b); - ret = snapshot_collect_images(&id, karg.image_info_array, - &karg.count); - - if (copy_to_user((void *)arg, &karg, sizeof(karg))) { - pr_err("Unable to collect snapshot images: invalid user buffer\n"); - return -ENODATA; - } - - return ret; -} - -static int (*const blk_snap_ioctl_table[])(unsigned long arg) = { - ioctl_version, - ioctl_tracker_remove, - ioctl_tracker_collect, - ioctl_tracker_read_cbt_map, - ioctl_tracker_mark_dirty_blocks, - ioctl_snapshot_create, - ioctl_snapshot_destroy, - ioctl_snapshot_append_storage, - ioctl_snapshot_take, - ioctl_snapshot_collect, - ioctl_snapshot_collect_images, - ioctl_snapshot_wait_event, -}; - -static_assert( - sizeof(blk_snap_ioctl_table) == - ((blk_snap_ioctl_snapshot_wait_event + 1) * sizeof(void *)), - "The size of table blk_snap_ioctl_table does not match the enum blk_snap_ioctl."); - -#ifdef BLK_SNAP_MODIFICATION - -static const struct blk_snap_mod modification = { - .name = MOD_NAME, - .compatibility_flags = -#ifdef BLK_SNAP_DEBUG_SECTOR_STATE - (1ull << blk_snap_compat_flag_debug_sector_state) | -#endif -#ifdef BLK_SNAP_FILELOG - (1ull << blk_snap_compat_flag_setlog) | -#endif - 0 -}; - -int ioctl_mod(unsigned long arg) -{ - if (copy_to_user((void *)arg, &modification, sizeof(modification))) { - pr_err("Unable to get modification: invalid user buffer\n"); - return -ENODATA; - } - - return 0; -} - -int ioctl_setlog(unsigned long arg) -{ -#ifdef BLK_SNAP_FILELOG - struct blk_snap_setlog karg; - char *filepath = NULL; - - if (copy_from_user(&karg, (void *)arg, sizeof(karg))) { - pr_err("Unable to get log parameters: invalid user buffer\n"); - return -ENODATA; - } - - /* - * logging can be disabled - * To do this, it is enough not to specify a logging file or set - * a negative logging level. - */ - if ((karg.level < 0) || !karg.filepath) - return log_restart(-1, NULL, 0); - - if (karg.filepath_size == 0) { - pr_err("Invalid parameters. 'filepath_size' cannot be zero\n"); - return -EINVAL; - } - filepath = kzalloc(karg.filepath_size + 1, GFP_KERNEL); - if (!filepath) - return -ENOMEM; - memory_object_inc(memory_object_log_filepath); - - if (copy_from_user(filepath, (void *)karg.filepath, karg.filepath_size)) { - pr_err("Unable to get log filepath: invalid user buffer\n"); - - kfree(filepath); - memory_object_dec(memory_object_log_filepath); - return -ENODATA; - } - - return log_restart(karg.level, filepath, karg.tz_minuteswest); -#else - return -ENOTTY; -#endif -} - -static int ioctl_get_sector_state(unsigned long arg) -{ -#ifdef BLK_SNAP_DEBUG_SECTOR_STATE - int ret; - struct blk_snap_get_sector_state karg; - dev_t dev_id; - - if (copy_from_user(&karg, (void *)arg, sizeof(karg))) { - pr_err("Unable to get sector state: invalid user buffer\n"); - return -ENODATA; - } - - dev_id = MKDEV(karg.image_dev_id.mj, karg.image_dev_id.mn); - ret = snapshot_get_chunk_state(dev_id, karg.sector, &karg.state); - if (unlikely(ret)) { - pr_err("Failed to get sector state: cannot get chunk state\n"); - return ret; - } - - if (copy_to_user((void *)arg, &karg, sizeof(karg))) { - pr_err("Unable to get sector state: invalid user buffer\n"); - return -ENODATA; - } - - return ret; -#else - return -ENOTTY; -#endif -} - -static int (*const blk_snap_ioctl_table_mod[])(unsigned long arg) = { - ioctl_mod, - ioctl_setlog, - ioctl_get_sector_state, -}; -static_assert( - sizeof(blk_snap_ioctl_table_mod) == - ((blk_snap_ioctl_end_mod - IOCTL_MOD) * sizeof(void *)), - "The size of table blk_snap_ioctl_table_mod does not match the enum blk_snap_ioctl."); -#endif /*BLK_SNAP_MODIFICATION*/ - -static long ctrl_unlocked_ioctl(struct file *filp, unsigned int cmd, - unsigned long arg) -{ - int nr = _IOC_NR(cmd); - - if (nr > (sizeof(blk_snap_ioctl_table) / sizeof(void *))) { -#ifdef BLK_SNAP_MODIFICATION - if ((nr >= IOCTL_MOD) && - (nr < (IOCTL_MOD + (sizeof(blk_snap_ioctl_table_mod) / - sizeof(void *))))) { - nr -= IOCTL_MOD; - if (blk_snap_ioctl_table_mod[nr]) - return blk_snap_ioctl_table_mod[nr](arg); - } -#endif - return -ENOTTY; - } - - if (!blk_snap_ioctl_table[nr]) - return -ENOTTY; - - return blk_snap_ioctl_table[nr](arg); -} - -static const struct file_operations blksnap_ctrl_fops = { - .owner = THIS_MODULE, - .unlocked_ioctl = ctrl_unlocked_ioctl, -}; - -static struct miscdevice blksnap_ctrl_misc = { - .minor = MISC_DYNAMIC_MINOR, - .name = BLK_SNAP_CTL, - .fops = &blksnap_ctrl_fops, -}; - -static int __init blk_snap_init(void) -{ - int ret; - - log_init(); -#ifdef STANDALONE_BDEVFILTER - pr_info("Loading\n"); -#else - pr_debug("Loading\n"); -#endif - pr_debug("Version: %s\n", VERSION_STR); - pr_debug("tracking_block_minimum_shift: %d\n", - tracking_block_minimum_shift); - pr_debug("tracking_block_maximum_count: %d\n", - tracking_block_maximum_count); - pr_debug("chunk_minimum_shift: %d\n", chunk_minimum_shift); - pr_debug("chunk_maximum_count: %d\n", chunk_maximum_count); - pr_debug("chunk_maximum_in_cache: %d\n", chunk_maximum_in_cache); - pr_debug("free_diff_buffer_pool_size: %d\n", - free_diff_buffer_pool_size); - pr_debug("diff_storage_minimum: %d\n", diff_storage_minimum); - - ret = diff_io_init(); - if (ret) - goto fail_diff_io_init; - - ret = tracker_init(); - if (ret) - goto fail_tracker_init; - - ret = misc_register(&blksnap_ctrl_misc); - if (ret) - goto fail_misc_register; - - return 0; - -fail_misc_register: - tracker_done(); -fail_tracker_init: - diff_io_done(); -fail_diff_io_init: - log_done(); - - return ret; -} - -static void __exit blk_snap_exit(void) -{ -#ifdef STANDALONE_BDEVFILTER - pr_info("Unloading module\n"); -#else - pr_debug("Unloading module\n"); -#endif - misc_deregister(&blksnap_ctrl_misc); - - diff_io_done(); - snapshot_done(); - tracker_done(); - log_done(); - memory_object_print(true); - pr_debug("Module was unloaded\n"); -} - -module_init(blk_snap_init); -module_exit(blk_snap_exit); - -module_param_named(tracking_block_minimum_shift, tracking_block_minimum_shift, - int, 0644); -MODULE_PARM_DESC(tracking_block_minimum_shift, - "The power of 2 for minimum tracking block size"); -module_param_named(tracking_block_maximum_count, tracking_block_maximum_count, - int, 0644); -MODULE_PARM_DESC(tracking_block_maximum_count, - "The maximum number of tracking blocks"); -module_param_named(chunk_minimum_shift, chunk_minimum_shift, int, 0644); -MODULE_PARM_DESC(chunk_minimum_shift, - "The power of 2 for minimum chunk size"); -module_param_named(chunk_maximum_count, chunk_maximum_count, int, 0644); -MODULE_PARM_DESC(chunk_maximum_count, - "The maximum number of chunks"); -module_param_named(chunk_maximum_in_cache, chunk_maximum_in_cache, int, 0644); -MODULE_PARM_DESC(chunk_maximum_in_cache, - "The maximum number of chunks in memory cache"); -module_param_named(free_diff_buffer_pool_size, free_diff_buffer_pool_size, int, - 0644); -MODULE_PARM_DESC(free_diff_buffer_pool_size, - "The size of the pool of preallocated difference buffers"); -module_param_named(diff_storage_minimum, diff_storage_minimum, int, 0644); -MODULE_PARM_DESC(diff_storage_minimum, - "The minimum allowable size of the difference storage in sectors"); - -MODULE_DESCRIPTION("Block Device Snapshots Module"); -MODULE_VERSION(VERSION_STR); -MODULE_AUTHOR("Veeam Software Group GmbH"); -MODULE_LICENSE("GPL"); - -#ifdef STANDALONE_BDEVFILTER -/* - * Allow to be loaded on OpenSUSE/SLES - */ -MODULE_INFO(supported, "external"); -#endif diff --git a/module/memory_checker.c b/module/memory_checker.c deleted file mode 100644 index 7b0b00d3..00000000 --- a/module/memory_checker.c +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#ifdef BLK_SNAP_DEBUG_MEMORY_LEAK -#define pr_fmt(fmt) KBUILD_MODNAME "-memory_checker: " fmt -#include -#include -#include "memory_checker.h" -#ifdef STANDALONE_BDEVFILTER -#include "log.h" -#endif - -char *memory_object_names[] = { - /*alloc_page*/ - "page", - /*kzalloc*/ - "cbt_map", - "cbt_buffer", - "chunk", - "blk_snap_snaphot_event", - "diff_area", - "diff_io", - "diff_storage", - "storage_bdev", - "storage_block", - "diff_region", - "diff_buffer", - "event", - "snapimage", - "snapshot", - "tracker", - "tracked_device", - /*kcalloc*/ - "blk_snap_cbt_info", - "blk_snap_block_range", - "blk_snap_dev", - "tracker_array", - "snapimage_array", - "superblock_array", - "blk_snap_image_info", - "log_filepath", - /*end*/ -}; - -static_assert( - sizeof(memory_object_names) == (memory_object_count * sizeof(char *)), - "The size of enum memory_object_type is not equal to size of memory_object_names array."); - -static atomic_t memory_counter[memory_object_count]; -static atomic_t memory_counter_max[memory_object_count]; - -void memory_object_inc(enum memory_object_type type) -{ - int value; - - if (unlikely(type >= memory_object_count)) - return; - - value = atomic_inc_return(&memory_counter[type]); - if (value > atomic_read(&memory_counter_max[type])) - atomic_inc(&memory_counter_max[type]); -} - -void memory_object_dec(enum memory_object_type type) -{ - if (unlikely(type >= memory_object_count)) - return; - - atomic_dec(&memory_counter[type]); -} - -void memory_object_print(bool is_error) -{ - int inx; - int not_free = 0; - - pr_debug("Objects in memory:\n"); - for (inx = 0; inx < memory_object_count; inx++) { - int count = atomic_read(&memory_counter[inx]); - - if (count) { - not_free += count; - if (is_error) { - pr_err("%s: %d\n", memory_object_names[inx], - count); - } else { - pr_debug("%s: %d\n", memory_object_names[inx], - count); - } - } - } - if (not_free) - if (is_error) - pr_err("%d not released objects found\n", not_free); - else - pr_debug("Found %d allocated objects\n", not_free); - else - pr_debug("All objects have been released\n"); -} - -void memory_object_max_print(void) -{ - int inx; - - pr_debug("Maximim objects in memory:\n"); - for (inx = 0; inx < memory_object_count; inx++) { - int count = atomic_read(&memory_counter_max[inx]); - - if (count) - pr_debug("%s: %d\n", memory_object_names[inx], count); - } - pr_debug(".\n"); -} -#endif diff --git a/module/memory_checker.h b/module/memory_checker.h deleted file mode 100644 index ac259c04..00000000 --- a/module/memory_checker.h +++ /dev/null @@ -1,57 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef __BLK_SNAP_MEMORY_CHECKER_H -#define __BLK_SNAP_MEMORY_CHECKER_H -#include - -enum memory_object_type { - /*alloc_page*/ - memory_object_page, - /*kzalloc*/ - memory_object_cbt_map, - memory_object_cbt_buffer, - memory_object_chunk, - memory_object_blk_snap_snapshot_event, - memory_object_diff_area, - memory_object_diff_io, - memory_object_diff_storage, - memory_object_storage_bdev, - memory_object_storage_block, - memory_object_diff_region, - memory_object_diff_buffer, - memory_object_event, - memory_object_snapimage, - memory_object_snapshot, - memory_object_tracker, - memory_object_tracked_device, - /*kcalloc*/ - memory_object_blk_snap_cbt_info, - memory_object_blk_snap_block_range, - memory_object_blk_snap_dev, - memory_object_tracker_array, - memory_object_snapimage_array, - memory_object_superblock_array, - memory_object_blk_snap_image_info, - memory_object_log_filepath, - /*end*/ - memory_object_count -}; - -#ifdef BLK_SNAP_DEBUG_MEMORY_LEAK -void memory_object_inc(enum memory_object_type type); -void memory_object_dec(enum memory_object_type type); -void memory_object_print(bool is_error); -void memory_object_max_print(void); -#else -static inline void memory_object_inc( - __attribute__ ((unused)) enum memory_object_type type) -{}; -static inline void memory_object_dec( - __attribute__ ((unused)) enum memory_object_type type) -{}; -static inline void memory_object_print( - __attribute__ ((unused)) bool is_error) -{}; -static inline void memory_object_max_print(void) -{}; -#endif -#endif /* __BLK_SNAP_MEMORY_CHECKER_H */ diff --git a/module/mk.sh b/module/mk.sh deleted file mode 100755 index 17e334dd..00000000 --- a/module/mk.sh +++ /dev/null @@ -1,79 +0,0 @@ -#!/bin/bash -e -# SPDX-License-Identifier: GPL-2.0 -MODULE_NAME=blksnap -FILTER_NAME=bdevfilter -CMD=$1 -if [ -n "$2" ] -then - if [ "-" = "$2" ] - then - echo "kernel version is not set" - else - KERNEL_RELEASE="$2" - fi -else - KERNEL_RELEASE="$(uname -r)" -fi -MODULE_PATH=/lib/modules/${KERNEL_RELEASE}/kernel/drivers/block - -case "$CMD" in - build) - echo Making ... - make -j`nproc` -C /lib/modules/${KERNEL_RELEASE}/build M=$(pwd) modules - echo Completed. - ;; - clean) - echo Cleaning ... - make -C /lib/modules/${KERNEL_RELEASE}/build M=$(pwd) clean - echo Completed. - ;; - install) - echo "Installing ${MODULE_NAME}" - mkdir -p ${MODULE_PATH} - cp ${MODULE_NAME}.ko ${MODULE_PATH} - depmod - echo Completed. - ;; - uninstall) - echo "Uninstalling ${MODULE_NAME}" - rm -f ${MODULE_PATH}/${MODULE_NAME}.ko - ;; - load) - echo "Loading ${MODULE_NAME} kernel module from current folder" - insmod ./${MODULE_NAME}.ko - ;; - unload) - echo "Unloading ${MODULE_NAME} kernel module" - rmmod ${MODULE_NAME} - ;; - install-flt) - echo "Installing ${FILTER_NAME}" - mkdir -p ${MODULE_PATH} - cp ${FILTER_NAME}.ko ${MODULE_PATH} - depmod - echo Completed. - ;; - uninstall-flt) - echo "Uninstalling ${FILTER_NAME}" - rm -f ${MODULE_PATH}/${FILTER_NAME}.ko - ;; - load-flt) - echo "Loading ${FILTER_NAME} kernel module from current folder" - insmod ./${FILTER_NAME}.ko - ;; - unload-flt) - echo "Unloading ${FILTER_NAME} kernel module" - echo 0 > /sys/kernel/livepatch/bdevfilter/enabled || true - sleep 2s - rmmod ${FILTER_NAME} - ;; - *) - echo "Usage " - echo "Compile project: " - echo " $0 {build | clean} []" - echo "for ${MODULE_NAME} module : " - echo " $0 {install | uninstall | load | unload} []" - echo "for ${FILTER_NAME} module : " - echo " $0 {install-flt | uninstall-flt | load-flt | unload-flt} []" - exit 1 -esac diff --git a/module/snapimage.c b/module/snapimage.c deleted file mode 100644 index 29835455..00000000 --- a/module/snapimage.c +++ /dev/null @@ -1,315 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#define pr_fmt(fmt) KBUILD_MODNAME "-snapimage: " fmt - -#include -#include -#include -#ifdef STANDALONE_BDEVFILTER -#include "blksnap.h" -#else -#include -#endif -#include "memory_checker.h" -#include "snapimage.h" -#include "diff_area.h" -#include "chunk.h" -#include "cbt_map.h" -#include "log.h" - -static void snapimage_process_bio(struct snapimage *snapimage, struct bio *bio) -{ - - struct diff_area_image_ctx io_ctx; - struct bio_vec bvec; - struct bvec_iter iter; - sector_t pos = bio->bi_iter.bi_sector; - - diff_area_throttling_io(snapimage->diff_area); - diff_area_image_ctx_init(&io_ctx, snapimage->diff_area, - op_is_write(bio_op(bio))); - bio_for_each_segment(bvec, bio, iter) { - blk_status_t st; - - st = diff_area_image_io(&io_ctx, &bvec, &pos); - if (unlikely(st != BLK_STS_OK)) - break; - } - diff_area_image_ctx_done(&io_ctx); - bio_endio(bio); -} - -static inline struct bio *get_bio_from_queue(struct snapimage *snapimage) -{ - struct bio *bio; - - spin_lock(&snapimage->queue_lock); - bio = bio_list_pop(&snapimage->queue); - spin_unlock(&snapimage->queue_lock); - - return bio; -} - -static int snapimage_kthread_worker_fn(void *param) -{ - struct snapimage *snapimage = param; - struct bio *bio; - - for (;;) { - while ((bio = get_bio_from_queue(snapimage))) - snapimage_process_bio(snapimage, bio); - if (kthread_should_stop()) - break; - schedule(); - } - - return 0; -} - -#ifdef HAVE_QC_SUBMIT_BIO -static blk_qc_t snapimage_submit_bio(struct bio *bio) -{ - blk_qc_t ret = BLK_QC_T_NONE; -#else -static void snapimage_submit_bio(struct bio *bio) -{ -#endif -#ifdef HAVE_BI_BDEV - struct snapimage *snapimage = bio->bi_bdev->bd_disk->private_data; -#endif -#ifdef HAVE_BI_BDISK - struct snapimage *snapimage = bio->bi_disk->private_data; -#endif - - if (!diff_area_is_corrupted(snapimage->diff_area)) { - spin_lock(&snapimage->queue_lock); - bio_list_add(&snapimage->queue, bio); - spin_unlock(&snapimage->queue_lock); - - wake_up_process(snapimage->worker); - } else - bio_io_error(bio); - -#ifdef HAVE_QC_SUBMIT_BIO - return ret; -} -#else -} -#endif - -#ifndef HAVE_GENHD_H -static void snapimage_free_disk(struct gendisk *disk) -{ - struct snapimage *snapimage = disk->private_data; - - might_sleep(); - - diff_area_put(snapimage->diff_area); - cbt_map_put(snapimage->cbt_map); - - kfree(snapimage); - memory_object_dec(memory_object_snapimage); -} -#endif - -const struct block_device_operations bd_ops = { - .owner = THIS_MODULE, - .submit_bio = snapimage_submit_bio, -#ifndef HAVE_GENHD_H - .free_disk = snapimage_free_disk, -#endif -}; - -void snapimage_free(struct snapimage *snapimage) -{ - pr_debug("Snapshot image disk %s delete\n", snapimage->disk->disk_name); - - del_gendisk(snapimage->disk); - - kthread_stop(snapimage->worker); -#ifdef HAVE_BLK_ALLOC_DISK -#ifdef HAVE_BLK_CLEANUP_DISK - blk_cleanup_disk(snapimage->disk); -#else - put_disk(snapimage->disk); -#endif -#else - blk_cleanup_queue(snapimage->disk->queue); - put_disk(snapimage->disk); -#endif - -#ifdef HAVE_GENHD_H - diff_area_put(snapimage->diff_area); - cbt_map_put(snapimage->cbt_map); - - kfree(snapimage); - memory_object_dec(memory_object_snapimage); -#endif -} - -#ifndef HAVE_BLK_ALLOC_DISK -static inline struct gendisk *blk_alloc_disk(int node) -{ - struct request_queue *q; - struct gendisk *disk; - - q = blk_alloc_queue(node); - if (!q) - return NULL; - - disk = __alloc_disk_node(0, node); - if (!disk) { - blk_cleanup_queue(q); - return NULL; - } - disk->queue = q; - - return disk; -} -#endif - -struct snapimage *snapimage_create(struct diff_area *diff_area, - struct cbt_map *cbt_map) -{ - int ret = 0; - dev_t dev_id = diff_area->orig_bdev->bd_dev; - struct snapimage *snapimage = NULL; - struct gendisk *disk; - struct task_struct *task; - - snapimage = kzalloc(sizeof(struct snapimage), GFP_KERNEL); - if (snapimage == NULL) - return ERR_PTR(-ENOMEM); - memory_object_inc(memory_object_snapimage); - - snapimage->capacity = cbt_map->device_capacity; - pr_info("Create snapshot image device for original device [%u:%u]\n", - MAJOR(dev_id), MINOR(dev_id)); - - spin_lock_init(&snapimage->queue_lock); - bio_list_init(&snapimage->queue); - - task = kthread_create(snapimage_kthread_worker_fn, snapimage, - "blksnap_%d_%d", - MAJOR(dev_id), MINOR(dev_id)); - if (IS_ERR(task)) { - ret = PTR_ERR(task); - pr_err("Failed to start worker thread. errno=%d\n", abs(ret)); - goto fail_create_task; - } - - snapimage->worker = task; - set_user_nice(task, MAX_NICE); - task->flags |= PF_LOCAL_THROTTLE | PF_MEMALLOC_NOIO; - - disk = blk_alloc_disk(NUMA_NO_NODE); - if (!disk) { - pr_err("Failed to allocate disk\n"); - ret = -ENOMEM; - goto fail_disk_alloc; - } - snapimage->disk = disk; - - blk_queue_max_hw_sectors(disk->queue, BLK_DEF_MAX_SECTORS); - blk_queue_flag_set(QUEUE_FLAG_NOMERGES, disk->queue); - - disk->flags = 0; -#ifdef STANDALONE_BDEVFILTER -#ifdef GENHD_FL_NO_PART_SCAN - disk->flags |= GENHD_FL_NO_PART_SCAN; -#else - disk->flags |= GENHD_FL_NO_PART; -#endif -#else - disk->flags |= GENHD_FL_NO_PART; -#endif - - disk->fops = &bd_ops; - disk->private_data = snapimage; - - set_capacity(disk, snapimage->capacity); - pr_debug("Snapshot image device capacity %lld bytes\n", - (u64)(snapimage->capacity << SECTOR_SHIFT)); - - diff_area_get(diff_area); - snapimage->diff_area = diff_area; - cbt_map_get(cbt_map); - snapimage->cbt_map = cbt_map; - - ret = snprintf(disk->disk_name, DISK_NAME_LEN, "%s_%d:%d", - BLK_SNAP_IMAGE_NAME, MAJOR(dev_id), MINOR(dev_id)); - if (ret < 0) { - pr_err("Unable to set disk name for snapshot image device: invalid device id [%d:%d]\n", - MAJOR(dev_id), MINOR(dev_id)); - ret = -EINVAL; - goto fail_cleanup_disk; - } - pr_debug("Snapshot image disk name [%s]\n", disk->disk_name); -#ifdef HAVE_ADD_DISK_RESULT - ret = add_disk(disk); - if (ret) { - pr_err("Failed to add disk [%s] for snapshot image device\n", - disk->disk_name); - goto fail_cleanup_disk; - } -#else - add_disk(disk); -#endif - - pr_debug("Image block device [%d:%d] has been created.\n", - disk->major, disk->first_minor); - - return snapimage; - -fail_cleanup_disk: - kthread_stop(snapimage->worker); -#ifdef HAVE_BLK_ALLOC_DISK -#ifdef HAVE_BLK_CLEANUP_DISK - blk_cleanup_disk(disk); -#else - put_disk(disk); -#endif -#else - del_gendisk(disk); -#endif - return ERR_PTR(ret); - -fail_disk_alloc: - kthread_stop(snapimage->worker); -fail_create_task: - kfree(snapimage); - memory_object_dec(memory_object_snapimage); - return ERR_PTR(ret); -} - -#ifdef BLK_SNAP_DEBUG_SECTOR_STATE -int snapimage_get_chunk_state(struct snapimage *snapimage, sector_t sector, - struct blk_snap_sector_state *state) -{ - int ret; - - ret = diff_area_get_sector_state(snapimage->diff_area, sector, - &state->chunk_state); - if (ret) - return ret; - - ret = cbt_map_get_sector_state(snapimage->cbt_map, sector, - &state->snap_number_prev, - &state->snap_number_curr); - if (ret) - return ret; - { - char buf[SECTOR_SIZE]; - - ret = diff_area_get_sector_image(snapimage->diff_area, sector, - buf /*&state->buf*/); - if (ret) - return ret; - - pr_info("sector #%llu", sector); - print_hex_dump(KERN_INFO, "data header: ", DUMP_PREFIX_OFFSET, - 32, 1, buf, 96, true); - } - - return 0; -} -#endif diff --git a/module/snapimage.h b/module/snapimage.h deleted file mode 100644 index 01066561..00000000 --- a/module/snapimage.h +++ /dev/null @@ -1,64 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef __BLK_SNAP_SNAPIMAGE_H -#define __BLK_SNAP_SNAPIMAGE_H - -#ifdef HAVE_GENHD_H -#include -#endif -#include -#include - -struct diff_area; -struct cbt_map; - -/** - * struct snapimage - Snapshot image block device. - * - * @capacity: - * The size of the snapshot image in sectors must be equal to the size - * of the original device at the time of taking the snapshot. - * @worker: - * A pointer to the &struct task of the worker thread that process I/O - * units. - * @queue_lock: - * Lock for &queue. - * @queue: - * A queue of I/O units waiting to be processed. - * @disk: - * A pointer to the &struct gendisk for the image block device. - * @diff_area: - * A pointer to the owned &struct diff_area. - * @cbt_map: - * A pointer to the owned &struct cbt_map. - * - * The snapshot image is presented in the system as a block device. But - * when reading or writing a snapshot image, the data is redirected to - * the original block device or to the block device of the difference storage. - * - * The module does not prohibit reading and writing data to the snapshot - * from different threads in parallel. To avoid the problem with simultaneous - * access, it is enough to open the snapshot image block device with the - * FMODE_EXCL parameter. - */ -struct snapimage { - sector_t capacity; - - struct task_struct *worker; - spinlock_t queue_lock; - struct bio_list queue; - - struct gendisk *disk; - - struct diff_area *diff_area; - struct cbt_map *cbt_map; -}; - -void snapimage_free(struct snapimage *snapimage); -struct snapimage *snapimage_create(struct diff_area *diff_area, - struct cbt_map *cbt_map); - -#ifdef BLK_SNAP_DEBUG_SECTOR_STATE -int snapimage_get_chunk_state(struct snapimage *snapimage, sector_t sector, - struct blk_snap_sector_state *state); -#endif -#endif /* __BLK_SNAP_SNAPIMAGE_H */ diff --git a/module/snapshot.c b/module/snapshot.c deleted file mode 100644 index c8bcbabf..00000000 --- a/module/snapshot.c +++ /dev/null @@ -1,953 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#define pr_fmt(fmt) KBUILD_MODNAME "-snapshot: " fmt - -#include -#include -#ifdef STANDALONE_BDEVFILTER -#include "blksnap.h" -#include "bdevfilter.h" -#else -#include -#endif -#include "memory_checker.h" -#include "snapshot.h" -#include "tracker.h" -#include "diff_storage.h" -#include "diff_area.h" -#include "snapimage.h" -#include "cbt_map.h" -#include "log.h" - -LIST_HEAD(snapshots); -DECLARE_RWSEM(snapshots_lock); - -#if defined(BLK_SNAP_SEQUENTALFREEZE) -/* - * snapshot_release_trackers - Releases snapshots trackers - * - * The sequential algorithm allows to freeze block devices one at a time. - */ -static void snapshot_release_trackers(struct snapshot *snapshot) -{ - int inx; - - pr_info("Sequentially release snapshots trackers\n"); - - for (inx = 0; inx < snapshot->count; ++inx) { - struct tracker *tracker = snapshot->tracker_array[inx]; -#if defined(HAVE_SUPER_BLOCK_FREEZE) - struct super_block *sb = NULL; -#else - bool is_frozen = false; -#endif - if (!tracker || !tracker->diff_area) - continue; - - /* Flush and freeze fs */ -#if defined(HAVE_SUPER_BLOCK_FREEZE) - _freeze_bdev(tracker->diff_area->orig_bdev, &sb); -#else - if (freeze_bdev(tracker->diff_area->orig_bdev)) - pr_err("Failed to freeze device [%u:%u]\n", - MAJOR(tracker->dev_id), MINOR(tracker->dev_id)); - else { - is_frozen = true; - pr_debug("Device [%u:%u] was frozen\n", - MAJOR(tracker->dev_id), MINOR(tracker->dev_id)); - } -#endif - - /* Set tracker as available for new snapshots. */ - tracker_release_snapshot(tracker); - - /* Thaw fs */ -#if defined(HAVE_SUPER_BLOCK_FREEZE) - _thaw_bdev(tracker->diff_area->orig_bdev, sb); -#else - if (!is_frozen) - continue; - if (thaw_bdev(tracker->diff_area->orig_bdev)) - pr_err("Failed to thaw device [%u:%u]\n", - MAJOR(tracker->dev_id), MINOR(tracker->dev_id)); - else - pr_debug("Device [%u:%u] was unfrozen\n", - MAJOR(tracker->dev_id), MINOR(tracker->dev_id)); -#endif - } -} - -#else /* BLK_SNAP_SEQUENTALFREEZE */ - -/* - * snapshot_release_trackers - Releases snapshots trackers - * - * The simultaneous algorithm allows to freeze all the snapshot block devices. - */ -static void snapshot_release_trackers(struct snapshot *snapshot) -{ - int inx; - - /* Flush and freeze fs on each original block device. */ - for (inx = 0; inx < snapshot->count; ++inx) { - struct tracker *tracker = snapshot->tracker_array[inx]; - - if (!tracker || !tracker->diff_area) - continue; - -#if defined(HAVE_SUPER_BLOCK_FREEZE) - _freeze_bdev(tracker->diff_area->orig_bdev, - &snapshot->superblock_array[inx]); -#else - if (freeze_bdev(tracker->diff_area->orig_bdev)) - pr_warn("Failed to freeze device [%u:%u]\n", - MAJOR(tracker->dev_id), MINOR(tracker->dev_id)); - else { - tracker->is_frozen = true; - pr_debug("Device [%u:%u] was frozen\n", - MAJOR(tracker->dev_id), MINOR(tracker->dev_id)); - } -#endif - } - - /* Set tracker as available for new snapshots. */ - for (inx = 0; inx < snapshot->count; ++inx) - tracker_release_snapshot(snapshot->tracker_array[inx]); - - /* Thaw fs on each original block device. */ - for (inx = 0; inx < snapshot->count; ++inx) { - struct tracker *tracker = snapshot->tracker_array[inx]; - - if (!tracker || !tracker->diff_area || !tracker->is_frozen) - continue; - -#if defined(HAVE_SUPER_BLOCK_FREEZE) - _thaw_bdev(tracker->diff_area->orig_bdev, - snapshot->superblock_array[inx]); -#else - if (thaw_bdev(tracker->diff_area->orig_bdev)) - pr_err("Failed to thaw device [%u:%u]\n", - MAJOR(tracker->dev_id), MINOR(tracker->dev_id)); - else - pr_debug("Device [%u:%u] was unfrozen\n", - MAJOR(tracker->dev_id), MINOR(tracker->dev_id)); -#endif - tracker->is_frozen = false; - } -} - -#endif /* BLK_SNAP_SEQUENTALFREEZE */ - -static void snapshot_release(struct snapshot *snapshot) -{ - int inx; - - pr_info("Release snapshot %pUb\n", &snapshot->id); - - /* Destroy all snapshot images. */ - for (inx = 0; inx < snapshot->count; ++inx) { - struct snapimage *snapimage = snapshot->snapimage_array[inx]; - - if (snapimage) - snapimage_free(snapimage); - } - - snapshot_release_trackers(snapshot); - - /* Destroy diff area for each tracker. */ - for (inx = 0; inx < snapshot->count; ++inx) { - struct tracker *tracker = snapshot->tracker_array[inx]; - - if (tracker) { - diff_area_put(tracker->diff_area); - tracker->diff_area = NULL; - - tracker_put(tracker); - snapshot->tracker_array[inx] = NULL; - } - } -} - -static void snapshot_free(struct kref *kref) -{ - struct snapshot *snapshot = container_of(kref, struct snapshot, kref); - - snapshot_release(snapshot); - - kfree(snapshot->snapimage_array); - if (snapshot->snapimage_array) - memory_object_dec(memory_object_snapimage_array); - kfree(snapshot->tracker_array); - if (snapshot->tracker_array) - memory_object_dec(memory_object_tracker_array); - -#if defined(HAVE_SUPER_BLOCK_FREEZE) && !defined(BLK_SNAP_SEQUENTALFREEZE) - if (snapshot->superblock_array) { - kfree(snapshot->superblock_array); - memory_object_dec(memory_object_superblock_array); - } -#endif - - diff_storage_put(snapshot->diff_storage); - - kfree(snapshot); - memory_object_dec(memory_object_snapshot); -} - -static inline void snapshot_get(struct snapshot *snapshot) -{ - kref_get(&snapshot->kref); -}; -static inline void snapshot_put(struct snapshot *snapshot) -{ - if (likely(snapshot)) - kref_put(&snapshot->kref, snapshot_free); -}; - -static struct snapshot *snapshot_new(unsigned int count) -{ - int ret; - struct snapshot *snapshot = NULL; - - snapshot = kzalloc(sizeof(struct snapshot), GFP_KERNEL); - if (!snapshot) { - ret = -ENOMEM; - goto fail; - } - memory_object_inc(memory_object_snapshot); - - snapshot->tracker_array = kcalloc(count, sizeof(void *), GFP_KERNEL); - if (!snapshot->tracker_array) { - ret = -ENOMEM; - goto fail_free_snapshot; - } - memory_object_inc(memory_object_tracker_array); - - snapshot->snapimage_array = kcalloc(count, sizeof(void *), GFP_KERNEL); - if (!snapshot->snapimage_array) { - ret = -ENOMEM; - goto fail_free_trackers; - } - memory_object_inc(memory_object_snapimage_array); - -#if defined(HAVE_SUPER_BLOCK_FREEZE) && !defined(BLK_SNAP_SEQUENTALFREEZE) - snapshot->superblock_array = kcalloc(count, sizeof(void *), GFP_KERNEL); - if (!snapshot->superblock_array) { - ret = -ENOMEM; - goto fail_free_snapimage; - } - memory_object_inc(memory_object_superblock_array); -#endif - snapshot->diff_storage = diff_storage_new(); - if (!snapshot->diff_storage) { - ret = -ENOMEM; - goto fail_free_snapimage; - } - - INIT_LIST_HEAD(&snapshot->link); - kref_init(&snapshot->kref); - uuid_gen(&snapshot->id); - snapshot->is_taken = false; - - return snapshot; - -fail_free_snapimage: -#if defined(HAVE_SUPER_BLOCK_FREEZE) && !defined(BLK_SNAP_SEQUENTALFREEZE) - kfree(snapshot->superblock_array); - if (snapshot->superblock_array) - memory_object_dec(memory_object_superblock_array); -#endif - kfree(snapshot->snapimage_array); - if (snapshot->snapimage_array) - memory_object_dec(memory_object_snapimage_array); - -fail_free_trackers: - kfree(snapshot->tracker_array); - if (snapshot->tracker_array) - memory_object_dec(memory_object_tracker_array); - -fail_free_snapshot: - kfree(snapshot); - if (snapshot) - memory_object_dec(memory_object_snapshot); -fail: - return ERR_PTR(ret); -} - -void snapshot_done(void) -{ - struct snapshot *snapshot; - - pr_debug("Cleanup snapshots\n"); - do { - down_write(&snapshots_lock); - snapshot = list_first_entry_or_null(&snapshots, struct snapshot, - link); - if (snapshot) - list_del(&snapshot->link); - up_write(&snapshots_lock); - - snapshot_put(snapshot); - } while (snapshot); -} - -static inline bool blk_snap_dev_is_equal(struct blk_snap_dev *first, - struct blk_snap_dev *second) -{ - return (first->mj == second->mj) && (first->mn == second->mn); -} - -static inline int check_same_devices(struct blk_snap_dev *devices, - unsigned int count) -{ - struct blk_snap_dev *first; - struct blk_snap_dev *second; - - for (first = devices; first < (devices + (count - 1)); ++first) { - for (second = first + 1; second < (devices + count); ++second) { - if (blk_snap_dev_is_equal(first, second)) { - pr_err("Unable to create snapshot: The same device [%d:%d] was added twice.\n", - first->mj, first->mn); - return -EINVAL; - } - } - } - - return 0; -} - -int snapshot_create(struct blk_snap_dev *dev_id_array, unsigned int count, - uuid_t *id) -{ - struct snapshot *snapshot = NULL; - int ret; - unsigned int inx; - - pr_info("Create snapshot for devices:\n"); - for (inx = 0; inx < count; ++inx) - pr_info("\t%u:%u\n", dev_id_array[inx].mj, - dev_id_array[inx].mn); - - ret = check_same_devices(dev_id_array, count); - if (ret) - return ret; - - snapshot = snapshot_new(count); - if (IS_ERR(snapshot)) { - pr_err("Unable to create snapshot: failed to allocate snapshot structure\n"); - return PTR_ERR(snapshot); - } - - ret = -ENODEV; - for (inx = 0; inx < count; ++inx) { - dev_t dev_id = - MKDEV(dev_id_array[inx].mj, dev_id_array[inx].mn); - struct tracker *tracker; - - tracker = tracker_create_or_get(dev_id); - if (IS_ERR(tracker)) { - pr_err("Unable to create snapshot\n"); - pr_err("Failed to add device [%u:%u] to snapshot tracking\n", - MAJOR(dev_id), MINOR(dev_id)); - ret = PTR_ERR(tracker); - goto fail; - } - - snapshot->tracker_array[inx] = tracker; - snapshot->count++; - } - - down_write(&snapshots_lock); - list_add_tail(&snapshots, &snapshot->link); - up_write(&snapshots_lock); - - uuid_copy(id, &snapshot->id); - pr_info("Snapshot %pUb was created\n", &snapshot->id); - return 0; -fail: - pr_err("Snapshot cannot be created\n"); - - snapshot_put(snapshot); - return ret; -} - -static struct snapshot *snapshot_get_by_id(uuid_t *id) -{ - struct snapshot *snapshot = NULL; - struct snapshot *s; - - down_read(&snapshots_lock); - if (list_empty(&snapshots)) - goto out; - - list_for_each_entry(s, &snapshots, link) { - if (uuid_equal(&s->id, id)) { - snapshot = s; - snapshot_get(snapshot); - break; - } - } -out: - up_read(&snapshots_lock); - return snapshot; -} - -int snapshot_destroy(uuid_t *id) -{ - struct snapshot *snapshot = NULL; - - pr_info("Destroy snapshot %pUb\n", id); - memory_object_print(false); - down_write(&snapshots_lock); - if (!list_empty(&snapshots)) { - struct snapshot *s = NULL; - - list_for_each_entry(s, &snapshots, link) { - if (uuid_equal(&s->id, id)) { - snapshot = s; - list_del(&snapshot->link); - break; - } - } - } - up_write(&snapshots_lock); - - if (!snapshot) { - pr_err("Unable to destroy snapshot: cannot find snapshot by id %pUb\n", - id); - return -ENODEV; - } - snapshot_put(snapshot); - memory_object_print(false); - memory_object_max_print(); - - return 0; -} - -int snapshot_append_storage(uuid_t *id, struct blk_snap_dev dev_id, - struct blk_snap_block_range __user *ranges, - unsigned int range_count) -{ - int ret = 0; - struct snapshot *snapshot; - - snapshot = snapshot_get_by_id(id); - if (!snapshot) - return -ESRCH; - - ret = diff_storage_append_block(snapshot->diff_storage, - MKDEV(dev_id.mj, dev_id.mn), ranges, - range_count); - snapshot_put(snapshot); - return ret; -} - -#if defined(BLK_SNAP_SEQUENTALFREEZE) - -/* - * snapshot_take_trackers - Take tracker for snapshot - * - * The sequential algorithm allows to freeze block devices one at a time. - */ -static int snapshot_take_trackers(struct snapshot *snapshot) -{ - int ret = 0; - int inx; - - /* Try to flush and freeze file system on each original block device. */ - for (inx = 0; inx < snapshot->count; inx++) { - struct tracker *tracker = snapshot->tracker_array[inx]; -#if defined(HAVE_SUPER_BLOCK_FREEZE) - struct super_block *sb; -#else - bool is_frozen = false; -#endif - struct block_device *orig_bdev; - - if (!tracker) - continue; - - orig_bdev = tracker->diff_area->orig_bdev; -#if defined(HAVE_SUPER_BLOCK_FREEZE) - _freeze_bdev(orig_bdev, &sb); -#else - if (freeze_bdev(orig_bdev)) - pr_err("Failed to freeze device [%u:%u]\n", - MAJOR(tracker->dev_id), MINOR(tracker->dev_id)); - else { - is_frozen = true; - pr_debug("Device [%u:%u] was frozen\n", - MAJOR(tracker->dev_id), MINOR(tracker->dev_id)); - } -#endif - - - /* - * Take snapshot - switch CBT tables and enable COW logic - * for each tracker. - */ - ret = tracker_take_snapshot(tracker); - if (ret) { - pr_err("Unable to take snapshot: failed to capture snapshot %pUb\n", - &snapshot->id); - break; - } - - /* Thaw file systems on original block devices. */ -#if defined(HAVE_SUPER_BLOCK_FREEZE) - _thaw_bdev(orig_bdev, sb); -#else - if (!is_frozen) - continue; - if (thaw_bdev(orig_bdev)) - pr_err("Failed to thaw device [%u:%u]\n", - MAJOR(tracker->dev_id), MINOR(tracker->dev_id)); - else - pr_debug("Device [%u:%u] was unfrozen\n", - MAJOR(tracker->dev_id), MINOR(tracker->dev_id)); -#endif - } - - if (!ret) { - snapshot->is_taken = true; - return 0; - } - - while (inx--) { - struct tracker *tracker = snapshot->tracker_array[inx]; -#if defined(HAVE_SUPER_BLOCK_FREEZE) - struct super_block *sb; -#endif - - if (!tracker) - continue; - -#if defined(HAVE_SUPER_BLOCK_FREEZE) - _freeze_bdev(tracker->diff_area->orig_bdev, &sb); -#else - if (freeze_bdev(tracker->diff_area->orig_bdev)) - pr_warn("Failed to freeze device [%u:%u]\n", - MAJOR(tracker->dev_id), MINOR(tracker->dev_id)); - else - pr_debug("Device [%u:%u] was frozen\n", - MAJOR(tracker->dev_id), MINOR(tracker->dev_id)); -#endif - - tracker_release_snapshot(tracker); - -#if defined(HAVE_SUPER_BLOCK_FREEZE) - _thaw_bdev(tracker->diff_area->orig_bdev, sb); -#else - if (thaw_bdev(tracker->diff_area->orig_bdev)) - pr_err("Failed to thaw device [%u:%u]\n", - MAJOR(tracker->dev_id), - MINOR(tracker->dev_id)); - else - pr_debug("Device [%u:%u] was unfrozen\n", - MAJOR(tracker->dev_id), - MINOR(tracker->dev_id)); -#endif - } - - return ret; -} -#else /* BLK_SNAP_SEQUENTALFREEZE */ - -/* - * snapshot_take_trackers - Take tracker for snapshot - * - * The simultaneous algorithm allows to freeze all the snapshot block devices. - */ - -static int snapshot_take_trackers(struct snapshot *snapshot) -{ - int ret = 0; - int inx; - - /* Try to flush and freeze file system on each original block device. */ - for (inx = 0; inx < snapshot->count; inx++) { - struct tracker *tracker = snapshot->tracker_array[inx]; - - if (!tracker) - continue; - -#if defined(HAVE_SUPER_BLOCK_FREEZE) - _freeze_bdev(tracker->diff_area->orig_bdev, - &snapshot->superblock_array[inx]); -#else - if (freeze_bdev(tracker->diff_area->orig_bdev)) - pr_warn("Failed to freeze device [%u:%u]\n", - MAJOR(tracker->dev_id), MINOR(tracker->dev_id)); - else { - tracker->is_frozen = true; - pr_debug("Device [%u:%u] was frozen\n", - MAJOR(tracker->dev_id), MINOR(tracker->dev_id)); - } -#endif - } - - /* - * Take snapshot - switch CBT tables and enable COW logic - * for each tracker. - */ - for (inx = 0; inx < snapshot->count; inx++) { - if (!snapshot->tracker_array[inx]) - continue; - - ret = tracker_take_snapshot(snapshot->tracker_array[inx]); - if (ret) { - pr_err("Unable to take snapshot: failed to capture snapshot %pUb\n", - &snapshot->id); - break; - } - } - - if (ret) { - while (inx--) { - struct tracker *tracker = snapshot->tracker_array[inx]; - - if (tracker) - tracker_release_snapshot(tracker); - } - } else - snapshot->is_taken = true; - - /* Thaw file systems on original block devices. */ - for (inx = 0; inx < snapshot->count; inx++) { - struct tracker *tracker = snapshot->tracker_array[inx]; - - if (!tracker || !tracker->is_frozen) - continue; - -#if defined(HAVE_SUPER_BLOCK_FREEZE) - _thaw_bdev(tracker->diff_area->orig_bdev, - snapshot->superblock_array[inx]); -#else - if (thaw_bdev(tracker->diff_area->orig_bdev)) - pr_err("Failed to thaw device [%u:%u]\n", - MAJOR(tracker->dev_id), MINOR(tracker->dev_id)); - else - pr_debug("Device [%u:%u] was unfrozen\n", - MAJOR(tracker->dev_id), MINOR(tracker->dev_id)); -#endif - tracker->is_frozen = false; - } - - return ret; -} -#endif /* BLK_SNAP_SEQUENTALFREEZE */ - -int snapshot_take(uuid_t *id) -{ - int ret = 0; - struct snapshot *snapshot; - int inx; - - snapshot = snapshot_get_by_id(id); - if (!snapshot) - return -ESRCH; - - if (snapshot->is_taken) { - ret = -EALREADY; - goto out; - } - - if (!snapshot->count) { - ret = -ENODEV; - goto out; - } - - /* Allocate diff area for each device in the snapshot. */ - for (inx = 0; inx < snapshot->count; inx++) { - struct tracker *tracker = snapshot->tracker_array[inx]; - struct diff_area *diff_area; - - if (!tracker) - continue; - - diff_area = - diff_area_new(tracker->dev_id, snapshot->diff_storage); - if (IS_ERR(diff_area)) { - ret = PTR_ERR(diff_area); - goto fail; - } - tracker->diff_area = diff_area; - } - - ret = snapshot_take_trackers(snapshot); - if (ret) - goto fail; - - pr_info("Snapshot was taken successfully\n"); - - /* - * Sometimes a snapshot is in the state of corrupt immediately - * after it is taken. - */ - for (inx = 0; inx < snapshot->count; inx++) { - struct tracker *tracker = snapshot->tracker_array[inx]; - - if (!tracker) - continue; - - if (unlikely(diff_area_is_corrupted(tracker->diff_area))) { - pr_err("Unable to freeze devices [%u:%u]: diff area is corrupted\n", - MAJOR(tracker->dev_id), MINOR(tracker->dev_id)); - ret = -EFAULT; - goto fail; - } - } - - /* Create all image block devices. */ - for (inx = 0; inx < snapshot->count; inx++) { - struct snapimage *snapimage; - struct tracker *tracker = snapshot->tracker_array[inx]; - - snapimage = - snapimage_create(tracker->diff_area, tracker->cbt_map); - if (IS_ERR(snapimage)) { - ret = PTR_ERR(snapimage); - pr_err("Failed to create snapshot image for device [%u:%u] with error=%d\n", - MAJOR(tracker->dev_id), MINOR(tracker->dev_id), - ret); - break; - } - snapshot->snapimage_array[inx] = snapimage; - } - - goto out; -fail: - pr_err("Unable to take snapshot %pUb.\n", &snapshot->id); - - down_write(&snapshots_lock); - list_del(&snapshot->link); - up_write(&snapshots_lock); - snapshot_put(snapshot); -out: - snapshot_put(snapshot); - return ret; -} - -struct event *snapshot_wait_event(uuid_t *id, unsigned long timeout_ms) -{ - struct snapshot *snapshot; - struct event *event; - - snapshot = snapshot_get_by_id(id); - if (!snapshot) - return ERR_PTR(-ESRCH); - - event = event_wait(&snapshot->diff_storage->event_queue, timeout_ms); - - snapshot_put(snapshot); - return event; -} - -int snapshot_collect(unsigned int *pcount, struct blk_snap_uuid __user *id_array) -{ - int ret = 0; - int inx = 0; - struct snapshot *s; - - pr_debug("Collect snapshots\n"); - - down_read(&snapshots_lock); - if (list_empty(&snapshots)) - goto out; - - if (!id_array) { - list_for_each_entry(s, &snapshots, link) - inx++; - goto out; - } - - list_for_each_entry(s, &snapshots, link) { - if (inx >= *pcount) { - ret = -ENODATA; - goto out; - } - - if (copy_to_user(id_array[inx].b, &s->id.b, sizeof(uuid_t))) { - pr_err("Unable to collect snapshots: failed to copy data to user buffer\n"); - goto out; - } - - inx++; - } -out: - up_read(&snapshots_lock); - *pcount = inx; - return ret; -} - -int snapshot_collect_images( - uuid_t *id, struct blk_snap_image_info __user *user_image_info_array, - unsigned int *pcount) -{ - int ret = 0; - int inx; - unsigned long len; - struct blk_snap_image_info *image_info_array = NULL; - struct snapshot *snapshot; - - pr_debug("Collect images for snapshots\n"); - - snapshot = snapshot_get_by_id(id); - if (!snapshot) - return -ESRCH; - - if (!snapshot->is_taken) { - ret = -ENODEV; - goto out; - } - - pr_debug("Found snapshot with %d devices\n", snapshot->count); - if (!user_image_info_array) { - pr_debug( - "Unable to collect snapshot images: users buffer is not set\n"); - goto out; - } - - if (*pcount < snapshot->count) { - ret = -ENODATA; - goto out; - } - - image_info_array = kcalloc(snapshot->count, - sizeof(struct blk_snap_image_info), - GFP_KERNEL); - if (!image_info_array) { - pr_err("Unable to collect snapshot images: not enough memory.\n"); - ret = -ENOMEM; - goto out; - } - memory_object_inc(memory_object_blk_snap_image_info); - - for (inx = 0; inx < snapshot->count; inx++) { - if (snapshot->tracker_array[inx]) { - struct tracker *tr = snapshot->tracker_array[inx]; - int mj = MAJOR(tr->dev_id); - int mn = MINOR(tr->dev_id); - - pr_debug("Original [%u:%u]\n", mj, mn); - image_info_array[inx].orig_dev_id.mj = mj; - image_info_array[inx].orig_dev_id.mn = mn; - } - - if (snapshot->snapimage_array[inx]) { - struct snapimage *img = snapshot->snapimage_array[inx]; - int mj = img->disk->major; - int mn = img->disk->first_minor; - - pr_debug("Image [%u:%u]\n", mj, mn); - image_info_array[inx].image_dev_id.mj = mj; - image_info_array[inx].image_dev_id.mn = mn; - } - } - - len = copy_to_user(user_image_info_array, image_info_array, - snapshot->count * - sizeof(struct blk_snap_image_info)); - if (len != 0) { - pr_err("Unable to collect snapshot images: failed to copy data to user buffer\n"); - ret = -ENODATA; - } -out: - *pcount = snapshot->count; - - kfree(image_info_array); - if (image_info_array) - memory_object_dec(memory_object_blk_snap_image_info); - snapshot_put(snapshot); - - return ret; -} - -int snapshot_mark_dirty_blocks(dev_t image_dev_id, - struct blk_snap_block_range *block_ranges, - unsigned int count) -{ - int ret = 0; - int inx = 0; - struct snapshot *s; - struct cbt_map *cbt_map = NULL; - - pr_debug("Marking [%d] dirty blocks for device [%u:%u]\n", count, - MAJOR(image_dev_id), MINOR(image_dev_id)); - - down_read(&snapshots_lock); - if (list_empty(&snapshots)) - goto out; - - list_for_each_entry(s, &snapshots, link) { - for (inx = 0; inx < s->count; inx++) { - struct snapimage *img = s->snapimage_array[inx]; - int mj = img->disk->major; - int mn = img->disk->first_minor; - - if (MKDEV(mj,mn) == image_dev_id) { - cbt_map = img->cbt_map; - break; - } - } - - inx++; - } - if (!cbt_map) { - pr_debug("Cannot find snapshot image device [%u:%u]\n", - MAJOR(image_dev_id), MINOR(image_dev_id)); - ret = -ENODEV; - goto out; - } - - ret = cbt_map_mark_dirty_blocks(cbt_map, block_ranges, count); - if (ret) - pr_err("Failed to set CBT table. errno=%d\n", abs(ret)); -out: - up_read(&snapshots_lock); - - return ret; -} - -#ifdef BLK_SNAP_DEBUG_SECTOR_STATE -int snapshot_get_chunk_state(dev_t image_dev_id, sector_t sector, - struct blk_snap_sector_state *state) -{ - int ret = 0; - int inx = 0; - struct snapshot *s; - struct snapimage *image = NULL; - - down_read(&snapshots_lock); - if (list_empty(&snapshots)) - goto out; - - list_for_each_entry (s, &snapshots, link) { - for (inx = 0; inx < s->count; inx++) { - struct snapimage *img = s->snapimage_array[inx]; - int mj = img->disk->major; - int mn = img->disk->first_minor; - - if (MKDEV(mj,mn) == image_dev_id) { - image = img; - break; - } - } - - inx++; - } - if (!image) { - pr_err("Cannot find snapshot image device [%u:%u]\n", - MAJOR(image_dev_id), MINOR(image_dev_id)); - ret = -ENODEV; - goto out; - } - - ret = snapimage_get_chunk_state(image, sector, state); - if (ret) - pr_err("Failed to get chunk state. errno=%d\n", abs(ret)); -out: - up_read(&snapshots_lock); - - return ret; -} -#endif diff --git a/module/snapshot.h b/module/snapshot.h deleted file mode 100644 index 45a7d702..00000000 --- a/module/snapshot.h +++ /dev/null @@ -1,86 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef __BLK_SNAP_SNAPSHOT_H -#define __BLK_SNAP_SNAPSHOT_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include "event_queue.h" - -struct tracker; -struct diff_storage; -struct snapimage; -/** - * struct snapshot - Snapshot structure. - * @link: - * The list header allows to store snapshots in a linked list. - * @kref: - * Protects the structure from being released during the processing of - * an ioctl. - * @id: - * UUID of snapshot. - * @is_taken: - * Flag that the snapshot was taken. - * @diff_storage: - * A pointer to the difference storage of this snapshot. - * @count: - * The number of block devices in the snapshot. This number - * corresponds to the size of arrays of pointers to trackers - * and snapshot images. - * @tracker_array: - * Array of pointers to block device trackers. - * @snapimage_array: - * Array of pointers to images of snapshots of block devices. - * - * A snapshot corresponds to a single backup session and provides snapshot - * images for multiple block devices. Several backup sessions can be - * performed at the same time, which means that several snapshots can - * exist at the same time. However, the original block device can only - * belong to one snapshot. Creating multiple snapshots from the same block - * device is not allowed. - * - * A UUID is used to identify the snapshot. - * - */ -struct snapshot { - struct list_head link; - struct kref kref; - uuid_t id; - bool is_taken; - struct diff_storage *diff_storage; - int count; - struct tracker **tracker_array; - struct snapimage **snapimage_array; -#if defined(HAVE_SUPER_BLOCK_FREEZE) && !defined(BLK_SNAP_SEQUENTALFREEZE) - struct super_block **superblock_array; -#endif -}; - -void snapshot_done(void); - -int snapshot_create(struct blk_snap_dev *dev_id_array, unsigned int count, - uuid_t *id); -int snapshot_destroy(uuid_t *id); -int snapshot_append_storage(uuid_t *id, struct blk_snap_dev dev_id, - struct blk_snap_block_range __user *ranges, - unsigned int range_count); -int snapshot_take(uuid_t *id); -struct event *snapshot_wait_event(uuid_t *id, unsigned long timeout_ms); -int snapshot_collect(unsigned int *pcount, struct blk_snap_uuid __user *id_array); -int snapshot_collect_images(uuid_t *id, - struct blk_snap_image_info __user *image_info_array, - unsigned int *pcount); -int snapshot_mark_dirty_blocks(dev_t image_dev_id, - struct blk_snap_block_range *block_ranges, - unsigned int count); - -#ifdef BLK_SNAP_DEBUG_SECTOR_STATE -int snapshot_get_chunk_state(dev_t image_dev_id, sector_t sector, - struct blk_snap_sector_state *state); -#endif -#endif /* __BLK_SNAP_SNAPSHOT_H */ diff --git a/module/tracker.c b/module/tracker.c deleted file mode 100644 index f97a19b6..00000000 --- a/module/tracker.c +++ /dev/null @@ -1,790 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#define pr_fmt(fmt) KBUILD_MODNAME "-tracker: " fmt - -#include -#include -#include -#ifdef STANDALONE_BDEVFILTER -#include "blksnap.h" -#else -#include -#endif -#include "memory_checker.h" -#include "tracker.h" -#include "cbt_map.h" -#include "diff_area.h" -#include "log.h" - -#ifndef HAVE_BDEV_NR_SECTORS -static inline sector_t bdev_nr_sectors(struct block_device *bdev) -{ - return i_size_read(bdev->bd_inode) >> 9; -}; -#endif - -struct tracked_device { - struct list_head link; - dev_t dev_id; -}; - -LIST_HEAD(tracked_device_list); -DEFINE_SPINLOCK(tracked_device_lock); -static refcount_t trackers_counter = REFCOUNT_INIT(1); - -struct tracker_release_worker { - struct work_struct work; - struct list_head list; - spinlock_t lock; -}; -static struct tracker_release_worker tracker_release_worker; - -static void tracker_free(struct tracker *tracker) -{ - might_sleep(); - - pr_debug("Free tracker for device [%u:%u].\n", MAJOR(tracker->dev_id), - MINOR(tracker->dev_id)); - - diff_area_put(tracker->diff_area); - cbt_map_put(tracker->cbt_map); - - kfree(tracker); - memory_object_dec(memory_object_tracker); - - refcount_dec(&trackers_counter); -} - -static inline struct tracker *tracker_get_by_dev(struct block_device *bdev) -{ -#ifdef STANDALONE_BDEVFILTER - struct bdev_filter *flt; - - flt = bdev_filter_get_by_bdev(bdev); - if (IS_ERR(flt)) - return ERR_PTR(PTR_ERR(flt)); - if (!flt) - return NULL; -#else - struct bdev_filter *flt = bdev->bd_filter; - - if (!flt) - return NULL; - - bdev_filter_get(flt); -#endif - return container_of(flt, struct tracker, flt); -} - -#ifdef STANDALONE_BDEVFILTER -void diff_io_endio(struct bio *bio); -#endif - -#ifdef STANDALONE_BDEVFILTER -static bool tracker_submit_bio(struct bio *bio, - struct bdev_filter *flt) -{ -#else -static bool tracker_submit_bio(struct bio *bio) -{ - struct bdev_filter *flt = bio->bi_bdev->bd_filter; -#endif - struct bio_list bio_list_on_stack[2] = { }; - struct bio *new_bio; - struct tracker *tracker = container_of(flt, struct tracker, flt); - int err; - sector_t sector; - sector_t count; - unsigned int current_flag; - bool is_nowait = !!(bio->bi_opf & REQ_NOWAIT); - -#ifdef STANDALONE_BDEVFILTER - /* - * For the upstream version of the module, the definition of bio that - * does not need to be intercepted is performed using the flag - * BIO_FILTERED. - * But for the standalone version of the module, we can only use the - * context of bio. - */ - if (bio->bi_end_io == diff_io_endio) - return false; -#endif - - if (!op_is_write(bio_op(bio))) - return false; - - count = bio_sectors(bio); - if (!count) - return false; - - sector = bio->bi_iter.bi_sector; -#ifdef HAVE_BI_BDEV - if (bio_flagged(bio, BIO_REMAPPED)) - sector -= bio->bi_bdev->bd_start_sect; -#endif - current_flag = memalloc_noio_save(); - err = cbt_map_set(tracker->cbt_map, sector, count); - memalloc_noio_restore(current_flag); - - if (err || - !atomic_read(&tracker->snapshot_is_taken) || - diff_area_is_corrupted(tracker->diff_area)) - return false; - - current_flag = memalloc_noio_save(); - bio_list_init(&bio_list_on_stack[0]); - current->bio_list = bio_list_on_stack; - - err = diff_area_copy(tracker->diff_area, sector, count, is_nowait); - - current->bio_list = NULL; - memalloc_noio_restore(current_flag); - - if (unlikely(err)) { - if (err == -EAGAIN) { - bio_wouldblock_error(bio); - return true; - } - pr_err("Failed to copy data to diff storage with error %d.\n", abs(err)); - return false; - } - - while ((new_bio = bio_list_pop(&bio_list_on_stack[0]))) { - /* - * The result from submitting a bio from the - * filter itself does not need to be processed, - * even if this function has a return code. - */ -#ifdef STANDALONE_BDEVFILTER - submit_bio_noacct_notrace(new_bio); -#else - bio_set_flag(new_bio, BIO_FILTERED); - submit_bio_noacct(new_bio); -#endif - } - /* - * If a new bio was created during the handling, then new bios must - * be sent and returned to complete the processing of the original bio. - * Unfortunately, this has to be done for any bio, regardless of their - * flags and options. - * Otherwise, write I/O units may overtake read I/O units. - */ - err = diff_area_wait(tracker->diff_area, sector, count, is_nowait); - if (unlikely(err)) { - if (err == -EAGAIN) { - bio_wouldblock_error(bio); - return true; - } - pr_err("Failed to wait for available data in diff storage with error %d.\n", abs(err)); - } - return false; -} - - -static void tracker_release_work(struct work_struct *work) -{ - struct tracker *tracker = NULL; - struct tracker_release_worker *tracker_release = - container_of(work, struct tracker_release_worker, work); - - do { - spin_lock(&tracker_release->lock); - tracker = list_first_entry_or_null(&tracker_release->list, - struct tracker, link); - if (tracker) - list_del(&tracker->link); - spin_unlock(&tracker_release->lock); - - if (tracker) - tracker_free(tracker); - } while (tracker); -} - -static void tracker_release(struct bdev_filter *flt) -{ - struct tracker *tracker = container_of(flt, struct tracker, flt); - - spin_lock(&tracker_release_worker.lock); - list_add_tail(&tracker->link, &tracker_release_worker.list); - spin_unlock(&tracker_release_worker.lock); - - queue_work(system_wq, &tracker_release_worker.work); -} - -static const struct bdev_filter_operations tracker_fops = { - .submit_bio = tracker_submit_bio, - .release = tracker_release -}; - -static int tracker_filter_attach(struct block_device *bdev, - struct tracker *tracker) -{ - int ret; - -#if defined(HAVE_SUPER_BLOCK_FREEZE) - struct super_block *superblock = NULL; -#endif - pr_debug("Tracker attach filter\n"); - -#if defined(HAVE_SUPER_BLOCK_FREEZE) - _freeze_bdev(bdev, &superblock); -#else - if (freeze_bdev(bdev)) { - /* - * If the file system has not been frozen, we have to attach a - * filter. This means that when the filter was attached, the - * state of the file system was not consistent. - * If the file system cannot be frozen, it is possible that it - * is damaged and requires repair. For such a file system, we - * still need to create a snapshot and perform a backup for - * subsequent repair during recovery. - */ - pr_warn("Failed to freeze device [%u:%u]\n", - MAJOR(bdev->bd_dev), MINOR(bdev->bd_dev)); - } - else { - tracker->is_frozen = true; - pr_debug("Device [%u:%u] was frozen\n", - MAJOR(bdev->bd_dev), MINOR(bdev->bd_dev)); - } -#endif - - ret = bdev_filter_attach(bdev, &tracker->flt); - -#if defined(HAVE_SUPER_BLOCK_FREEZE) - _thaw_bdev(bdev, superblock); -#else - if (tracker->is_frozen) { - tracker->is_frozen = false; - if (thaw_bdev(bdev)) - pr_err("Failed to thaw device [%u:%u]\n", - MAJOR(tracker->dev_id), MINOR(tracker->dev_id)); - else - pr_debug("Device [%u:%u] was unfrozen\n", - MAJOR(bdev->bd_dev), MINOR(bdev->bd_dev)); - } -#endif - - if (ret) - pr_err("Failed to attach tracker to device [%u:%u]\n", - MAJOR(tracker->dev_id), MINOR(tracker->dev_id)); - - return ret; -} - -static void tracker_filter_detach(struct block_device *bdev) -{ -#if defined(HAVE_SUPER_BLOCK_FREEZE) - struct super_block *superblock = NULL; -#else - bool is_frozen = false; -#endif - - pr_debug("Tracker delete filter\n"); -#if defined(HAVE_SUPER_BLOCK_FREEZE) - _freeze_bdev(bdev, &superblock); -#else - if (freeze_bdev(bdev)) { - /* - * It is assumed that if the filter no longer wants to filter - * I/O units on a block device, then it does not matter at all - * what state the file system is in. - */ - pr_warn("Failed to freeze device [%u:%u]\n", - MAJOR(bdev->bd_dev), MINOR(bdev->bd_dev)); - } - else { - is_frozen = true; - pr_debug("Device [%u:%u] was frozen\n", MAJOR(bdev->bd_dev), - MINOR(bdev->bd_dev)); - } -#endif - - bdev_filter_detach(bdev); - -#if defined(HAVE_SUPER_BLOCK_FREEZE) - _thaw_bdev(bdev, superblock); -#else - if (is_frozen) { - if (thaw_bdev(bdev)) - pr_err("Failed to thaw device [%u:%u]\n", - MAJOR(bdev->bd_dev), MINOR(bdev->bd_dev)); - else - pr_debug("Device [%u:%u] was unfrozen\n", - MAJOR(bdev->bd_dev), MINOR(bdev->bd_dev)); - } -#endif -} - -static struct tracker *tracker_new(struct block_device *bdev) -{ - int ret; - struct tracker *tracker = NULL; - struct cbt_map *cbt_map; - - pr_debug("Creating tracker for device [%u:%u].\n", MAJOR(bdev->bd_dev), - MINOR(bdev->bd_dev)); - - tracker = kzalloc(sizeof(struct tracker), GFP_KERNEL); - if (tracker == NULL) - return ERR_PTR(-ENOMEM); - memory_object_inc(memory_object_tracker); - - refcount_inc(&trackers_counter); - bdev_filter_init(&tracker->flt, &tracker_fops); - INIT_LIST_HEAD(&tracker->link); - atomic_set(&tracker->snapshot_is_taken, false); - tracker->dev_id = bdev->bd_dev; - - pr_info("Create tracker for device [%u:%u]. Capacity 0x%llx sectors\n", - MAJOR(tracker->dev_id), MINOR(tracker->dev_id), - (unsigned long long)bdev_nr_sectors(bdev)); - - cbt_map = cbt_map_create(bdev); - if (!cbt_map) { - pr_err("Failed to create tracker for device [%u:%u]\n", - MAJOR(tracker->dev_id), MINOR(tracker->dev_id)); - ret = -ENOMEM; - goto fail; - } - tracker->cbt_map = cbt_map; - tracker->is_frozen = false; - - ret = tracker_filter_attach(bdev, tracker); - if (ret) { - pr_err("Failed to attach tracker. errno=%d\n", abs(ret)); - goto fail; - } - - pr_debug("New tracker for device [%u:%u] was created.\n", - MAJOR(tracker->dev_id), MINOR(tracker->dev_id)); - - return tracker; -fail: - tracker_put(tracker); - return ERR_PTR(ret); -} - -int tracker_take_snapshot(struct tracker *tracker) -{ - int ret = 0; - bool cbt_reset_needed = false; - struct block_device *orig_bdev = tracker->diff_area->orig_bdev; - sector_t capacity; - unsigned int current_flag; - -#ifdef STANDALONE_BDEVFILTER - bdevfilter_freeze_queue(&tracker->flt); -#else - blk_mq_freeze_queue(orig_bdev->bd_queue); -#endif - - current_flag = memalloc_noio_save(); - - if (tracker->cbt_map->is_corrupted) { - cbt_reset_needed = true; - pr_warn("Corrupted CBT table detected. CBT fault\n"); - } - - capacity = bdev_nr_sectors(orig_bdev); - if (tracker->cbt_map->device_capacity != capacity) { - cbt_reset_needed = true; - pr_warn("Device resize detected. CBT fault\n"); - } - - if (cbt_reset_needed) { - ret = cbt_map_reset(tracker->cbt_map, capacity); - if (ret) { - pr_err("Failed to create tracker. errno=%d\n", - abs(ret)); - return ret; - } - } - - cbt_map_switch(tracker->cbt_map); - atomic_set(&tracker->snapshot_is_taken, true); - - memalloc_noio_restore(current_flag); - -#ifdef STANDALONE_BDEVFILTER - bdevfilter_unfreeze_queue(&tracker->flt); -#else - blk_mq_unfreeze_queue(orig_bdev->bd_queue); -#endif - return 0; -} - -void tracker_release_snapshot(struct tracker *tracker) -{ -#ifdef STANDALONE_BDEVFILTER - bdevfilter_freeze_queue(&tracker->flt); -#else - blk_mq_freeze_queue(tracker->diff_area->orig_bdev->bd_queue); -#endif - - pr_debug("Tracker for device [%u:%u] release snapshot\n", - MAJOR(tracker->dev_id), MINOR(tracker->dev_id)); - - atomic_set(&tracker->snapshot_is_taken, false); - -#ifdef STANDALONE_BDEVFILTER - bdevfilter_unfreeze_queue(&tracker->flt); -#else - blk_mq_unfreeze_queue(tracker->diff_area->orig_bdev->bd_queue); -#endif -} - -int tracker_init(void) -{ - INIT_WORK(&tracker_release_worker.work, tracker_release_work); - INIT_LIST_HEAD(&tracker_release_worker.list); - spin_lock_init(&tracker_release_worker.lock); - - return 0; -} - -/* - * tracker_wait_for_release - Waiting for all trackers are released. - * - * Trackers are released in the worker thread. So, this function allows to wait - * for the end of the process of releasing trackers. - */ -static void tracker_wait_for_release(void) -{ - long inx = 0; - u64 start_waiting = jiffies_64; - - while (refcount_read(&trackers_counter) > 1) { - schedule_timeout_interruptible(HZ); - if (jiffies_64 > (start_waiting + 10*HZ)) { - start_waiting = jiffies_64; - inx++; - - if (inx <= 12) - pr_warn("Waiting for trackers release\n"); - - WARN_ONCE(inx > 12, "Failed to release trackers\n"); - } - } -} - -void tracker_done(void) -{ - struct tracked_device *tr_dev; - - pr_debug("Cleanup trackers\n"); - while (true) { - spin_lock(&tracked_device_lock); - tr_dev = list_first_entry_or_null(&tracked_device_list, - struct tracked_device, link); - if (tr_dev) - list_del(&tr_dev->link); - spin_unlock(&tracked_device_lock); - - if (!tr_dev) - break; - - tracker_remove(tr_dev->dev_id); - kfree(tr_dev); - memory_object_dec(memory_object_tracked_device); - } - - tracker_wait_for_release(); -} - -struct tracker *tracker_create_or_get(dev_t dev_id) -{ - struct tracker *tracker; - struct block_device *bdev; - struct tracked_device *tr_dev; - - bdev = blkdev_get_by_dev(dev_id, 0, NULL); - if (IS_ERR(bdev)) { - int err = PTR_ERR(bdev); - - pr_info("Cannot open device [%u:%u]\n", MAJOR(dev_id), - MINOR(dev_id)); - return ERR_PTR(err); - } - - tracker = tracker_get_by_dev(bdev); - if (IS_ERR(tracker)) { - int err = PTR_ERR(tracker); - - pr_err("Cannot get tracker for device [%u:%u]. errno=%d\n", - MAJOR(dev_id), MINOR(dev_id), abs(err)); - goto put_bdev; - } - if (tracker) { - pr_debug("Device [%u:%u] is already under tracking\n", - MAJOR(dev_id), MINOR(dev_id)); - goto put_bdev; - } - - tr_dev = kzalloc(sizeof(struct tracked_device), GFP_KERNEL); - if (!tr_dev) { - tracker = ERR_PTR(-ENOMEM); - goto put_bdev; - } - memory_object_inc(memory_object_tracked_device); - - INIT_LIST_HEAD(&tr_dev->link); - tr_dev->dev_id = dev_id; - - tracker = tracker_new(bdev); - if (IS_ERR(tracker)) { - int err = PTR_ERR(tracker); - - pr_err("Failed to create tracker. errno=%d\n", abs(err)); - kfree(tr_dev); - memory_object_dec(memory_object_tracked_device); - } else { - /* - * It is normal that the new trackers filter will have - * a ref counter value of 2. This allows not to detach - * the filter when the snapshot is released. - */ - bdev_filter_get(&tracker->flt); - - spin_lock(&tracked_device_lock); - list_add_tail(&tr_dev->link, &tracked_device_list); - spin_unlock(&tracked_device_lock); - } -put_bdev: - blkdev_put(bdev, 0); - return tracker; -} - -int tracker_remove(dev_t dev_id) -{ - int ret; - struct tracker *tracker; - struct block_device *bdev; - struct tracked_device *tr_dev = NULL; - struct tracked_device *iter_tr_dev; - - pr_info("Removing device [%u:%u] from tracking\n", MAJOR(dev_id), - MINOR(dev_id)); - - bdev = blkdev_get_by_dev(dev_id, 0, NULL); - if (IS_ERR(bdev)) { - pr_info("Cannot open device [%u:%u]\n", MAJOR(dev_id), - MINOR(dev_id)); -#ifdef STANDALONE_BDEVFILTER - return lp_bdev_filter_detach(dev_id); -#else - return PTR_ERR(bdev); -#endif - } - - tracker = tracker_get_by_dev(bdev); -#ifdef STANDALONE_BDEVFILTER - if (IS_ERR(tracker)) { - ret = PTR_ERR(tracker); - - pr_err("Failed to get tracker for device [%u:%u]. errno=%d\n", - MAJOR(dev_id), MINOR(dev_id), abs(ret)); - goto put_bdev; - } -#endif - if (!tracker) { - pr_info("Unable to remove device [%u:%u] from tracking: ", - MAJOR(dev_id), MINOR(dev_id)); - pr_info("tracker not found\n"); - ret = -ENODATA; - goto put_bdev; - } - - if (atomic_read(&tracker->snapshot_is_taken)) { - pr_err("Tracker for device [%u:%u] is busy with a snapshot\n", - MAJOR(dev_id), MINOR(dev_id)); - ret = -EBUSY; - goto put_tracker; - } - - tracker_filter_detach(bdev); - - spin_lock(&tracked_device_lock); - list_for_each_entry(iter_tr_dev, &tracked_device_list, link) { - if (iter_tr_dev->dev_id == dev_id) { - list_del(&iter_tr_dev->link); - tr_dev = iter_tr_dev; - break; - } - } - spin_unlock(&tracked_device_lock); - - kfree(tr_dev); - if (tr_dev) - memory_object_dec(memory_object_tracked_device); - -put_tracker: - tracker_put(tracker); -put_bdev: - blkdev_put(bdev, 0); - return ret; -} - -int tracker_read_cbt_bitmap(dev_t dev_id, unsigned int offset, size_t length, - char __user *user_buff) -{ - int ret; - struct tracker *tracker; - struct block_device *bdev; - - bdev = blkdev_get_by_dev(dev_id, 0, NULL); - if (IS_ERR(bdev)) { - pr_info("Cannot open device [%u:%u]\n", MAJOR(dev_id), - MINOR(dev_id)); - return PTR_ERR(bdev); - } - - tracker = tracker_get_by_dev(bdev); - if (IS_ERR(tracker)) { - pr_err("Cannot get tracker for device [%u:%u]\n", - MAJOR(dev_id), MINOR(dev_id)); - ret = PTR_ERR(tracker); - goto put_bdev; - } - if (!tracker) { - pr_info("Unable to read CBT bitmap for device [%u:%u]: ", - MAJOR(dev_id), MINOR(dev_id)); - pr_info("tracker not found\n"); - ret = -ENODATA; - goto put_bdev; - } - - if (atomic_read(&tracker->snapshot_is_taken)) { - ret = cbt_map_read_to_user(tracker->cbt_map, user_buff, - offset, length); - } else { - pr_err("Unable to read CBT bitmap for device [%u:%u]: ", - MAJOR(dev_id), MINOR(dev_id)); - pr_err("device is not captured by snapshot\n"); - ret = -EPERM; - } - - tracker_put(tracker); -put_bdev: - blkdev_put(bdev, 0); - return ret; -} - -static inline void collect_cbt_info(dev_t dev_id, - struct blk_snap_cbt_info *cbt_info) -{ - struct block_device *bdev; - struct tracker *tracker; - - bdev = blkdev_get_by_dev(dev_id, 0, NULL); - if (IS_ERR(bdev)) { - pr_err("Cannot open device [%u:%u]\n", MAJOR(dev_id), - MINOR(dev_id)); - return; - } - - tracker = tracker_get_by_dev(bdev); - if (IS_ERR_OR_NULL(tracker)) - goto put_bdev; - if (!tracker->cbt_map) - goto put_tracker; - - cbt_info->device_capacity = - (__u64)(tracker->cbt_map->device_capacity << SECTOR_SHIFT); - cbt_info->blk_size = (__u32)cbt_map_blk_size(tracker->cbt_map); - cbt_info->blk_count = (__u32)tracker->cbt_map->blk_count; - cbt_info->snap_number = (__u8)tracker->cbt_map->snap_number_previous; - - export_uuid(cbt_info->generation_id.b, &tracker->cbt_map->generation_id); -put_tracker: - tracker_put(tracker); -put_bdev: - blkdev_put(bdev, 0); -} - -int tracker_collect(int max_count, struct blk_snap_cbt_info *cbt_info, - int *pcount) -{ - int ret = 0; - int count = 0; - int iter = 0; - struct tracked_device *tr_dev; - - if (!cbt_info) { - /* - * Just calculate trackers list length. - */ - spin_lock(&tracked_device_lock); - list_for_each_entry(tr_dev, &tracked_device_list, link) - ++count; - spin_unlock(&tracked_device_lock); - goto out; - } - - spin_lock(&tracked_device_lock); - list_for_each_entry(tr_dev, &tracked_device_list, link) { - if (count >= max_count) { - ret = -ENOBUFS; - break; - } - - cbt_info[count].dev_id.mj = MAJOR(tr_dev->dev_id); - cbt_info[count].dev_id.mn = MINOR(tr_dev->dev_id); - ++count; - } - spin_unlock(&tracked_device_lock); - - if (ret) - return ret; - - for (iter = 0; iter < count; iter++) { - dev_t dev_id = MKDEV(cbt_info[iter].dev_id.mj, - cbt_info[iter].dev_id.mn); - - collect_cbt_info(dev_id, &cbt_info[iter]); - } -out: - *pcount = count; - return 0; -} - -int tracker_mark_dirty_blocks(dev_t dev_id, - struct blk_snap_block_range *block_ranges, - unsigned int count) -{ - int ret = 0; - struct tracker *tracker; - struct block_device *bdev; - - bdev = blkdev_get_by_dev(dev_id, 0, NULL); - if (IS_ERR(bdev)) { - pr_err("Cannot open device [%u:%u]\n", MAJOR(dev_id), - MINOR(dev_id)); - return PTR_ERR(bdev); - } - - pr_debug("Marking [%d] dirty blocks for device [%u:%u]\n", count, - MAJOR(dev_id), MINOR(dev_id)); - - tracker = tracker_get_by_dev(bdev); - if (IS_ERR(tracker)) { - pr_err("Failed to get tracker for device [%u:%u]\n", - MAJOR(dev_id), MINOR(dev_id)); - ret = PTR_ERR(tracker); - goto put_bdev; - } - if (!tracker) { - pr_err("Cannot find tracker for device [%u:%u]\n", - MAJOR(dev_id), MINOR(dev_id)); - ret = -ENODEV; - goto put_bdev; - } - - ret = cbt_map_mark_dirty_blocks(tracker->cbt_map, block_ranges, count); - if (ret) - pr_err("Failed to set CBT table. errno=%d\n", abs(ret)); - - tracker_put(tracker); -put_bdev: - blkdev_put(bdev, 0); - return ret; -} diff --git a/module/tracker.h b/module/tracker.h deleted file mode 100644 index e839db68..00000000 --- a/module/tracker.h +++ /dev/null @@ -1,128 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef __BLK_SNAP_TRACKER_H -#define __BLK_SNAP_TRACKER_H - -#include -#include -#include -#include -#include -#include - -#ifdef STANDALONE_BDEVFILTER -#include "bdevfilter.h" -#endif - -struct cbt_map; -struct diff_area; - -/** - * struct tracker - Tracker for a block device. - * - * @flt: - * The block device filter structure. - * @link: - * List header. Tracker release cannot be performed in the release() - * filters callback function. Therefore, the trackers are queued for - * release in the worker thread. - * @dev_id: - * Original block device ID. - * @snapshot_is_taken: - * Indicates that a snapshot was taken for the device whose I/O unit are - * handled by this tracker. - * @cbt_map: - * Pointer to a change block tracker map. - * @diff_area: - * Pointer to a difference area. - * - * The goal of the tracker is to handle I/O unit. The tracker detectes - * the range of sectors that will change and transmits them to the CBT map - * and to the difference area. - */ -struct tracker { - struct bdev_filter flt; - struct list_head link; - dev_t dev_id; - - atomic_t snapshot_is_taken; - - struct cbt_map *cbt_map; - struct diff_area *diff_area; - - bool is_frozen; -}; - -static inline void tracker_put(struct tracker *tracker) -{ - if (likely(tracker)) - bdev_filter_put(&tracker->flt); -}; - -int tracker_init(void); -void tracker_done(void); - -struct tracker *tracker_create_or_get(dev_t dev_id); -int tracker_remove(dev_t dev_id); -int tracker_collect(int max_count, struct blk_snap_cbt_info *cbt_info, - int *pcount); -int tracker_read_cbt_bitmap(dev_t dev_id, unsigned int offset, size_t length, - char __user *user_buff); -int tracker_mark_dirty_blocks(dev_t dev_id, - struct blk_snap_block_range *block_ranges, - unsigned int count); - -int tracker_take_snapshot(struct tracker *tracker); -void tracker_release_snapshot(struct tracker *tracker); - -#if defined(HAVE_SUPER_BLOCK_FREEZE) -static inline int _freeze_bdev(struct block_device *bdev, - struct super_block **psuperblock) -{ - struct super_block *superblock; - - pr_debug("Freezing device [%u:%u]\n", MAJOR(bdev->bd_dev), - MINOR(bdev->bd_dev)); - - if (bdev->bd_super == NULL) { - pr_warn("Unable to freeze device [%u:%u]: no superblock was found\n", - MAJOR(bdev->bd_dev), MINOR(bdev->bd_dev)); - return 0; - } - - superblock = freeze_bdev(bdev); - if (IS_ERR_OR_NULL(superblock)) { - int result; - - pr_err("Failed to freeze device [%u:%u]\n", MAJOR(bdev->bd_dev), - MINOR(bdev->bd_dev)); - - if (superblock == NULL) - result = -ENODEV; - else { - result = PTR_ERR(superblock); - pr_err("Error code: %d\n", result); - } - return result; - } - - pr_debug("Device [%u:%u] was frozen\n", MAJOR(bdev->bd_dev), - MINOR(bdev->bd_dev)); - *psuperblock = superblock; - - return 0; -} -static inline void _thaw_bdev(struct block_device *bdev, - struct super_block *superblock) -{ - if (superblock == NULL) - return; - - if (thaw_bdev(bdev, superblock)) - pr_err("Failed to unfreeze device [%u:%u]\n", - MAJOR(bdev->bd_dev), MINOR(bdev->bd_dev)); - else - pr_debug("Device [%u:%u] was unfrozen\n", MAJOR(bdev->bd_dev), - MINOR(bdev->bd_dev)); -} -#endif -#endif /* __BLK_SNAP_TRACKER_H */ diff --git a/module/version.h b/module/version.h deleted file mode 100644 index fc9d97c9..00000000 --- a/module/version.h +++ /dev/null @@ -1,10 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef __BLK_SNAP_VERSION_H -#define __BLK_SNAP_VERSION_H - -#define VERSION_MAJOR 1 -#define VERSION_MINOR 0 -#define VERSION_REVISION 0 -#define VERSION_BUILD 0 -#define VERSION_STR "1.0.0.0" -#endif /* __BLK_SNAP_VERSION_H */ diff --git a/pkg/blksnap.dkms b/pkg/blksnap.dkms deleted file mode 100644 index 2f1830fd..00000000 --- a/pkg/blksnap.dkms +++ /dev/null @@ -1,13 +0,0 @@ -PACKAGE_VERSION="#PACKAGE_VERSION#" -PACKAGE_NAME="blksnap" -BUILT_MODULE_NAME[0]="blksnap" -BUILT_MODULE_NAME[1]="bdevfilter" -DEST_MODULE_LOCATION[0]="/kernel/drivers/block/" -DEST_MODULE_LOCATION[1]="/kernel/drivers/block/" - -MAKE[0]="make -j$(nproc) -C ${kernel_source_dir} M=${dkms_tree}/${PACKAGE_NAME}/${PACKAGE_VERSION}/build modules" -CLEAN="make -C ${kernel_source_dir} M=${dkms_tree}/${PACKAGE_NAME}/${PACKAGE_VERSION}/build clean" -STRIP[0]="no" - -AUTOINSTALL="yes" -NO_WEAK_MODULES="yes" diff --git a/pkg/build_functions.sh b/pkg/build_functions.sh deleted file mode 100644 index 68616ae6..00000000 --- a/pkg/build_functions.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -e - -generate_version() -{ - local FILEPATH=$1 - local VER_STR=$2 - local VER_MAJOR=$(cut -d "." -f1 <<< ${VER_STR}) - local VER_MINOR=$(cut -d "." -f2 <<< ${VER_STR}) - local VER_REVIS=$(cut -d "." -f3 <<< ${VER_STR}) - local VER_BUILD=$(cut -d "." -f4 <<< ${VER_STR}) - - echo "/* SPDX-License-Identifier: GPL-2.0 */" > ${FILEPATH} - echo "#pragma once" >> ${FILEPATH} - echo "#define VERSION_MAJOR ${VER_MAJOR}" >> ${FILEPATH} - echo "#define VERSION_MINOR ${VER_MINOR}" >> ${FILEPATH} - echo "#define VERSION_REVISION ${VER_REVIS}" >> ${FILEPATH} - echo "#define VERSION_BUILD ${VER_BUILD}" >> ${FILEPATH} - echo "#define VERSION_STR \"${VER_STR}\"" >> ${FILEPATH} -} diff --git a/pkg/deb/blksnap-dkms/compat b/pkg/deb/blksnap-dkms/compat deleted file mode 100644 index ec635144..00000000 --- a/pkg/deb/blksnap-dkms/compat +++ /dev/null @@ -1 +0,0 @@ -9 diff --git a/pkg/deb/blksnap-dkms/control b/pkg/deb/blksnap-dkms/control deleted file mode 100644 index 6fda584c..00000000 --- a/pkg/deb/blksnap-dkms/control +++ /dev/null @@ -1,23 +0,0 @@ -Source: blksnap-dkms -Section: kernel -Priority: optional -Maintainer: Veeam Software Group GmbH -Build-Depends: debhelper (>= 9.0.0), dh-dkms | dkms -Homepage: https://github.org/veeam/blksnap -Testsuite: autopkgtest-pkg-dkms - -Package: blksnap-dkms -Architecture: all -Depends: dkms, ${shlibs:Depends}, ${misc:Depends} -Conflicts: veeamsnap -Replaces: veeamsnap -Recommends: blksnap-tools -Suggests: blksnap-dev -Description: blksnap kernel module - This kernel module implements non-persistent snapshots of block devices - and changed block tracking functionality for backup. - The module is developed by Veeam with the condition of simply adding it - to the upstream. - Therefore, the module is divided into two parts: bdevfilter and blksnap. - bdevfilter provides the ability to intercept I/O units (bio). - The main logic is concentrated in blksnap. diff --git a/pkg/deb/blksnap-dkms/copyright b/pkg/deb/blksnap-dkms/copyright deleted file mode 100644 index 29255fa2..00000000 --- a/pkg/deb/blksnap-dkms/copyright +++ /dev/null @@ -1,22 +0,0 @@ -Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: blksnap -Source: https://github.org/veeam/blksnap - -Files: * -Copyright: 2022 Veeam Software Group GmbH -License: GPL-2.0 - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; version 2 dated June, 1991. - . - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - . - You should have received a copy of the GNU General Public License - along with this program. If not, see . - . - On Debian systems, the complete text of the GNU General Public License - Version 2 can be found in `/usr/share/common-licenses/GPL-2'. - diff --git a/pkg/deb/blksnap-dkms/postrm b/pkg/deb/blksnap-dkms/postrm deleted file mode 100755 index 6ea3dc8a..00000000 --- a/pkg/deb/blksnap-dkms/postrm +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/sh -set -e - -checkModule() -{ - if ! lsmod | grep "$1" > /dev/null - then - return 1 - fi - return 0 -} - -case "$1" in - remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear|purge) - if checkModule "blksnap" - then - rmmod blksnap || true - fi - if checkModule "bdevfilter" - then - echo 0 > /sys/kernel/livepatch/bdevfilter/enabled || true - sleep 3s - rmmod bdevfilter || true - fi - ;; - *) - echo "prerm called with unknown argument '$1'" >&2 - exit 1 - ;; -esac - -exit 0 diff --git a/pkg/deb/blksnap-dkms/rules b/pkg/deb/blksnap-dkms/rules deleted file mode 100755 index 3a4f3f74..00000000 --- a/pkg/deb/blksnap-dkms/rules +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/make -f -# -*- makefile -*- -include /usr/share/dpkg/pkg-info.mk - -PACKAGE_VERSION=$(shell grep PACKAGE_VERSION= src/dkms.conf | cut -d= -f2 | cut -d\" -f2) - -%: - dh $@ --with dkms - -override_dh_install: - dh_install src/* usr/src/blksnap-${PACKAGE_VERSION}/ - -override_dh_dkms: - dh_dkms -V ${PACKAGE_VERSION} -- src/dkms.conf - diff --git a/pkg/deb/blksnap-dkms/source/format b/pkg/deb/blksnap-dkms/source/format deleted file mode 100644 index 89ae9db8..00000000 --- a/pkg/deb/blksnap-dkms/source/format +++ /dev/null @@ -1 +0,0 @@ -3.0 (native) diff --git a/pkg/deb/blksnap/blksnap-tools.manpages b/pkg/deb/blksnap/blksnap-tools.manpages new file mode 100644 index 00000000..2409df87 --- /dev/null +++ b/pkg/deb/blksnap/blksnap-tools.manpages @@ -0,0 +1 @@ +doc/blksnap.8 diff --git a/pkg/deb/blksnap/copyright b/pkg/deb/blksnap/copyright index 7fa2f935..f6346e43 100644 --- a/pkg/deb/blksnap/copyright +++ b/pkg/deb/blksnap/copyright @@ -1,7 +1,7 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: blksnap Source: https://github.org/veeam/blksnap -Files-Excluded: module/ patches/ pkg/ +Files-Excluded: pkg/ Files: * Copyright: 2022 Veeam Software Group GmbH diff --git a/pkg/deb/build-blksnap-dkms.sh b/pkg/deb/build-blksnap-dkms.sh deleted file mode 100755 index 2b20ebe2..00000000 --- a/pkg/deb/build-blksnap-dkms.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash -e - -VERSION="$1" -if [ -n "$1" ] -then - VERSION="$1" -else - VERSION="1.0.0.0" -fi - -CURR_DIR=$(pwd) -cd "../../" - -. pkg/build_functions.sh - -BUILD_DIR="build/pkg" -rm -rf ${BUILD_DIR} -mkdir -p ${BUILD_DIR} - -# prepare module sources -mkdir -p ${BUILD_DIR}/src -cp module/*.c ${BUILD_DIR}/src/ -cp module/*.h ${BUILD_DIR}/src/ -cp module/Makefile* ${BUILD_DIR}/src/ -generate_version ${BUILD_DIR}/src/version.h ${VERSION} - -cp ./pkg/blksnap.dkms ${BUILD_DIR}/src/dkms.conf -sed -i 's/#PACKAGE_VERSION#/'${VERSION}'/g' ${BUILD_DIR}/src/dkms.conf - -# prepare other package files -cp -r ${CURR_DIR}/blksnap-dkms ${BUILD_DIR}/debian - -cat > ${BUILD_DIR}/debian/changelog << EOF -blksnap-dkms (${VERSION}) stable; urgency=low - - * Release. - -- Veeam Software Group GmbH $(date -R) -EOF - -cd ${BUILD_DIR} && dpkg-buildpackage -us -uc -b - -cd ${CURR_DIR} diff --git a/pkg/deb/build-blksnap.sh b/pkg/deb/build-blksnap.sh index 1bce6238..894831a9 100755 --- a/pkg/deb/build-blksnap.sh +++ b/pkg/deb/build-blksnap.sh @@ -4,26 +4,23 @@ if [ -n "$1" ] then VERSION="$1" else - VERSION="1.0.0.0" + VERSION="2.0.0.0" fi CURR_DIR=$(pwd) cd "../../" -ROOT_DIR=$(pwd) # prepare debian directory -rm -rf ${ROOT_DIR}/debian -cp -r ${CURR_DIR}/blksnap ${ROOT_DIR}/debian +rm -rf ./debian +cp -r ${CURR_DIR}/blksnap ./debian -cat > ${ROOT_DIR}/debian/changelog << EOF +cat > ./debian/changelog << EOF blksnap (${VERSION}) stable; urgency=low * Release. -- Veeam Software Group GmbH $(date -R) EOF -cd ${ROOT_DIR} - # build backage dpkg-buildpackage -us -uc -b diff --git a/pkg/rpm/blksnap-cumulate-rh/blksnap-loader b/pkg/rpm/blksnap-cumulate-rh/blksnap-loader deleted file mode 100644 index 14df389b..00000000 --- a/pkg/rpm/blksnap-cumulate-rh/blksnap-loader +++ /dev/null @@ -1,428 +0,0 @@ -#!/usr/bin/python3 -# -*- coding: utf-8 -*- - -""" -Special loader for blksnap precompiled kernel module -""" - -__author__ = "Veeam Software Group GmbH" -__license__ = "GPL-2" - -import os -import sys -import time -import platform - - -class CKernelVersion: - def __init__(self, str): - #common version string like "3.10.0-862.6.3.el7.x86_64.debug" - self._versionString = str - - #two halhs like "3.10.0" and "862.6.3.el7.x86_64.debug" - (self._revisionString, self._patchString) = str.split("-") - - #first half numbers collection [3, 10, 0] - nums = self._revisionString.split(".") - inx = 0 - self._revisionNumbers = [] - while inx second[inx]: - return 1 - - if firstLen < secondLen: - return -1 - elif firstLen > secondLen: - return 1 - else: - return 0 - - def __eq__(self,other): - if not isinstance(other,CKernelVersion): - return NotImplemented - - res = self._CompareNumbers(self._revisionNumbers, other._revisionNumbers) - if res == 0: - res = self._CompareNumbers(self._patchNumbers, other._patchNumbers) - if res == 0: - return True - return False - - - def __lt__(self,other): - if not isinstance(other,CKernelVersion): - return NotImplemented - - res = self._CompareNumbers(self._revisionNumbers, other._revisionNumbers) - if res == -1: - return True - elif res == 0: - res = self._CompareNumbers(self._patchNumbers, other._patchNumbers) - if res == -1: - return True - - return False - - def __le__(self,other): - if not isinstance(other,CKernelVersion): - return NotImplemented - - res = self._CompareNumbers(self._revisionNumbers, other._revisionNumbers) - if res == -1: - return True - elif res == 0: - res = self._CompareNumbers(self._patchNumbers, other._patchNumbers) - if res == -1 or res == 0: - return True - - return False - - def __gt__(self,other): - if not isinstance(other,CKernelVersion): - return NotImplemented - - res = self._CompareNumbers(self._revisionNumbers, other._revisionNumbers) - if res == 1: - return True - elif res == 0: - res = self._CompareNumbers(self._patchNumbers, other._patchNumbers) - if res == 1: - return True - - return False - - def __ge__(self,other): - if not isinstance(other,CKernelVersion): - return NotImplemented - - res = self._CompareNumbers(self._revisionNumbers, other._revisionNumbers) - if res == 1: - return True - elif res == 0: - res = self._CompareNumbers(self._patchNumbers, other._patchNumbers) - if res == 1 or res == 0: - return True - - return False - - def __str__(self): - return self._versionString - def __repr__(self): - return self._versionString - -class CKernelModule: - def __init__(self, _kernelVersion, _modulePath): - self._kernelVersion = _kernelVersion; - self._modulePath = _modulePath - -def ExecuteCommand(cmd): - print("Execute ["+cmd+"]") - sys.stdout.flush() - result = os.system(cmd) - if result != 0: - raise RuntimeError("Failed to execute ["+cmd+"]") - -def TryExecuteCommand(cmd): - print("Try execute ["+cmd+"]") - sys.stdout.flush() - result = os.system(cmd) - -def EnumerateAvaildableKernels(moduleNames): - modulesList = [] - - path = "/lib/modules" - entries = os.listdir(path) - for dirName in entries: - fullDirPath = os.path.join(os.path.join(path, dirName), "extra") - - if not os.path.isdir(fullDirPath): - continue - - found = True - for moduleName in moduleNames: - if not os.path.exists(os.path.join(fullDirPath, moduleName+".ko")): - found = False - break - - if found: - modulesList.append(CKernelModule(CKernelVersion(dirName), fullDirPath)) - - return modulesList; - -def FindSameReleaseModule(kver, moduleNames): - if not isinstance(kver, CKernelVersion): - return NotImplemented - - modulesList = EnumerateAvaildableKernels(moduleNames) - modulesList = sorted(modulesList, key=lambda CKernelModule: CKernelModule._kernelVersion, reverse=False) - - print("Kmod binaries were found:") - for module in modulesList: - print("\t"+str(module._modulePath)) - - for module in modulesList: - if kver.IsSameRelease(module._kernelVersion): - return module._modulePath - - return None - -def FindSuitableModule(strFoundVersion, moduleNames): - foundVersion = CKernelVersion(strFoundVersion) - print("Linux kernel found:"+str(foundVersion)) - - modulesList = EnumerateAvaildableKernels(moduleNames) - modulesList = sorted(modulesList, key=lambda CKernelModule: CKernelModule._kernelVersion, reverse=True) - - print("Blksnap kmod binaries were found:") - for module in modulesList: - print("\t"+str(module._modulePath)) - - for module in modulesList: - if module._kernelVersion <= foundVersion: - return module._modulePath - - return None - -def CheckModuleExist(modulePath): - if not os.path.isfile(modulePath): - raise RuntimeError("Module [{0}] is not exist".format(modulePath)) - -def TryUnloadModules(): - if os.path.exists("/sys/class/blksnap"): - try: - ExecuteCommand("rmmod blksnap") - except Exception as ex: - print("Failed to unload blksnap. {0}".format(ex)) - return False - - if os.path.exists("/sys/kernel/livepatch/bdevfilter"): - try: - ExecuteCommand("echo 0 > /sys/kernel/livepatch/bdevfilter/enabled") - except Exception as ex: - print("Failed to disable bdevfilter. {0}".format(ex)) - return False - - cnt = 0 - while os.path.exists("/sys/kernel/livepatch/bdevfilter"): - time.sleep(1) - cnt+=1 - if cnt > 10: - print("bdevfilter cannot be disabled.") - return False - - TryExecuteCommand("rmmod bdevfilter") - - return True - -def TryLoadBdevFilter(modulePath): - modulePath = modulePath+'/'+"bdevfilter.ko" - - CheckModuleExist(modulePath) - - if os.path.isfile("/sys/kernel/livepatch/bdevfilter/enabled"): - UnloadModule(); - - cmd = "insmod " + modulePath - for param in params: - cmd += " " - cmd += param - - ExecuteCommand(cmd) - -def TryLoadBlksnap(modulePath, params): - modulePath = modulePath+'/'+"blksnap.ko" - - CheckModuleExist(modulePath) - - cmd = "insmod " + modulePath - for param in params: - cmd += " " - cmd += param - - ExecuteCommand(cmd) - -def ModInfo(modulePath): - CheckModuleExist(modulePath) - - ExecuteCommand("modinfo \""+modulePath+"\"") - - -def TryProcess(modulePath, params, isCheck, isModinfo): - if isCheck: - CheckModuleExist(modulePath+'/'+"blksnap.ko") - elif isModinfo: - ModInfo(modulePath+'/'+"blksnap.ko") - else: - TryUnloadModules() - TryLoadBdevFilter(modulePath) - TryLoadBlksnap(modulePath, params) - -def dist_os_release(): - d = {} - with open("/etc/os-release") as file: - for line in file: - stripped = line.rstrip() - if stripped: - key, val = stripped.split("=") - d[key] = val - return d["ID"].strip("\""), d["VERSION_ID"].strip("\"") - -def Usage(): - usageText = "Usage:\n" - usageText += "Load a compatible blksnap kernel module for the current kernel.\n" - usageText += "\n" - usageText += "\t--help, -h - Show this usage.\n" - usageText += "\t--check - Checks the existence of a compatible kernel module for the current kernel.\n" - usageText += "\t--modinfo - Call 'modinfo' for a compatible kernel module.\n" - usageText += "\t--kmodAnyKernel - Try to find a module with a suitable release. In this case the system kernel may be incompatible with the module.\n" - usageText += "\t--unload - Try to unload kernel module.\n" - print(usageText); - -def main(params): - isCheck = False; - isModinfo = False; - isKmodAnyKernel = False; - - try: - # Process parameters - isParameter = True; - while isParameter and (len(params) > 0): - if params[0] == "--help" or params[0] == "-h": - Usage() - return 0 - if params[0] == "--check": - isCheck = True; - isKmodAnyKernel = True; - elif params[0] == "--modinfo": - isModinfo = True; - isKmodAnyKernel = True; - elif params[0] == "--kmodAnyKernel": - isKmodAnyKernel = True; - elif params[0] == "--unload": - TryUnloadModules() - return 0 - else: - isParameter = False; - - if isParameter: - del params[0] - - if (platform.system() != "Linux"): - raise RuntimeError("Found unsupported platform [{0}]".format(platform.system()) ) - - distName, distVersion = dist_os_release(); - unameKernelVersion = platform.uname()[2] - - if not distName in ["oracle", "ol", "redhat", "rhel", "centos"]: - raise RuntimeError("Found unsupported distribution [{0}]".format(distName) ) - - distVersionWords = distVersion.split(".") - if distVersionWords[0] < "6": - raise RuntimeError("Found unsupported platform version [{0}]".format(distVersion) ) - - kver = CKernelVersion(unameKernelVersion) - - #try find direct module - strAccurateKernelVersion = kver.GetAccurateVersion() - kmodPath = "/lib/modules/"+strAccurateKernelVersion+"/extra" - print("Accurate kernel version: "+strAccurateKernelVersion) - try: - TryProcess(kmodPath, params, isCheck, isModinfo) - return 0 - except Exception as ex: - print("Failed to load module for accurate kernel version [{0}]".format(strAccurateKernelVersion) ) - - #try find module with same release - if kver.IsUek(): - print("Skip an attempt to load a same release kernel for UEK") - else: - kmodPath = FindSameReleaseModule(kver, ["blksnap", "bdevfilter"]) - if kmodPath is None: - print("Failed to find same release modules for kernel [{0}]".format(strAccurateKernelVersion) ) - else: - print("Same release kernel module path: "+kmodPath) - try: - TryProcess(kmodPath, params, isCheck, isModinfo) - return 0; - except Exception as ex: - print("Failed to load module for same release kernel version") - - if not isKmodAnyKernel: - return -1 - - kmodPath = FindSuitableModule(strAccurateKernelVersion, ["blksnap", "bdevfilter"]) - if kmodPath is None: - print("Failed to find suitable module for kernel [{0}]".format(strAccurateKernelVersion) ) - return -1 - print("Suitable kernel module path: "+kmodPath) - try: - TryProcess(kmodPath, params, isCheck, isModinfo) - return 1 # This mean that the module was loaded, but the system kernel may be incompatible with the module - - except Exception as ex: - print("Failed to find suitable kernel module. {0}".format(ex)) - return -1 - except: - print("Unexpected error:"+sys.exc_info()[0]) - raise - -params = sys.argv -del params[0] -result = main(params) -exit(result) diff --git a/pkg/rpm/blksnap-cumulate-rh/blksnap.spec b/pkg/rpm/blksnap-cumulate-rh/blksnap.spec deleted file mode 100644 index 8c569e24..00000000 --- a/pkg/rpm/blksnap-cumulate-rh/blksnap.spec +++ /dev/null @@ -1,74 +0,0 @@ -Name: blksnap -Version: #PACKAGE_VERSION# -Release: 1%{?dist} -Packager: "#PACKAGE_VENDOR#" -Vendor: "#PACKAGE_VENDOR#" -Group: System Environment/Kernel -License: GPL-2 -Summary: Block device snapshot (kernel module) -URL: http://github.com/veeam/blksnap -ExclusiveOS: linux -ExclusiveArch: %{ix86} x86_64 - -Source0: %{name}-%{version}.tar.gz -Source1: %{name}-loader - -BuildRoot: %{_tmppath}/%{name}-buildroot - -%description -This package provides the blksnap kernel module. -It is built to depend upon the specific ABI provided by a range of releases -of the same variant of the Linux kernel and not on any one specific build. - -%package -n kmod-%{name} -Summary: %{name} kernel module -Group: System Environment/Kernel -Provides: %{name} = %{version} -Requires: python3 -Conflicts: veeamsnap - -%description -n kmod-%{name} -This package provides the ${kmod_name} kernel modules built for the Linux -kernels %{kversion} for the %{_target_cpu} family of processors. - -# Disable the building of the debug package(s). -%define debug_package %{nil} - -%prep -%setup -q -n %{name}-%{version} - -%build -for kver in %{kversion}; do - KSRC=%{_usrsrc}/kernels/${kver} - - mkdir -p $PWD/${kver} - cp -rf $PWD/module/ $PWD/${kver}/ - %{__make} -j$(nproc) -C "${KSRC}" %{?_smp_mflags} modules M=$PWD/${kver}/module -done - -for kver in %{kversion}; do - KSRC=%{_usrsrc}/kernels/${kver} - - export INSTALL_MOD_PATH=%{buildroot} - export INSTALL_MOD_DIR=extra - - %{__make} -C "${KSRC}" modules_install M=$PWD/${kver}/module - - %{__rm} -f %{buildroot}/lib/modules/${kver}/modules.* -done -%{__install} -d %{buildroot}/usr/sbin/ -%{__install} -m 744 %{SOURCE1} %{buildroot}/usr/sbin/ - -%files -n kmod-%{name} -%defattr(644,root,root,755) -/lib/modules/ -/usr/sbin/%{name}-loader -%attr(744,root,root) /usr/sbin/%{name}-loader - -%clean -%{__rm} -rf %{buildroot} - -%preun -n kmod-%{name} -/usr/sbin/%{name}-loader --unload - -%changelog diff --git a/pkg/rpm/blksnap-cumulate-rh/build.sh b/pkg/rpm/blksnap-cumulate-rh/build.sh deleted file mode 100755 index 47972373..00000000 --- a/pkg/rpm/blksnap-cumulate-rh/build.sh +++ /dev/null @@ -1,87 +0,0 @@ -#!/bin/bash -e - -. /etc/os-release -PACKAGE_RELEASE=${ID/-/_}${VERSION_ID/-/_} - -PACKAGE_NAME="blksnap" - -PACKAGE_VERSION="$1" -PACKAGE_VENDOR="$2" - -if [[ -z "${PACKAGE_VERSION}" ]] -then - echo >&2 "Version parameters is not set." - exit -fi -if [[ -z "${PACKAGE_VENDOR}" ]] -then - echo >&2 "Vendor parameters is not set." - PACKAGE_VENDOR="Veeam Software Group GmbH" -fi - -hash rpmbuild 2>/dev/null || { echo >&2 "The 'rpmbuild' package is required."; exit; } - -set -e - -PROJECT_DIR=$(pwd) -cd "../../../" - -. ./pkg/build_functions.sh - -BUILD_DIR="${HOME}/rpmbuild" -# rpmdev-setuptree -for DIR in BUILD BUILDROOT OTHER RPMS SOURCES SPECS SRPMS -do - mkdir -p ${BUILD_DIR}/${DIR} -done - -# prepare module sources -rm -rf ${BUILD_DIR}/SOURCES/* -# copy '-loader' file -cp ${PROJECT_DIR}/${PACKAGE_NAME}-loader ${BUILD_DIR}/SOURCES/${PACKAGE_NAME}-loader - -# generate module sources tarbal -SRC_DIR=${BUILD_DIR}/SOURCES/${PACKAGE_NAME}-${PACKAGE_VERSION}/module -mkdir -p ${SRC_DIR} -cp ./module/*.c ${SRC_DIR}/ -cp ./module/*.h ${SRC_DIR}/ -cp ./module/Makefile* ${SRC_DIR}/ -generate_version ${SRC_DIR}/version.h ${PACKAGE_VERSION} - -CURR_DIR=$(pwd) -cd ${BUILD_DIR}/SOURCES -tar --format=gnu -zcf ${PACKAGE_NAME}-${PACKAGE_VERSION}.tar.gz ./${PACKAGE_NAME}-${PACKAGE_VERSION} -rm -rf ./${PACKAGE_NAME}-${PACKAGE_VERSION} -cd ${CURR_DIR} -echo "SOURCES:" -ls ${BUILD_DIR}/SOURCES/ - -# prepare spec files -rm -rf ${BUILD_DIR}/SPECS/* -SPECFILE=${BUILD_DIR}/SPECS/${PACKAGE_NAME}.spec -cp ${PROJECT_DIR}/${PACKAGE_NAME}.spec ${SPECFILE} -chmod +w ${SPECFILE} -sed -i "s/#PACKAGE_VERSION#/${PACKAGE_VERSION}/g; s/#PACKAGE_VENDOR#/${PACKAGE_VENDOR}/g" ${SPECFILE} -echo " " >> ${SPECFILE} -echo "* $(date +'%a %b %d %Y') "$(whoami) >> ${SPECFILE} -echo "- Build ${PKG_VER}" >> ${SPECFILE} -echo "SPECS:" -ls ${BUILD_DIR}/SPECS - -KVERSION="" -for KERN in $(ls /usr/src/kernels/) -do - if [[ -d "/usr/src/kernels/${KERN}" ]] - then - if [[ -z ${KVERSION} ]] - then - KVERSION="${KERN}" - else - KVERSION+=" ${KERN}" - fi - fi -done - -cd ${BUILD_DIR} -rpmbuild --ba SPECS/${PACKAGE_NAME}.spec --define="kversion ${KVERSION}" -cd ${PROJECT_DIR} diff --git a/pkg/rpm/blksnap-dkms-rh/blksnap.spec b/pkg/rpm/blksnap-dkms-rh/blksnap.spec deleted file mode 100644 index 550273fb..00000000 --- a/pkg/rpm/blksnap-dkms-rh/blksnap.spec +++ /dev/null @@ -1,86 +0,0 @@ -Name: blksnap -Version: #PACKAGE_VERSION# -Release: 1 -BuildArch: noarch -Summary: Veeam Agent for Linux (kernel module) -Packager: Veeam Software Group GmbH -Vendor: Veeam Software Group GmbH -Group: System Environment/Kernel -License: GPL-2.0 -AutoReqProv: no -Requires: dkms -Requires: gcc, make, perl -Requires: kernel-devel -Provides: %{name} = %{version} -Conflicts: kmod-%{name} -Conflicts: veeamsnap - -%description -This kernel module implements snapshot and changed block tracking -functionality used by Veeam Agent for Linux - simple and FREE backup agent -designed to ensure the Availability of your Linux server instances, whether -they reside in the public cloud or on premises. - -%post -for POSTINST in /usr/lib/dkms/common.postinst; do - if [ -f $POSTINST ]; then - $POSTINST %{name} %{version} - RETVAL=$? - if [ -z "$(dkms status -m %{name} -v %{version} -k $(uname -r) | grep 'installed')" ] ; then - echo "WARNING: Package not configured! See output!" - exit 1 - fi - exit $RETVAL - fi - echo "WARNING: $POSTINST does not exist." -done -echo -e "ERROR: DKMS version is too old and %{name} was not" -echo -e "built with legacy DKMS support." -echo -e "You must either rebuild %{name} with legacy postinst" -echo -e "support or upgrade DKMS to a more current version." -exit 1 - -%postun -checkModule() -{ - if ! lsmod | grep "$1" > /dev/null - then - return 1 - fi - return 0 -} - -if checkModule %{name} -then - modprobe -r %{name} 2>/dev/null || true -fi - -if checkModule bdevfilter -then - if [ -e /sys/kernel/livepatch/bdevfilter/enabled ] - then - echo 0 > /sys/kernel/livepatch/bdevfilter/enabled || true - while [ -e /sys/kernel/livepatch/bdevfilter/enabled ] - do - sleep 1s - done - fi - modprobe -r bdevfilter 2>/dev/null || true -fi -exit 0 - -%preun -if [ "$(dkms status -m %{name} -v %{version})" ]; then - dkms remove -m %{name} -v %{version} --all -fi -exit 0 - -%files -/usr/src/%{name}-%{version}/* - -#build definition -%define _rpmdir ./ - -%changelog -* Fri Dec 1 2021 Veeam Software GmbH veeam_team@veeam.com - #PACKAGE_VERSION# -- First release diff --git a/pkg/rpm/blksnap-dkms-rh/build.sh b/pkg/rpm/blksnap-dkms-rh/build.sh deleted file mode 100755 index c9f0beb2..00000000 --- a/pkg/rpm/blksnap-dkms-rh/build.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash -e - -VERSION="$1" -if [ -n "$1" ] -then - VERSION="$1" -else - VERSION="1.0.0.0" -fi - -CURR_DIR=$(pwd) -cd "../../../" - -. ./pkg/build_functions.sh - -BUILD_DIR=$(pwd)"/build/pkg" -rm -rf ${BUILD_DIR}/ -mkdir -p ${BUILD_DIR}/ - -# copy spec -cp ${CURR_DIR}/blksnap.spec ${BUILD_DIR}/ - -# prepare content -CONTENT_DIR="${BUILD_DIR}/content" -mkdir -p ${CONTENT_DIR} - -# prepare module sources -SRC_DIR=${CONTENT_DIR}/usr/src/blksnap-${VERSION} -mkdir -p ${SRC_DIR} -cp ./module/*.c ${SRC_DIR}/ -cp ./module/*.h ${SRC_DIR}/ -cp ./module/Makefile* ${SRC_DIR}/ -generate_version ${SRC_DIR}/version.h ${VERSION} -cp ./pkg/blksnap.dkms ${SRC_DIR}/dkms.conf - -# set package version -find ${BUILD_DIR} -type f -exec sed -i 's/#PACKAGE_VERSION#/'${VERSION}'/g' {} + - -cd ${BUILD_DIR} -HOME=${BUILD_DIR} fakeroot rpmbuild -bb --buildroot ${CONTENT_DIR} --nodeps blksnap.spec -mv ./noarch/* ../ - -cd ${CURR_DIR} diff --git a/pkg/rpm/blksnap-dkms-suse/blksnap.spec b/pkg/rpm/blksnap-dkms-suse/blksnap.spec deleted file mode 100644 index 6fee4e4f..00000000 --- a/pkg/rpm/blksnap-dkms-suse/blksnap.spec +++ /dev/null @@ -1,84 +0,0 @@ -Name: blksnap -Version: #PACKAGE_VERSION# -Release: sle -BuildArch: noarch -Summary: Veeam Agent for Linux (kernel module) -Packager: Veeam Software Group GmbH -Vendor: Veeam Software Group GmbH -Group: System Environment/Kernel -License: GPL-2.0 -AutoReqProv: no -Requires: dkms -Requires: gcc, make, perl -Requires: kernel-default-devel -Provides: %{name} = %{version} -# for compatibility with Veeam Agent for Linux -Obsoletes: %{name}-kmp < %{version} -Obsoletes: %{name} < %{version} -Conflicts: veeamsnap - -%description -This kernel module implements snapshot and changed block tracking -functionality used by Veeam Agent for Linux - simple and FREE backup agent -designed to ensure the Availability of your Linux server instances, whether -they reside in the public cloud or on premises. - -%post -for POSTINST in /usr/lib/dkms/common.postinst; do - if [ -f $POSTINST ]; then - $POSTINST %{name} %{version} - RETVAL=$? - if [ -z "$(dkms status -m %{name} -v %{version} -k $(uname -r) | grep 'installed')" ] ; then - echo "WARNING: Package not configured! See output!" - exit 1 - fi - exit $RETVAL - fi - echo "WARNING: $POSTINST does not exist." -done -echo -e "ERROR: DKMS version is too old and %{name} was not" -echo -e "built with legacy DKMS support." -echo -e "You must either rebuild %{name} with legacy postinst" -echo -e "support or upgrade DKMS to a more current version." -exit 1 - -%postun -checkModule() -{ - if ! lsmod | grep "$1" > /dev/null - then - return 1 - fi - return 0 -} - -if checkModule %{name} -then - modprobe -r %{name} 2>/dev/null || true -fi - -if checkModule bdevfilter -then - if [ -e /sys/kernel/livepatch/bdevfilter/enabled ] - then - echo 0 > /sys/kernel/livepatch/bdevfilter/enabled || true - while [ -e /sys/kernel/livepatch/bdevfilter/enabled ] - do - sleep 1s - done - fi - modprobe -r bdevfilter 2>/dev/null || true -fi -exit 0 - -%preun -if [ "$(dkms status -m %{name} -v %{version})" ]; then - dkms remove -m %{name} -v %{version} --all -fi -exit 0 - -%files -/usr/src/%{name}-%{version}/* - -#build definition -%define _rpmdir ./ diff --git a/pkg/rpm/blksnap-dkms-suse/build.sh b/pkg/rpm/blksnap-dkms-suse/build.sh deleted file mode 100755 index c9f0beb2..00000000 --- a/pkg/rpm/blksnap-dkms-suse/build.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash -e - -VERSION="$1" -if [ -n "$1" ] -then - VERSION="$1" -else - VERSION="1.0.0.0" -fi - -CURR_DIR=$(pwd) -cd "../../../" - -. ./pkg/build_functions.sh - -BUILD_DIR=$(pwd)"/build/pkg" -rm -rf ${BUILD_DIR}/ -mkdir -p ${BUILD_DIR}/ - -# copy spec -cp ${CURR_DIR}/blksnap.spec ${BUILD_DIR}/ - -# prepare content -CONTENT_DIR="${BUILD_DIR}/content" -mkdir -p ${CONTENT_DIR} - -# prepare module sources -SRC_DIR=${CONTENT_DIR}/usr/src/blksnap-${VERSION} -mkdir -p ${SRC_DIR} -cp ./module/*.c ${SRC_DIR}/ -cp ./module/*.h ${SRC_DIR}/ -cp ./module/Makefile* ${SRC_DIR}/ -generate_version ${SRC_DIR}/version.h ${VERSION} -cp ./pkg/blksnap.dkms ${SRC_DIR}/dkms.conf - -# set package version -find ${BUILD_DIR} -type f -exec sed -i 's/#PACKAGE_VERSION#/'${VERSION}'/g' {} + - -cd ${BUILD_DIR} -HOME=${BUILD_DIR} fakeroot rpmbuild -bb --buildroot ${CONTENT_DIR} --nodeps blksnap.spec -mv ./noarch/* ../ - -cd ${CURR_DIR} diff --git a/pkg/rpm/blksnap-kmp-suse/blksnap-preamble b/pkg/rpm/blksnap-kmp-suse/blksnap-preamble deleted file mode 100644 index 2b374799..00000000 --- a/pkg/rpm/blksnap-kmp-suse/blksnap-preamble +++ /dev/null @@ -1,7 +0,0 @@ -Requires: kernel-%1 -Provides: %{name} = %version -Provides: %{name}-kmp = %version -Obsoletes: %{name}-kmp-%1 < %version -Obsoletes: %{name}-kmp < %version -Obsoletes: %{name} < %version -Conflicts: veeamsnap diff --git a/pkg/rpm/blksnap-kmp-suse/blksnap-unload.sh b/pkg/rpm/blksnap-kmp-suse/blksnap-unload.sh deleted file mode 100644 index f9badc7c..00000000 --- a/pkg/rpm/blksnap-kmp-suse/blksnap-unload.sh +++ /dev/null @@ -1,26 +0,0 @@ -checkModule() -{ - if ! lsmod | grep "$1" > /dev/null - then - return 1 - fi - return 0 -} - -if checkModule blksnap -then - modprobe -r blksnap || true -fi - -if checkModule bdevfilter -then - if [ -e /sys/kernel/livepatch/bdevfilter/enabled ] - then - echo 0 > /sys/kernel/livepatch/bdevfilter/enabled || true - while [ -e /sys/kernel/livepatch/bdevfilter/enabled ] - do - sleep 1s - done - fi - modprobe -r bdevfilter || true -fi diff --git a/pkg/rpm/blksnap-kmp-suse/blksnap.spec b/pkg/rpm/blksnap-kmp-suse/blksnap.spec deleted file mode 100644 index d972ded9..00000000 --- a/pkg/rpm/blksnap-kmp-suse/blksnap.spec +++ /dev/null @@ -1,53 +0,0 @@ -Name: blksnap -BuildRequires: kernel-syms modutils -License: GPL-2.0 -Packager: "#PACKAGE_VENDOR#" -Vendor: "#PACKAGE_VENDOR#" -Group: System/Kernel -Summary: Block device snapshot (kernel module) -Version: #PACKAGE_VERSION# -Release: #PACKAGE_RELEASE# -Source0: %{name}-%{version}.tar.gz -Source10: %{name}-%{version}-preamble -Source20: kernel-module-subpackage -BuildRoot: %{_tmppath}/%{name}-%{version}-build -URL: http://github.com/veeam/%{name} -Requires: modutils suse-module-tools - -%kernel_module_package -n %{name} -p %{_sourcedir}/%{name}-%{version}-preamble -t %{_sourcedir}/kernel-module-subpackage - -%description -This kernel module implements snapshot and changed block tracking -functionality. - -%package KMP -Summary: Block device snapshot (binary kernel module package) -Group: System/Kernel - -%description KMP -This kernel module implements snapshot and changed block tracking -functionality. - -%prep -%setup -q -n %{name}-%{version} -mkdir obj - -%build -for flavor in %flavors_to_build; do - rm -rf obj/$flavor - cp -r source obj/$flavor - %{__make} -C %{kernel_source $flavor} modules M=$PWD/obj/$flavor -done - -%install -export INSTALL_MOD_PATH=$RPM_BUILD_ROOT -export INSTALL_MOD_DIR=updates/drivers -export INSTALL_MOD_STRIP=--strip-debug -for flavor in %flavors_to_build; do - make -C %{kernel_source $flavor} modules_install M=$PWD/obj/$flavor -done - -%clean -rm -rf %{buildroot} - -%changelog diff --git a/pkg/rpm/blksnap-kmp-suse/build.sh b/pkg/rpm/blksnap-kmp-suse/build.sh deleted file mode 100755 index a77eeb6f..00000000 --- a/pkg/rpm/blksnap-kmp-suse/build.sh +++ /dev/null @@ -1,77 +0,0 @@ -#!/bin/bash - -. /etc/os-release -PACKAGE_RELEASE=${ID/-/_}${VERSION_ID/-/_} - -PACKAGE_NAME="blksnap" -#VERSION="1.0.0.0" -PACKAGE_VERSION="$1" -PACKAGE_VENDOR="Veeam Software Group GmbH" -#PACKAGE_VENDOR="$2" -if [ -z ${PACKAGE_VERSION} ] -then - echo >&2 "Version parameters is not set." - exit -fi -if [ -z ${PACKAGE_VENDOR} ] -then - echo >&2 "Vendor parameters is not set." - exit -fi - -hash rpmbuild 2>/dev/null || { echo >&2 "The 'rpmbuild' package is required."; exit; } - -set -e - -PROJECT_DIR=$(pwd) -cd "../../../" - -. ./pkg/build_functions.sh - -BUILD_DIR="${HOME}/rpmbuild" -# rpmdev-setuptree -for DIR in BUILD BUILDROOT OTHER RPMS SOURCES SPECS SRPMS -do - mkdir -p ${BUILD_DIR}/${DIR} -done - -# prepare module sources -rm -rf ${BUILD_DIR}/SOURCES/* -# copy '-preamble' file -cp ${PROJECT_DIR}/${PACKAGE_NAME}-preamble ${BUILD_DIR}/SOURCES/${PACKAGE_NAME}-${PACKAGE_VERSION}-preamble -# Generate own kernel-module-subpackage -# The package build system for SUSE does not allow to add own scriptlets for -# the kmp package uninstall script. So, I have to patch an existing one. -# If you know a better way, let me know. -cp /usr/lib/rpm/kernel-module-subpackage ${BUILD_DIR}/SOURCES/kernel-module-subpackage -cat ${PROJECT_DIR}/${PACKAGE_NAME}-unload.sh | sed -i '/^%preun/ r /dev/stdin' ${BUILD_DIR}/SOURCES/kernel-module-subpackage -# generate module sources tarbal -SRC_DIR=${BUILD_DIR}/SOURCES/${PACKAGE_NAME}-${PACKAGE_VERSION}/source -mkdir -p ${SRC_DIR} -cp ./module/*.c ${SRC_DIR}/ -cp ./module/*.h ${SRC_DIR}/ -cp ./module/Makefile* ${SRC_DIR}/ -generate_version ${SRC_DIR}/version.h ${PACKAGE_VERSION} -CURR_DIR=$(pwd) -cd ${BUILD_DIR}/SOURCES -tar --format=gnu -zcf ${PACKAGE_NAME}-${PACKAGE_VERSION}.tar.gz ./${PACKAGE_NAME}-${PACKAGE_VERSION} -rm -rf ./${PACKAGE_NAME}-${PACKAGE_VERSION} -cd ${CURR_DIR} -echo "SOURCES:" -ls ${BUILD_DIR}/SOURCES/ - -# prepare spec files -rm -rf ${BUILD_DIR}/SPECS/* -SPECFILE=${BUILD_DIR}/SPECS/${PACKAGE_NAME}.spec -cp ${PROJECT_DIR}/${PACKAGE_NAME}.spec ${SPECFILE} -chmod +w ${SPECFILE} -sed -i "s/#PACKAGE_VERSION#/${PACKAGE_VERSION}/g; s/#PACKAGE_VENDOR#/${PACKAGE_VENDOR}/g; s/#PACKAGE_RELEASE#/${PACKAGE_RELEASE}/g" ${SPECFILE} -echo " " >> ${SPECFILE} -echo "* $(date +'%a %b %d %Y') "$(whoami) >> ${SPECFILE} -echo "- Build ${PKG_VER}" >> ${SPECFILE} -echo "SPECS:" -ls ${BUILD_DIR}/SPECS - -cd ${BUILD_DIR} -rpmbuild --ba SPECS/${PACKAGE_NAME}.spec -cd ${PROJECT_DIR} diff --git a/pkg/rpm/blksnap-kmp-suse/sign.sh b/pkg/rpm/blksnap-kmp-suse/sign.sh deleted file mode 100755 index 2306ef14..00000000 --- a/pkg/rpm/blksnap-kmp-suse/sign.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -e - -PROJECT_DIR=$(pwd) -BUILD_DIR="${HOME}/rpmbuild" - -# signing only if private key exist in current directory -if [ not -f ${HOME}/cert/signing_key.pem ] -then - echo "The directory '${HOME}/cert/' must contain a public 'signing_key.x509' and private 'signing_key.pem' keys." - exit 1; -fi - -cd ${BUILD_DIR} -for PKG in ./RPMS/*/blksnap-kmp*.rpm -do - modsign-repackage -c ${HOME}/cert/signing_key.x509 -k ${HOME}/cert/signing_key.pem ${PKG} -done -cd ${PROJECT_DIR} -exit 0 diff --git a/tests/1000-simple.sh b/tests/1000-simple.sh index e4c59272..39e65a0d 100755 --- a/tests/1000-simple.sh +++ b/tests/1000-simple.sh @@ -13,17 +13,17 @@ echo "Diff storage directory ${DIFF_STORAGE_DIR}" . ./functions.sh . ./blksnap.sh +BLOCK_SIZE=$(block_size_mnt ${DIFF_STORAGE_DIR}) echo "---" echo "Simple test start" -modprobe blksnap -sleep 2s +blksnap_load # check module is ready blksnap_version -TESTDIR=/tmp/blksnap-test +TESTDIR=${HOME}/blksnap-test rm -rf ${TESTDIR} mkdir -p ${TESTDIR} @@ -31,13 +31,14 @@ MPDIR=/mnt/blksnap-test rm -rf ${MPDIR} mkdir -p ${MPDIR} +DIFF_STORAGE="${DIFF_STORAGE_DIR}/diff_storage" +fallocate --length 4KiB ${DIFF_STORAGE} # create first device IMAGEFILE_1=${TESTDIR}/simple_1.img imagefile_make ${IMAGEFILE_1} 64 -echo "new image file ${IMAGEFILE_1}" -DEVICE_1=$(loop_device_attach ${IMAGEFILE_1}) +DEVICE_1=$(loop_device_attach ${IMAGEFILE_1} ${BLOCK_SIZE}) echo "new device ${DEVICE_1}" MOUNTPOINT_1=${MPDIR}/simple_1 @@ -47,38 +48,29 @@ mount ${DEVICE_1} ${MOUNTPOINT_1} # create second device IMAGEFILE_2=${TESTDIR}/simple_2.img imagefile_make ${IMAGEFILE_2} 128 -echo "new image file ${IMAGEFILE_2}" -DEVICE_2=$(loop_device_attach ${IMAGEFILE_2}) +DEVICE_2=$(loop_device_attach ${IMAGEFILE_2} ${BLOCK_SIZE}) echo "new device ${DEVICE_2}" MOUNTPOINT_2=${MPDIR}/simple_2 mkdir -p ${MOUNTPOINT_2} mount ${DEVICE_2} ${MOUNTPOINT_2} -generate_files ${MOUNTPOINT_1} "before" 9 +generate_files_direct ${MOUNTPOINT_1} "before" 9 drop_cache -echo "Block device prepared, press ..." +#echo "Block device prepared, press ..." #read -n 1 -blksnap_snapshot_create "${DEVICE_1} ${DEVICE_2}" - -DIFF_STORAGE=${DIFF_STORAGE_DIR}/diff_storage0 -rm -f ${DIFF_STORAGE} -fallocate --length 1GiB ${DIFF_STORAGE} -blksnap_snapshot_append ${DIFF_STORAGE} - +blksnap_snapshot_create "${DEVICE_1} ${DEVICE_2}" "${DIFF_STORAGE}" "2G" blksnap_snapshot_take -echo "Snapshot was token, press ..." -read -n 1 - -blksnap_snapshot_collect_all +#echo "Snapshot was token, press ..." +#read -n 1 -echo "Write to original" #echo "Write something" > ${MOUNTPOINT_1}/something.txt -generate_files ${MOUNTPOINT_1} "after" 3 +echo "Write to original" +generate_files_direct ${MOUNTPOINT_1} "after" 3 drop_cache check_files ${MOUNTPOINT_1} @@ -88,26 +80,28 @@ DEVICE_IMAGE_1=$(blksnap_get_image ${DEVICE_1}) IMAGE_1=${TESTDIR}/image0 mkdir -p ${IMAGE_1} mount ${DEVICE_IMAGE_1} ${IMAGE_1} +#echo "pause, press ..." +#read -n 1 check_files ${IMAGE_1} echo "Write to snapshot" -generate_files ${IMAGE_1} "snapshot" 3 +generate_files_direct ${IMAGE_1} "snapshot" 3 drop_cache umount ${DEVICE_IMAGE_1} mount ${DEVICE_IMAGE_1} ${IMAGE_1} +#echo "pause, press ..." +#read -n 1 check_files ${IMAGE_1} umount ${IMAGE_1} blksnap_snapshot_destroy -echo "Destroy snapshot, press ..." +#echo "Destroy snapshot, press ..." #read -n 1 -rm ${DIFF_STORAGE} - drop_cache umount ${DEVICE_1} mount ${DEVICE_1} ${MOUNTPOINT_1} @@ -115,20 +109,18 @@ mount ${DEVICE_1} ${MOUNTPOINT_1} check_files ${MOUNTPOINT_1} echo "Destroy second device" +blksnap_detach ${DEVICE_2} umount ${MOUNTPOINT_2} loop_device_detach ${DEVICE_2} imagefile_cleanup ${IMAGEFILE_2} echo "Destroy first device" +blksnap_detach ${DEVICE_1} umount ${MOUNTPOINT_1} loop_device_detach ${DEVICE_1} imagefile_cleanup ${IMAGEFILE_1} -echo "Tracking device info:" -blksnap_tracker_collect - -echo "Unload module" -modprobe -r blksnap +blksnap_unload echo "Simple test finish" echo "---" diff --git a/tests/1100-loop_diff_storage.sh b/tests/1100-loop_diff_storage.sh new file mode 100755 index 00000000..2c439c2c --- /dev/null +++ b/tests/1100-loop_diff_storage.sh @@ -0,0 +1,110 @@ +#!/bin/bash -e +# +# SPDX-License-Identifier: GPL-2.0+ + +. ./functions.sh +. ./blksnap.sh + +BLk_SZ=128 + +echo "---" +echo "Simple test start" + +# diff_storage_minimum=65536 - set 64 K sectors, it's 32MiB diff_storage portion size +blksnap_load "diff_storage_minimum=65536" + +# check module is ready +blksnap_version + +TESTDIR=${HOME}/blksnap-test +rm -rf ${TESTDIR} +mkdir -p ${TESTDIR} + +MPDIR=/mnt/blksnap-test +rm -rf ${MPDIR} +mkdir -p ${MPDIR} + +# create first device +DIFF_STOGAGE_FILE=${TESTDIR}/diff_storage.img +imagefile_make ${DIFF_STOGAGE_FILE} ${BLk_SZ} + +DIFF_STOGAGE_DEVICE=$(loop_device_attach ${DIFF_STOGAGE_FILE}) +echo "new device ${DIFF_STOGAGE_DEVICE}" + +# create first device +IMAGEFILE_1=${TESTDIR}/simple_1.img +imagefile_make ${IMAGEFILE_1} ${BLk_SZ} + +DEVICE_1=$(loop_device_attach ${IMAGEFILE_1}) +echo "new device ${DEVICE_1}" + +MOUNTPOINT_1=${MPDIR}/simple_1 +mkdir -p ${MOUNTPOINT_1} +mount ${DEVICE_1} ${MOUNTPOINT_1} + +generate_files_direct ${MOUNTPOINT_1} "before" 9 +drop_cache + +#echo "Block device prepared, press ..." +#read -n 1 + +blksnap_snapshot_create "${DEVICE_1}" "${DIFF_STOGAGE_DEVICE}" "${BLk_SZ}M" +blksnap_snapshot_take + +#echo "Snapshot was token, press ..." +#read -n 1 + +#echo "Write something" > ${MOUNTPOINT_1}/something.txt +echo "Write to original" +generate_files_direct ${MOUNTPOINT_1} "after" 3 +drop_cache + +check_files ${MOUNTPOINT_1} + +echo "Check snapshots" +DEVICE_IMAGE_1=$(blksnap_get_image ${DEVICE_1}) +IMAGE_1=${TESTDIR}/image0 +mkdir -p ${IMAGE_1} +mount ${DEVICE_IMAGE_1} ${IMAGE_1} +#echo "pause, press ..." +#read -n 1 +check_files ${IMAGE_1} + +echo "Write to snapshot" +generate_files_direct ${IMAGE_1} "snapshot" 3 + +drop_cache +umount ${DEVICE_IMAGE_1} +mount ${DEVICE_IMAGE_1} ${IMAGE_1} + +#echo "pause, press ..." +#read -n 1 +check_files ${IMAGE_1} + +umount ${IMAGE_1} + +blksnap_snapshot_destroy + +#echo "Destroy snapshot, press ..." +#read -n 1 + +drop_cache +umount ${DEVICE_1} +mount ${DEVICE_1} ${MOUNTPOINT_1} + +check_files ${MOUNTPOINT_1} + +echo "Destroy first device" +blksnap_detach ${DEVICE_1} +umount ${MOUNTPOINT_1} +loop_device_detach ${DEVICE_1} +imagefile_cleanup ${IMAGEFILE_1} + +echo "Destroy diff storage device" +loop_device_detach ${DIFF_STOGAGE_DEVICE} +imagefile_cleanup ${DIFF_STOGAGE_FILE} + +blksnap_unload + +echo "Simple test finish" +echo "---" diff --git a/tests/1200-tmpfs_diff_storage.sh b/tests/1200-tmpfs_diff_storage.sh new file mode 100755 index 00000000..0f0d909e --- /dev/null +++ b/tests/1200-tmpfs_diff_storage.sh @@ -0,0 +1,115 @@ +#!/bin/bash -e +# +# SPDX-License-Identifier: GPL-2.0+ + +BLK_SZ=128 + +if [ -z $1 ] +then + DIFF_STORAGE_DIR=${HOME}/tmp +else + DIFF_STORAGE_DIR=$1/tmp +fi +echo "Diff storage directory ${DIFF_STORAGE_DIR}" + +. ./functions.sh +. ./blksnap.sh + +echo "---" +echo "Simple test start" + +# diff_storage_minimum=65536 - set 64 K sectors, it's 32MiB diff_storage portion size +blksnap_load "diff_storage_minimum=65536" + +# check module is ready +blksnap_version + +TESTDIR=${HOME}/blksnap-test +rm -rf ${TESTDIR} +mkdir -p ${TESTDIR} + +MPDIR=/mnt/blksnap-test +rm -rf ${MPDIR} +mkdir -p ${MPDIR} + +mkdir -p ${DIFF_STORAGE_DIR} +mount -t tmpfs -o size=${BLK_SZ}M diff_st ${DIFF_STORAGE_DIR} + +DIFF_STORAGE="${DIFF_STORAGE_DIR}/diff_storage" +fallocate --length 32MiB ${DIFF_STORAGE} + +# create device +IMAGEFILE_1=${TESTDIR}/simple_1.img +imagefile_make ${IMAGEFILE_1} ${BLK_SZ} + +DEVICE_1=$(loop_device_attach ${IMAGEFILE_1}) +echo "new device ${DEVICE_1}" + +MOUNTPOINT_1=${MPDIR}/simple_1 +mkdir -p ${MOUNTPOINT_1} +mount ${DEVICE_1} ${MOUNTPOINT_1} + +generate_files_direct ${MOUNTPOINT_1} "before" 9 +drop_cache + +#echo "Block device prepared, press ..." +#read -n 1 + +blksnap_snapshot_create "${DEVICE_1}" "${DIFF_STORAGE}" "2G" +blksnap_snapshot_take + +#echo "Snapshot was token, press ..." +#read -n 1 + +#echo "Write something" > ${MOUNTPOINT_1}/something.txt +echo "Write to original" +generate_files_direct ${MOUNTPOINT_1} "after" 3 +drop_cache + +check_files ${MOUNTPOINT_1} + +echo "Check snapshots" +DEVICE_IMAGE_1=$(blksnap_get_image ${DEVICE_1}) +IMAGE_1=${TESTDIR}/image0 +mkdir -p ${IMAGE_1} +mount ${DEVICE_IMAGE_1} ${IMAGE_1} +#echo "pause, press ..." +#read -n 1 +check_files ${IMAGE_1} + +echo "Write to snapshot" +generate_files_direct ${IMAGE_1} "snapshot" 3 + +drop_cache +umount ${DEVICE_IMAGE_1} +mount ${DEVICE_IMAGE_1} ${IMAGE_1} + +#echo "pause, press ..." +#read -n 1 +check_files ${IMAGE_1} + +umount ${IMAGE_1} + +blksnap_snapshot_destroy + +#echo "Destroy snapshot, press ..." +#read -n 1 + +drop_cache +umount ${DEVICE_1} +mount ${DEVICE_1} ${MOUNTPOINT_1} + +check_files ${MOUNTPOINT_1} + +echo "Destroy device" +blksnap_detach ${DEVICE_1} +umount ${MOUNTPOINT_1} +loop_device_detach ${DEVICE_1} +imagefile_cleanup ${IMAGEFILE_1} + +umount ${DIFF_STORAGE_DIR} + +blksnap_unload + +echo "Simple test finish" +echo "---" diff --git a/tests/1300-tmpfile_diff_storage.sh b/tests/1300-tmpfile_diff_storage.sh new file mode 100755 index 00000000..4888fae8 --- /dev/null +++ b/tests/1300-tmpfile_diff_storage.sh @@ -0,0 +1,106 @@ +#!/bin/bash -e +# +# SPDX-License-Identifier: GPL-2.0+ + + +if [ -z $1 ] +then + DIFF_STORAGE_DIR=${HOME} +else + DIFF_STORAGE_DIR=$1 +fi +echo "Diff storage directory ${DIFF_STORAGE_DIR}" + +. ./functions.sh +. ./blksnap.sh +BLOCK_SIZE=$(block_size_mnt ${DIFF_STORAGE_DIR}) + +echo "---" +echo "Simple test start" + +blksnap_load + +# check module is ready +blksnap_version + +TESTDIR=${HOME}/blksnap-test +rm -rf ${TESTDIR} +mkdir -p ${TESTDIR} + +MPDIR=/mnt/blksnap-test +rm -rf ${MPDIR} +mkdir -p ${MPDIR} + +# create first device +IMAGEFILE_1=${TESTDIR}/simple_1.img +imagefile_make ${IMAGEFILE_1} 64 + +DEVICE_1=$(loop_device_attach ${IMAGEFILE_1} ${BLOCK_SIZE}) +echo "new device ${DEVICE_1}" + +MOUNTPOINT_1=${MPDIR}/simple_1 +mkdir -p ${MOUNTPOINT_1} +mount ${DEVICE_1} ${MOUNTPOINT_1} + +generate_files_direct ${MOUNTPOINT_1} "before" 9 +drop_cache + +#echo "Block device prepared, press ..." +#read -n 1 + +blksnap_snapshot_create "${DEVICE_1}" "${DIFF_STORAGE_DIR}" "2G" +blksnap_snapshot_take + +#echo "Snapshot was token, press ..." +#read -n 1 + +#echo "Write something" > ${MOUNTPOINT_1}/something.txt +echo "Write to original" +generate_files_direct ${MOUNTPOINT_1} "after" 3 +drop_cache + +check_files ${MOUNTPOINT_1} + +echo "Check snapshots" +DEVICE_IMAGE_1=$(blksnap_get_image ${DEVICE_1}) +IMAGE_1=${TESTDIR}/image0 +mkdir -p ${IMAGE_1} +mount ${DEVICE_IMAGE_1} ${IMAGE_1} +#echo "pause, press ..." +#read -n 1 +check_files ${IMAGE_1} + +echo "Write to snapshot" +generate_files_direct ${IMAGE_1} "snapshot" 3 + +drop_cache +umount ${DEVICE_IMAGE_1} +mount ${DEVICE_IMAGE_1} ${IMAGE_1} + +#echo "pause, press ..." +#read -n 1 +check_files ${IMAGE_1} + +umount ${IMAGE_1} + +blksnap_snapshot_destroy + +#echo "Destroy snapshot, press ..." +#read -n 1 + +drop_cache +umount ${DEVICE_1} +mount ${DEVICE_1} ${MOUNTPOINT_1} + +check_files ${MOUNTPOINT_1} + +echo "Destroy first device" +blksnap_detach ${DEVICE_1} +umount ${MOUNTPOINT_1} +loop_device_detach ${DEVICE_1} +imagefile_cleanup ${IMAGEFILE_1} + +blksnap_unload + +echo "Simple test finish" +echo "---" diff --git a/tests/2000-stretch.sh b/tests/2000-stretch.sh index 2227d921..38c5a43f 100755 --- a/tests/2000-stretch.sh +++ b/tests/2000-stretch.sh @@ -2,56 +2,59 @@ # # SPDX-License-Identifier: GPL-2.0+ +if [ -z $1 ] +then + DIFF_STORAGE_DIR=${HOME} +else + DIFF_STORAGE_DIR=$1 +fi + . ./functions.sh . ./blksnap.sh +BLOCK_SIZE=$(block_size_mnt ${DIFF_STORAGE_DIR}) echo "---" echo "Stretch snapshot test" -# diff_storage_minimum=262144 - set 256 K sectors, it's 125MiB dikk_storage portion size -modprobe blksnap diff_storage_minimum=262144 -sleep 2s +# diff_storage_minimum=262144 - set 256 K sectors, it's 128MiB diff_storage portion size +blksnap_load "diff_storage_minimum=262144" # check module is ready blksnap_version -TESTDIR=~/blksnap-test +TESTDIR=${HOME}/blksnap-test MPDIR=/mnt/blksnap-test -DIFF_STORAGE=~/diff_storage/ +DIFF_STORAGE="${DIFF_STORAGE_DIR}/diff_storage" + rm -rf ${TESTDIR} rm -rf ${MPDIR} -rm -rf ${DIFF_STORAGE} mkdir -p ${TESTDIR} mkdir -p ${MPDIR} -mkdir -p ${DIFF_STORAGE} # create first device IMAGEFILE_1=${TESTDIR}/simple_1.img imagefile_make ${IMAGEFILE_1} 4096 -echo "new image file ${IMAGEFILE_1}" -DEVICE_1=$(loop_device_attach ${IMAGEFILE_1}) +DEVICE_1=$(loop_device_attach ${IMAGEFILE_1} ${BLOCK_SIZE}) echo "new device ${DEVICE_1}" MOUNTPOINT_1=${MPDIR}/simple_1 mkdir -p ${MOUNTPOINT_1} mount ${DEVICE_1} ${MOUNTPOINT_1} -generate_files ${MOUNTPOINT_1} "before" 5 +generate_files_direct ${MOUNTPOINT_1} "before" 5 drop_cache -blksnap_snapshot_create "${DEVICE_1}" +rm -f ${DIFF_STORAGE} +fallocate --length 128MiB ${DIFF_STORAGE} +blksnap_snapshot_create "${DEVICE_1}" "${DIFF_STORAGE}" "512M" -generate_files ${MOUNTPOINT_1} "tracked" 5 +generate_files_direct ${MOUNTPOINT_1} "tracked" 5 drop_cache -#fallocate --length 256MiB "${DIFF_STORAGE}/diff_storage" -#blksnap_snapshot_append "${DIFF_STORAGE}/diff_storage" - -#echo "Call: ${BLKSNAP} stretch_snapshot --id=${ID} --path=${DIFF_STORAGE} --limit=1024" -blksnap_stretch_snapshot ${DIFF_STORAGE} 1024 -echo "Press for taking snapshot..." -read -n 1 +blksnap_snapshot_watcher +#echo "Press for taking snapshot..." +#read -n 1 blksnap_snapshot_take @@ -59,8 +62,8 @@ generate_block_MB ${MOUNTPOINT_1} "after" 100 check_files ${MOUNTPOINT_1} echo "Check snapshot before overflow." -echo "press..." -read -n 1 +#echo "press..." +#read -n 1 DEVICE_IMAGE_1=$(blksnap_get_image ${DEVICE_1}) IMAGE_1=${MPDIR}/image0 @@ -69,29 +72,30 @@ mount ${DEVICE_IMAGE_1} ${IMAGE_1} check_files ${IMAGE_1} echo "Try to make snapshot overflow." -echo "press..." -read -n 1 -generate_block_MB ${MOUNTPOINT_1} "overflow" 300 +#echo "press..." +generate_block_MB ${MOUNTPOINT_1} "overflow" 768 echo "Umount images" -echo "press..." +#echo "press..." umount ${IMAGE_1} echo "Destroy snapshot" -echo "press..." +#echo "press..." blksnap_snapshot_destroy #echo "Check generated data" #check_files ${MOUNTPOINT_1} echo "Destroy first device" -echo "press..." +#echo "press..." +blksnap_detach ${DEVICE_1} umount ${MOUNTPOINT_1} loop_device_detach ${DEVICE_1} imagefile_cleanup ${IMAGEFILE_1} -echo "Unload module" -modprobe -r blksnap +blksnap_watcher_wait + +blksnap_unload echo "Stretch snapshot test finish" echo "---" diff --git a/tests/3000-cbt.sh b/tests/3000-cbt.sh index ba6d76ab..fb8ca2ef 100755 --- a/tests/3000-cbt.sh +++ b/tests/3000-cbt.sh @@ -2,50 +2,55 @@ # # SPDX-License-Identifier: GPL-2.0+ +if [ -z $1 ] +then + DIFF_STORAGE_DIR=${HOME} +else + DIFF_STORAGE_DIR=$1 +fi + . ./functions.sh . ./blksnap.sh +BLOCK_SIZE=$(block_size_mnt ${DIFF_STORAGE_DIR}) echo "---" echo "Change tracking test" # diff_storage_minimum=262144 - set 256 K sectors, it's 125MiB dikk_storage portion size -modprobe blksnap diff_storage_minimum=262144 -sleep 2s +blksnap_load "diff_storage_minimum=262144" # check module is ready blksnap_version -TESTDIR=~/blksnap-test +TESTDIR=${HOME}/blksnap-test MPDIR=/mnt/blksnap-test -DIFF_STORAGE=~/diff_storage/ +DIFF_STORAGE="${DIFF_STORAGE_DIR}/diff_storage" + rm -rf ${TESTDIR} rm -rf ${MPDIR} -rm -rf ${DIFF_STORAGE} mkdir -p ${TESTDIR} mkdir -p ${MPDIR} -mkdir -p ${DIFF_STORAGE} # create first device IMAGEFILE_1=${TESTDIR}/simple_1.img imagefile_make ${IMAGEFILE_1} 4096 -echo "new image file ${IMAGEFILE_1}" -DEVICE_1=$(loop_device_attach ${IMAGEFILE_1}) +DEVICE_1=$(loop_device_attach ${IMAGEFILE_1} ${BLOCK_SIZE}) echo "new device ${DEVICE_1}" MOUNTPOINT_1=${MPDIR}/simple_1 mkdir -p ${MOUNTPOINT_1} mount ${DEVICE_1} ${MOUNTPOINT_1} -generate_files ${MOUNTPOINT_1} "before" 5 +generate_files_direct ${MOUNTPOINT_1} "before" 5 drop_cache -fallocate --length 256MiB "${DIFF_STORAGE}/diff_storage" +rm -f ${DIFF_STORAGE} +fallocate --length 1GiB ${DIFF_STORAGE} # full echo "First snapshot for just attached devices" -blksnap_snapshot_create ${DEVICE_1} -blksnap_snapshot_append "${DIFF_STORAGE}/diff_storage" +blksnap_snapshot_create ${DEVICE_1} "${DIFF_STORAGE}" "1G" blksnap_snapshot_take blksnap_readcbt ${DEVICE_1} ${TESTDIR}/cbt0.map @@ -60,8 +65,7 @@ cmp -l ${TESTDIR}/cbt0.map ${TESTDIR}/cbt0_.map # increment 1 echo "First increment" -blksnap_snapshot_create ${DEVICE_1} -blksnap_snapshot_append "${DIFF_STORAGE}/diff_storage" +blksnap_snapshot_create ${DEVICE_1} "${DIFF_STORAGE}" "1G" blksnap_snapshot_take blksnap_readcbt ${DEVICE_1} ${TESTDIR}/cbt1.map @@ -74,8 +78,7 @@ cmp -l ${TESTDIR}/cbt1.map ${TESTDIR}/cbt1_.map # increment 2 echo "Second increment" -blksnap_snapshot_create ${DEVICE_1} -blksnap_snapshot_append "${DIFF_STORAGE}/diff_storage" +blksnap_snapshot_create ${DEVICE_1} "${DIFF_STORAGE}" "1G" blksnap_snapshot_take blksnap_readcbt ${DEVICE_1} ${TESTDIR}/cbt2.map @@ -88,8 +91,7 @@ cmp -l ${TESTDIR}/cbt2.map ${TESTDIR}/cbt2_.map # increment 3 echo "Second increment" -blksnap_snapshot_create ${DEVICE_1} -blksnap_snapshot_append "${DIFF_STORAGE}/diff_storage" +blksnap_snapshot_create ${DEVICE_1} "${DIFF_STORAGE}" "1G" blksnap_snapshot_take blksnap_readcbt ${DEVICE_1} ${TESTDIR}/cbt3.map @@ -98,19 +100,19 @@ blksnap_markdirty ${DEVICE_1} "${MOUNTPOINT_1}/dirty_file" blksnap_readcbt ${DEVICE_1} ${TESTDIR}/cbt3_.map blksnap_snapshot_destroy + set +e echo "dirty blocks:" cmp -l ${TESTDIR}/cbt3.map ${TESTDIR}/cbt3_.map 2>&1 set -e echo "Destroy first device" -echo "press..." +blksnap_detach ${DEVICE_1} umount ${MOUNTPOINT_1} loop_device_detach ${DEVICE_1} imagefile_cleanup ${IMAGEFILE_1} -echo "Unload module" -modprobe -r blksnap +blksnap_unload echo "Change tracking test finish" echo "---" diff --git a/tests/4000-diff_storage.sh b/tests/4000-diff_storage.sh index a08428cd..344fa058 100755 --- a/tests/4000-diff_storage.sh +++ b/tests/4000-diff_storage.sh @@ -8,18 +8,21 @@ echo "---" echo "Diff storage test" -# diff_storage_minimum=262144 - set 256 K sectors, it's 125MiB diff_storage portion size -modprobe blksnap diff_storage_minimum=262144 +# diff_storage_minimum=262144 - set 256 K sectors, it's 128MiB diff_storage portion size +blksnap_load "diff_storage_minimum=262144" # check module is ready blksnap_version if [ -z $1 ] then - TEST_DIR=$(realpath ~/blksnap-test) + TEST_DIR=${HOME}/blksnap-test else - TEST_DIR=$(realpath $1) + TEST_DIR=$(realpath $1"/blksnap-test") fi +mkdir -p ${TEST_DIR} +rm -rf ${TEST_DIR}/* + MP_TEST_DIR=$(stat -c %m ${TEST_DIR}) DEVICE="/dev/block/"$(mountpoint -d ${MP_TEST_DIR}) echo "Test directory [${TEST_DIR}] on device [${DEVICE}] selected" @@ -27,28 +30,20 @@ echo "Test directory [${TEST_DIR}] on device [${DEVICE}] selected" RELATIVE_TEST_DIR=${TEST_DIR#${MP_TEST_DIR}} MP_DIR=/mnt/blksnap-test -DIFF_STORAGE=${TEST_DIR}/diff_storage/ -rm -rf ${TEST_DIR} rm -rf ${MP_DIR} -rm -rf ${DIFF_STORAGE} -mkdir -p ${TEST_DIR} mkdir -p ${MP_DIR} -mkdir -p ${DIFF_STORAGE} - generate_block_MB ${TEST_DIR} "before" 10 check_files ${TEST_DIR} -blksnap_snapshot_create "${DEVICE}" - -blksnap_stretch_snapshot ${DIFF_STORAGE} 1024 -echo "Waiting for creating first portion" -sleep 2s +DIFF_STORAGE=/dev/shm +blksnap_snapshot_create "${DEVICE}" "${DIFF_STORAGE}" "1G" +blksnap_snapshot_watcher blksnap_snapshot_take -generate_block_MB ${TEST_DIR} "after" 1000 +generate_block_MB ${TEST_DIR} "after" 100 check_files ${TEST_DIR} IMAGE=${MP_DIR}/image0 @@ -68,11 +63,11 @@ umount ${IMAGE} echo "Destroy snapshot" blksnap_snapshot_destroy -echo "Waiting for streach process terminate" -sleep 2s +blksnap_detach ${DEVICE} + +blksnap_watcher_wait -echo "Unload module" -modprobe -r blksnap +blksnap_unload echo "Diff storage test finish" echo "---" diff --git a/tests/5000-pullout.sh b/tests/5000-pullout.sh index fd2bcd27..47e13a26 100755 --- a/tests/5000-pullout.sh +++ b/tests/5000-pullout.sh @@ -2,14 +2,21 @@ # # SPDX-License-Identifier: GPL-2.0+ +if [ -z $1 ] +then + DIFF_STORAGE_DIR=${HOME} +else + DIFF_STORAGE_DIR=$1 +fi +echo "Diff storage directory ${DIFF_STORAGE_DIR}" + . ./functions.sh . ./blksnap.sh echo "---" echo "pullout test start" -modprobe blksnap -sleep 2s +blksnap_load # check module is ready blksnap_version @@ -22,11 +29,12 @@ MPDIR=/mnt/blksnap-test rm -rf ${MPDIR} mkdir -p ${MPDIR} -modprobe zram num_devices=2 +ALG="lzo" +modprobe zram num_devices=2 && sleep 1 # create first device DEVICE_1="/dev/zram0" -zramctl --size 128M --algorithm lz4 ${DEVICE_1} +zramctl --size 128M --algorithm ${ALG} ${DEVICE_1} mkfs.ext4 ${DEVICE_1} echo "new device ${DEVICE_1}" @@ -36,7 +44,7 @@ mount ${DEVICE_1} ${MOUNTPOINT_1} # create second device DEVICE_2="/dev/zram1" -zramctl --size 128M --algorithm lz4 ${DEVICE_2} +zramctl --size 128M --algorithm ${ALG} ${DEVICE_2} mkfs.ext4 ${DEVICE_2} echo "new device ${DEVICE_2}" @@ -44,29 +52,28 @@ MOUNTPOINT_2=${MPDIR}/simple_2 mkdir -p ${MOUNTPOINT_2} mount ${DEVICE_2} ${MOUNTPOINT_2} -generate_files ${MOUNTPOINT_1} "before" 9 +generate_files_sync ${MOUNTPOINT_1} "before" 9 drop_cache -echo "Block device prepared, press ..." +echo "Block device prepared" +#echo "press ..." #read -n 1 -blksnap_snapshot_create "${DEVICE_1} ${DEVICE_2}" - -DIFF_STORAGE=~/diff_storage0 +DIFF_STORAGE="${DIFF_STORAGE_DIR}/diff_storage" rm -f ${DIFF_STORAGE} -fallocate --length 1GiB ${DIFF_STORAGE} -blksnap_snapshot_append ${DIFF_STORAGE} - +fallocate --length 256MiB ${DIFF_STORAGE} +blksnap_snapshot_create "${DEVICE_1} ${DEVICE_2}" "${DIFF_STORAGE}" "1G" blksnap_snapshot_take -echo "Snapshot was token, press ..." +echo "Snapshot was token" +#echo "press ..." #read -n 1 -blksnap_snapshot_collect_all +blksnap_snapshot_collect echo "Write to original" #echo "Write something" > ${MOUNTPOINT_1}/something.txt -generate_files ${MOUNTPOINT_1} "after" 3 +generate_files_sync ${MOUNTPOINT_1} "after" 3 drop_cache check_files ${MOUNTPOINT_1} @@ -79,7 +86,7 @@ mount ${DEVICE_IMAGE_1} ${IMAGE_1} check_files ${IMAGE_1} echo "Write to snapshot" -generate_files ${IMAGE_1} "snapshot" 3 +generate_files_sync ${IMAGE_1} "snapshot" 3 drop_cache umount ${DEVICE_IMAGE_1} @@ -91,11 +98,10 @@ umount ${IMAGE_1} blksnap_snapshot_destroy -echo "Destroy snapshot, press ..." +echo "Destroy snapshot" +#echo "press ..." #read -n 1 -rm ${DIFF_STORAGE} - drop_cache umount ${DEVICE_1} mount ${DEVICE_1} ${MOUNTPOINT_1} @@ -107,11 +113,7 @@ umount ${MOUNTPOINT_2} umount ${MOUNTPOINT_1} modprobe -r zram -echo "Tracking device info:" -blksnap_tracker_collect - -echo "Unload module" -modprobe -r blksnap +blksnap_unload echo "pullout test finish" echo "---" diff --git a/tests/6000-snapimage_write.sh b/tests/6000-snapimage_write.sh new file mode 100755 index 00000000..292bf84e --- /dev/null +++ b/tests/6000-snapimage_write.sh @@ -0,0 +1,131 @@ +#!/bin/bash -e +# +# SPDX-License-Identifier: GPL-2.0+ + +. ./functions.sh +. ./blksnap.sh + +echo "---" +echo "Snapshot write test" + +# diff_storage_minimum=262144 - set 256 K sectors, it's 125MiB. +modprobe blksnap diff_storage_minimum=262144 chunk_maximum_in_queue=16 +sleep 2s + +# check module is ready +blksnap_version + +if [ -z $1 ] +then + echo "Should use loop device" + + #echo "Create original loop device" + LOOPFILE=${HOME}/blksnap-original.img + dd if=/dev/zero of=${LOOPFILE} count=1024 bs=1M + + DEVICE=$(loop_device_attach ${LOOPFILE}) + mkfs.xfs -f ${DEVICE} + # mkfs.ext4 ${DEVICE} + + ORIGINAL=/mnt/blksnap-original + mkdir -p ${ORIGINAL} + mount ${DEVICE} ${ORIGINAL} +else + echo "Should use device [$1]" + + DEVICE=$1 + + MNTDIR=$(findmnt -n -o TARGET ${DEVICE}) + echo ${MNTDIR} + ORIGINAL=${MNTDIR}"/blksnap-original" + rm -rf ${ORIGINAL}/* + mkdir -p ${ORIGINAL} +fi + +FSTYPE=$(findmnt -n -o FSTYPE ${DEVICE}) + +if [ "${FSTYPE}" = "xfs" ] +then + MOUNTOPT="-o nouuid" +else + MOUNTOPT="" +fi + +if [ -z $2 ] +then + ITERATION_CNT="3" +else + ITERATION_CNT="$2" +fi + +IMAGE=/mnt/blksnap-image0 +mkdir -p ${IMAGE} + +DIFF_STORAGE="/dev/shm" + +generate_files_direct ${ORIGINAL} "original-it#0" 5 +drop_cache + +for ITERATOR in $(seq 1 $ITERATION_CNT) +do + echo "Itearation: ${ITERATOR}" + + blksnap_snapshot_create ${DEVICE} "${DIFF_STORAGE}" "256M" + blksnap_snapshot_take + + DEVICE_IMAGE=$(blksnap_get_image ${DEVICE}) + mount ${MOUNTOPT} ${DEVICE_IMAGE} ${IMAGE} + + generate_block_MB ${IMAGE} "image-it#${ITERATOR}" 10 & + IMAGE_PID=$! + generate_block_MB ${ORIGINAL} "original-it#${ITERATOR}" 10 + wait ${IMAGE_PID} + + drop_cache + + check_files ${IMAGE} & + IMAGE_PID=$! + check_files ${ORIGINAL} + wait ${IMAGE_PID} + + drop_cache + + #echo "pause, press ..." + #read -n 1 + + echo "Remount image device "${DEVICE_IMAGE} + umount ${DEVICE_IMAGE} + mount ${MOUNTOPT} ${DEVICE_IMAGE} ${IMAGE} + + check_files ${IMAGE} & + IMAGE_PID=$! + check_files ${ORIGINAL} + wait ${IMAGE_PID} + + #echo "pause, press ..." + #read -n 1 + + umount ${IMAGE} + + blksnap_snapshot_destroy +done + +if [ -z $1 ] +then + echo "Destroy original loop device" + umount ${ORIGINAL} + + blksnap_detach ${DEVICE} + + loop_device_detach ${DEVICE} + imagefile_cleanup ${LOOPFILE} +else + echo "Cleanup directory [${ORIGINAL}]" + rm -rf ${ORIGINAL}/* +fi + +echo "Unload module" +modprobe -r blksnap + +echo "Snapshot write test finish" +echo "---" diff --git a/tests/6100-snapimage_write.sh b/tests/6100-snapimage_write.sh new file mode 100755 index 00000000..1766fc79 --- /dev/null +++ b/tests/6100-snapimage_write.sh @@ -0,0 +1,107 @@ +#!/bin/bash -e +# +# SPDX-License-Identifier: GPL-2.0+ + +if [ -z $1 ] +then + DIFF_STORAGE_DIR=${HOME} +else + DIFF_STORAGE_DIR=$1 +fi +echo "Diff storage directory ${DIFF_STORAGE_DIR}" + +. ./functions.sh +. ./blksnap.sh +BLOCK_SIZE=$(block_size_mnt ${DIFF_STORAGE_DIR}) + +echo "---" +echo "Snapshot direct write test" + +# diff_storage_minimum=262144 - set 256 K sectors, it's 125MiB. +modprobe blksnap diff_storage_minimum=262144 + +modprobe blksnap +sleep 2s + +# check module is ready +blksnap_version + + +echo "Should use loop device" + +TESTDIR="${HOME}/blksnap-test" +rm -rf ${TESTDIR} +mkdir -p ${TESTDIR} + +#echo "Create original loop device" +LOOPFILE="${TESTDIR}/blksnap-original.img" +dd if=/dev/zero of=${LOOPFILE} count=256 bs=1M + +DEVICE=$(loop_device_attach ${LOOPFILE} ${BLOCK_SIZE}) + +if [ -z $1 ] +then + ITERATION_CNT="1" +else + ITERATION_CNT="$1" +fi + +DIFF_STORAGE="${DIFF_STORAGE_DIR}/diff_storage" +fallocate --length 1GiB ${DIFF_STORAGE} + +for ITERATOR in $(seq 1 $ITERATION_CNT) +do + echo "Itearation: ${ITERATOR}" + + blksnap_snapshot_create ${DEVICE} "${DIFF_STORAGE}" "256M" + blksnap_snapshot_take + + DEVICE_IMAGE=$(blksnap_get_image ${DEVICE}) + + FILE="${TESTDIR}/image-it#${ITERATOR}" + generate_file_magic ${FILE} 2048 + + dd if=${FILE} of=${DEVICE} bs=1KiB count=1024 seek=0 oflag=direct status=none + + dd if=${FILE} of=${DEVICE_IMAGE} bs=1KiB count=1024 seek=126 oflag=direct status=none + + dd if=${FILE} of=${DEVICE_IMAGE} bs=1KiB count=1024 seek=$((2048 + 126)) oflag=direct status=none + + dd if=${FILE} of=${DEVICE_IMAGE} bs=1KiB count=1024 seek=$((4096 + 126)) oflag=direct status=none + + + sync ${DEVICE_IMAGE} + # echo "pause, press ..." + # read -n 1 + + dd if=${DEVICE_IMAGE} of="${FILE}_copy" bs=1KiB count=1024 skip=126 iflag=direct status=none + cmp ${FILE} "${FILE}_copy" && echo "Files are equal." + + dd if=${DEVICE_IMAGE} of="${FILE}_copy1" bs=1KiB count=1024 skip=$((2048 + 126)) iflag=direct status=none + cmp ${FILE} "${FILE}_copy1" && echo "Files are equal." + + dd if=${DEVICE_IMAGE} of="${FILE}_copy2" bs=1KiB count=1024 skip=$((4096 + 126)) oflag=direct status=none + cmp ${FILE} "${FILE}_copy2" && echo "Files are equal." + + + # echo "pause, press ..." + # read -n 1 + + blksnap_snapshot_destroy +done + +echo "Destroy original loop device" + +blksnap_detach ${DEVICE} + +loop_device_detach ${DEVICE} +imagefile_cleanup ${LOOPFILE} + +echo "Cleanup test directory [${TESTDIR}]" +rm -rf "${TESTDIR}" + +echo "Unload module" +modprobe -r blksnap + +echo "Snapshot direct write test finish" +echo "---" diff --git a/tests/7000-destroy.sh b/tests/7000-destroy.sh new file mode 100755 index 00000000..14beddc7 --- /dev/null +++ b/tests/7000-destroy.sh @@ -0,0 +1,97 @@ +#!/bin/bash -e +# +# SPDX-License-Identifier: GPL-2.0+ + + +if [ -z $1 ] +then + DIFF_STORAGE_DIR=${HOME} +else + DIFF_STORAGE_DIR=$1 +fi +echo "Diff storage directory ${DIFF_STORAGE_DIR}" + +. ./functions.sh +. ./blksnap.sh +BLOCK_SIZE=$(block_size_mnt ${DIFF_STORAGE_DIR}) + +echo "---" +echo "Destroy test start" + +blksnap_load + +# check module is ready +blksnap_version + +TESTDIR=/tmp/blksnap-test +rm -rf ${TESTDIR} +mkdir -p ${TESTDIR} + +MPDIR=/mnt/blksnap-test +rm -rf ${MPDIR} +mkdir -p ${MPDIR} + + +# create first device +IMAGEFILE_1=${TESTDIR}/simple_1.img +imagefile_make ${IMAGEFILE_1} 128 + +DEVICE_1=$(loop_device_attach ${IMAGEFILE_1} ${BLOCK_SIZE}) +echo "new device ${DEVICE_1}" + +MOUNTPOINT_1=${MPDIR}/simple_1 +mkdir -p ${MOUNTPOINT_1} +mount ${DEVICE_1} ${MOUNTPOINT_1} + +echo "Write to original before taking snapshot" +generate_files_sync ${MOUNTPOINT_1} "before" 9 +drop_cache + +DIFF_STORAGE=${DIFF_STORAGE_DIR}/diff_storage +fallocate --length 1GiB ${DIFF_STORAGE} +blksnap_snapshot_create "${DEVICE_1}" "${DIFF_STORAGE}" "128M" +blksnap_snapshot_take + +echo "mount snapshot" +DEVICE_IMAGE_1=$(blksnap_get_image ${DEVICE_1}) +IMAGE_1=${TESTDIR}/image0 +mkdir -p ${IMAGE_1} +mount ${DEVICE_IMAGE_1} ${IMAGE_1} + +set +e + +echo "Write to original after taking snapshot" +generate_files_sync ${MOUNTPOINT_1} "after" 4 & +PID_GEN1=$! +dd if=${DEVICE_IMAGE_1} of=/dev/zero & +PID_DD1=$! + +echo "Write to snapshot" +generate_files_sync ${IMAGE_1} "snapshot" 4 & +PID_GEN2=$! +dd if=${DEVICE_IMAGE_1} of=/dev/zero & +PID_DD2=$! + +echo "Destroy snapshot ..." +blksnap_snapshot_destroy + +umount --lazy --force ${IMAGE_1} + +echo "Waiting for all process terminate" +wait ${PID_GEN1} +wait ${PID_DD1} +wait ${PID_GEN2} +wait ${PID_DD2} + +set -e + +echo "Destroy device" +blksnap_detach ${DEVICE_1} +umount ${MOUNTPOINT_1} +loop_device_detach ${DEVICE_1} +imagefile_cleanup ${IMAGEFILE_1} + +blksnap_unload + +echo "Destroy test finish" +echo "---" diff --git a/tests/blksnap.sh b/tests/blksnap.sh index 9034939c..84ce6ddb 100755 --- a/tests/blksnap.sh +++ b/tests/blksnap.sh @@ -6,10 +6,34 @@ if [ -f "/usr/bin/blksnap" ] || [ -f "/usr/sbin/blksnap" ] then BLKSNAP=blksnap else - BLKSNAP="$(cd ../; pwd)/tools/blksnap/blksnap" + BLKSNAP="$(cd ../; pwd)/tools/blksnap/bin/blksnap" fi ID="" +BLKSNAP_FILENAME=$(modinfo --field filename blksnap) +STRETCH_PROCESS_PID="" + +blksnap_load() +{ + if [ ${BLKSNAP_FILENAME} = "(builtin)" ] + then + return + fi + + modprobe blksnap $1 + sleep 2s +} + +blksnap_unload() +{ + if [ ${BLKSNAP_FILENAME} = "(builtin)" ] + then + return + fi + + echo "Unload module" + modprobe -r blksnap 2>&1 || sleep 1 && modprobe -r blksnap && echo "Unload success" +} blksnap_version() { @@ -24,20 +48,13 @@ blksnap_snapshot_create() do PARAM="${PARAM} --device ${DEVICE}" done + PARAM="${PARAM} --file $2" + PARAM="${PARAM} --limit $3" - ${BLKSNAP} version ID=$(${BLKSNAP} snapshot_create ${PARAM}) echo "New snapshot ${ID} was created" } -blksnap_snapshot_append() -{ - local FILE=$1 - - echo "Append file ${FILE} to diff storage" - ${BLKSNAP} snapshot_appendstorage --id=${ID} --file=${FILE} -} - blksnap_snapshot_destroy() { echo "Destroy snapshot ${ID}" @@ -51,35 +68,32 @@ blksnap_snapshot_take() ${BLKSNAP} snapshot_take --id=${ID} } -blksnap_snapshot_take() -{ - echo "Take snapshot ${ID}" - - ${BLKSNAP} snapshot_take --id=${ID} -} - blksnap_snapshot_collect() { - echo "Collect snapshot ${ID}" + echo "Collect snapshots" - ${BLKSNAP} snapshot_collect --id=${ID} + ${BLKSNAP} snapshot_collect } -blksnap_snapshot_collect_all() +blksnap_attach() { - ${BLKSNAP} snapshot_collect + local DEVICE=$1 + + ${BLKSNAP} attach --device=${DEVICE} } -blksnap_tracker_remove() +blksnap_detach() { local DEVICE=$1 - ${BLKSNAP} tracker_remove --device=${DEVICE} + ${BLKSNAP} detach --device=${DEVICE} } -blksnap_tracker_collect() +blksnap_cbtinfo() { - ${BLKSNAP} tracker_collect + local DEVICE=$1 + + ${BLKSNAP} cbtinfo --device=${DEVICE} --file=${CBTMAP} } blksnap_readcbt() @@ -87,25 +101,36 @@ blksnap_readcbt() local DEVICE=$1 local CBTMAP=$2 - ${BLKSNAP} tracker_readcbtmap --device=${DEVICE} --file=${CBTMAP} + ${BLKSNAP} readcbtmap --device=${DEVICE} --file=${CBTMAP} } blksnap_markdirty() { local DIRTYFILE=$2 - ${BLKSNAP} tracker_markdirtyblock --file=${DIRTYFILE} + ${BLKSNAP} markdirtyblock --file=${DIRTYFILE} } -blksnap_stretch_snapshot() +blksnap_snapshot_watcher() { - local DIFF_STORAGE_PATH=$1 - local LIMIT_MB=$2 - - ${BLKSNAP} stretch_snapshot --id=${ID} --path=${DIFF_STORAGE_PATH} --limit=${LIMIT_MB} & + ${BLKSNAP} snapshot_watcher --id=${ID} & + STRETCH_PROCESS_PID=$! +} +blksnap_watcher_wait() +{ + echo "Waiting for streach process terminate" + wait ${STRETCH_PROCESS_PID} } blksnap_get_image() { - echo "/dev/blksnap-image_"$(stat -c %t $1)":"$(stat -c %T $1) + ${BLKSNAP} snapshot_info --field image --device $1 +} + +blksnap_cleanup() +{ + for ID in $(${BLKSNAP} snapshot_collect) + do + ${BLKSNAP} snapshot_destroy --id=${ID} + done } diff --git a/tests/build_and_install.sh b/tests/build_and_install.sh deleted file mode 100755 index 019ba0fb..00000000 --- a/tests/build_and_install.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash -e -# -# SPDX-License-Identifier: GPL-2.0+ - -TEST_ALL_PWD=$(pwd) - -echo "Build and install module" -cd ${TEST_ALL_PWD}/../module -./mk.sh clean -./mk.sh build -./mk.sh install-flt -./mk.sh install - -cd ${TEST_ALL_PWD} diff --git a/tests/cpp/CMakeLists.txt b/tests/cpp/CMakeLists.txt index 4b128bcb..b5c9713e 100644 --- a/tests/cpp/CMakeLists.txt +++ b/tests/cpp/CMakeLists.txt @@ -34,11 +34,6 @@ add_executable(${TEST_CBT} cbt.cpp) target_link_libraries(${TEST_CBT} PRIVATE ${TESTS_LIBS}) target_include_directories(${TEST_CBT} PRIVATE ./) -set(TEST_DIFF_STORAGE test_diff_storage) -add_executable(${TEST_DIFF_STORAGE} TestSector.cpp diff_storage.cpp) -target_link_libraries(${TEST_DIFF_STORAGE} PRIVATE ${TESTS_LIBS}) -target_include_directories(${TEST_DIFF_STORAGE} PRIVATE ./) - set(TEST_BOUNDARY test_boundary) add_executable(${TEST_BOUNDARY} TestSector.cpp boundary.cpp) target_link_libraries(${TEST_BOUNDARY} PRIVATE ${TESTS_LIBS}) @@ -49,18 +44,10 @@ add_executable(${TEST_PERFORMANCE} performance.cpp) target_link_libraries(${TEST_PERFORMANCE} PRIVATE ${TESTS_LIBS}) target_include_directories(${TEST_PERFORMANCE} PRIVATE ./) -set_target_properties(${TEST_CORRUPT} ${TEST_CBT} ${TEST_DIFF_STORAGE} ${TEST_BOUNDARY} ${TEST_PERFORMANCE} - PROPERTIES - RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../ -) - -#add_custom_target(blksnap-tests DEPENDS ${TEST_CORRUPT} ${TEST_CBT} ${TEST_DIFF_STORAGE} ${TEST_BOUNDARY} ${TEST_PERFORMANCE}) - install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../ DESTINATION /opt/blksnap/tests USE_SOURCE_PERMISSIONS PATTERN "*.sh" - PATTERN "build_and_install.sh" EXCLUDE PATTERN "cpp" EXCLUDE ) diff --git a/tests/cpp/TestSector.cpp b/tests/cpp/TestSector.cpp index f2142c00..bc281a00 100644 --- a/tests/cpp/TestSector.cpp +++ b/tests/cpp/TestSector.cpp @@ -91,15 +91,17 @@ void CTestSectorGenetor::Check(unsigned char* buffer, size_t size, sector_t sect { failMessage += std::string("Invalid sequence number\n"); failMessage += std::string("sector " + std::to_string(t->header.sector) + "\n"); - failMessage += std::string("seqNumber " + std::to_string(t->header.seqNumber) + " > " - + std::to_string(seqNumber) + "\n"); + failMessage += std::string("seqNumber " + std::to_string(t->header.seqNumber) + + (isStrictly ? " != " : " > ") + + std::to_string(seqNumber) + "\n"); } if (isInvalidSeqTime) { failMessage += std::string("Invalid sequence time\n"); failMessage += std::string("sector " + std::to_string(t->header.sector) + "\n"); - failMessage += std::string("seqTime " + std::to_string(t->header.seqTime) + " > " - + std::to_string(seqTime) + "\n"); + failMessage += std::string("seqTime " + std::to_string(t->header.seqTime) + + (isStrictly ? " != " : " > ") + + std::to_string(seqTime) + "\n"); } } diff --git a/tests/cpp/boundary.cpp b/tests/cpp/boundary.cpp index 4b9b2d68..a2ae8a89 100644 --- a/tests/cpp/boundary.cpp +++ b/tests/cpp/boundary.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0+ #include -//#include +#include #include #include #include @@ -122,8 +122,9 @@ static inline int randomInt(const int upLimit, const int order) return (std::rand() % upLimit) & ~(order - 1); } -void CheckBoundary(const std::string& origDevName, const std::string& diffStorage, const int durationLimitSec, - const bool isSync, const int chunkSize) +void CheckBoundary(const std::string& origDevName, const std::string& diffStorage, + const unsigned long long diffStorageLimit, const int durationLimitSec, + const bool isSync, const int chunkSize) { std::srand(std::time(nullptr)); @@ -131,6 +132,7 @@ void CheckBoundary(const std::string& origDevName, const std::string& diffStorag logger.Info("version: " + blksnap::Version()); logger.Info("device: " + origDevName); logger.Info("diffStorage: " + diffStorage); + logger.Info("diffStorageLimit: " + std::to_string(diffStorageLimit)); logger.Info("duration: " + std::to_string(durationLimitSec) + " seconds"); logger.Info("chunkSize: " + std::to_string(chunkSize) + " bytes"); @@ -154,7 +156,7 @@ void CheckBoundary(const std::string& origDevName, const std::string& diffStorag logger.Info("-- Create snapshot"); { - auto ptrSession = blksnap::ISession::Create(devices, diffStorage); + auto ptrSession = blksnap::ISession::Create(devices, diffStorage, diffStorageLimit); int testSeqNumber = ptrGen->GetSequenceNumber(); ptrGen->IncSequence(); @@ -162,7 +164,7 @@ void CheckBoundary(const std::string& origDevName, const std::string& diffStorag logger.Info("test sequence time " + std::to_string(testSeqTime)); - std::string imageDevName = ptrSession->GetImageDevice(origDevName); + std::string imageDevName = blksnap::ICbt::Create(origDevName)->GetImage(); logger.Info("Found image block device [" + imageDevName + "]"); auto ptrImage = std::make_shared(imageDevName); @@ -232,7 +234,7 @@ void CheckBoundary(const std::string& origDevName, const std::string& diffStorag } { - auto ptrSession = blksnap::ISession::Create(devices, diffStorage); + auto ptrSession = blksnap::ISession::Create(devices, diffStorage, diffStorageLimit); int testSeqNumber = ptrGen->GetSequenceNumber(); clock_t testSeqTime = std::clock(); @@ -240,7 +242,7 @@ void CheckBoundary(const std::string& origDevName, const std::string& diffStorag logger.Info("test sequence time " + std::to_string(testSeqTime)); - std::string imageDevName = ptrSession->GetImageDevice(origDevName); + std::string imageDevName = blksnap::ICbt::Create(origDevName)->GetImage();; logger.Info("Found image block device [" + imageDevName + "]"); auto ptrImage = std::make_shared(imageDevName); @@ -305,7 +307,7 @@ void CheckBoundary(const std::string& origDevName, const std::string& diffStorag } { - auto ptrSession = blksnap::ISession::Create(devices, diffStorage); + auto ptrSession = blksnap::ISession::Create(devices, diffStorage, diffStorageLimit); int testSeqNumber = ptrGen->GetSequenceNumber(); clock_t testSeqTime = std::clock(); @@ -313,7 +315,7 @@ void CheckBoundary(const std::string& origDevName, const std::string& diffStorag logger.Info("test sequence time " + std::to_string(testSeqTime)); - std::string imageDevName = ptrSession->GetImageDevice(origDevName); + std::string imageDevName = blksnap::ICbt::Create(origDevName)->GetImage();; logger.Info("Found image block device [" + imageDevName + "]"); auto ptrImage = std::make_shared(imageDevName); @@ -362,7 +364,9 @@ void Main(int argc, char* argv[]) ("log,l", po::value(),"Detailed log of all transactions.") ("device,d", po::value(),"Device name.") ("diff_storage,s", po::value(), - "Directory name for allocating diff storage files.") + "The name of the file to allocate the difference storage.") + ("diff_storage_limit,l", po::value()->default_value("1G"), + "The available limit for the size of the difference storage file. The suffixes M, K and G is allowed.") ("duration,u", po::value(), "The test duration limit in minutes.") ("sync", "Use O_SYNC for access to original device.") ("blksz", po::value()->default_value(512), "Align reads and writes to the block size.") @@ -394,6 +398,22 @@ void Main(int argc, char* argv[]) throw std::invalid_argument("Argument 'diff_storage' is missed."); std::string diffStorage = vm["diff_storage"].as(); + unsigned long long diffStorageLimit = 0; + unsigned long long multiple = 1; + std::string limit_str = vm["diff_storage_limit"].as(); + switch (limit_str.back()) + { + case 'G': + multiple *= 1024; + case 'M': + multiple *= 1024; + case 'K': + multiple *= 1024; + limit_str.back() = '\0'; + default: + diffStorageLimit = std::stoll(limit_str.c_str()) * multiple; + } + int duration = 1; if (vm.count("duration")) duration = vm["duration"].as(); @@ -411,7 +431,8 @@ void Main(int argc, char* argv[]) try { - CheckBoundary(origDevName, diffStorage, duration * 60, isSync, chunkSize); + CheckBoundary(origDevName, diffStorage, diffStorageLimit, + duration * 60, isSync, chunkSize); } catch (std::exception& ex) { diff --git a/tests/cpp/corrupt.cpp b/tests/cpp/corrupt.cpp index 7f51cd3d..ac2a6b08 100644 --- a/tests/cpp/corrupt.cpp +++ b/tests/cpp/corrupt.cpp @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-2.0+ #include +#include #include #include #include @@ -9,7 +10,6 @@ #include #include #include -#include #include #include "helpers/AlignedBuffer.hpp" @@ -183,10 +183,10 @@ void CheckCbtCorrupt(const std::shared_ptr& ptrCbtInfoPreviou throw std::runtime_error("Failed to check CBT corruption. Corrupts list is empty"); const std::string& original = corrupts[0].original; - auto ptrCbt = blksnap::ICbt::Create(); + auto ptrCbt = blksnap::ICbt::Create(original); - std::shared_ptr ptrCbtInfoCurrent = ptrCbt->GetCbtInfo(original); - std::shared_ptr ptrCbtDataCurrent = ptrCbt->GetCbtData(ptrCbtInfoCurrent); + std::shared_ptr ptrCbtInfoCurrent = ptrCbt->GetCbtInfo(); + std::shared_ptr ptrCbtDataCurrent = ptrCbt->GetCbtData(); logger.Info("Previous CBT snap number= " + std::to_string(ptrCbtInfoPrevious->snapNumber)); logger.Info("Current CBT snap number= " + std::to_string(ptrCbtInfoCurrent->snapNumber)); @@ -210,13 +210,7 @@ void LogCurruptedSectors(const std::string& image, const std::vector& ra ss << "Ranges of corrupted sectors:" << std::endl; for (const SRange& range : ranges) { - blksnap::SectorState state = {0}; - ss << range.sector << ":" << range.count << std::endl; - blksnap::GetSectorState(image, range.sector << SECTOR_SHIFT, state); - ss << "prev= " + std::to_string(state.snapNumberPrevious) + " " - << "curr= " + std::to_string(state.snapNumberCurrent) + " " - << "state= " + std::to_string(state.chunkState) << std::endl; } logger.Err(ss); } @@ -226,7 +220,128 @@ void LogCurruptedSectors(const std::string& image, const std::vector& ra } } -void CheckCorruption(const std::string& origDevName, const std::string& diffStorage, const int durationLimitSec, +void SimpleCorruption(const std::string& origDevName, + const std::string& diffStorage, + const unsigned long long diffStorageLimit, + const bool isSync) +{ + bool isErrorFound = false; + std::vector corrupts; + + logger.Info("--- Test: check corruption ---"); + logger.Info("version: " + blksnap::Version()); + logger.Info("device: " + origDevName); + logger.Info("diffStorage: " + diffStorage); + logger.Info("diffStorageLimit: " + std::to_string(diffStorageLimit) + " MiB"); + + auto ptrGen = std::make_shared(true); + auto ptrOrininal = std::make_shared(origDevName, isSync); + + logger.Info("device size: " + std::to_string(ptrOrininal->Size())); + logger.Info("device block size: " + std::to_string(g_blksz)); + + logger.Info("-- Fill original device collection by test pattern"); + FillAll(ptrGen, ptrOrininal); + + std::vector devices; + devices.push_back(origDevName); + + {// single test circle + size_t size; + off_t offset; + auto ptrSession = blksnap::ISession::Create(devices, diffStorage, diffStorageLimit); + auto ptrCbt = blksnap::ICbt::Create(origDevName); + std::stringstream ss; + + int testSeqNumber = ptrGen->GetSequenceNumber(); + clock_t testSeqTime = std::clock(); + logger.Info("test sequence time " + std::to_string(testSeqTime)); + + std::string imageDevName = ptrCbt->GetImage(); + logger.Info("Found image block device [" + imageDevName + "]"); + auto ptrImage = std::make_shared(imageDevName); + + logger.Info("- Check image content before writing to original device"); + CheckAll(ptrGen, ptrImage, testSeqNumber, testSeqTime); + if (ptrGen->Fails() > 0) + { + isErrorFound = true; + const std::vector& ranges = ptrGen->GetFails(); + corrupts.emplace_back(origDevName, ranges); + + LogCurruptedSectors(ptrImage->Name(), ranges); + } + + // Write first sector, like superblock + offset = 0; + size = g_blksz; + FillBlocks(ptrGen, ptrOrininal, offset, size); + ss << (offset >> SECTOR_SHIFT) << ":" << (size >> SECTOR_SHIFT) << " "; + + // second chunk + offset = 1ULL << (SECTOR_SHIFT + 9); + size = g_blksz * 2; + FillBlocks(ptrGen, ptrOrininal, offset, size); + ss << (offset >> SECTOR_SHIFT) << ":" << (size >> SECTOR_SHIFT) << " "; + + // next chunk + offset = 2ULL << (SECTOR_SHIFT + 9); + size = g_blksz * 3; + FillBlocks(ptrGen, ptrOrininal, 2ULL << (SECTOR_SHIFT + 9), g_blksz * 3); + ss << (offset >> SECTOR_SHIFT) << ":" << (size >> SECTOR_SHIFT) << " "; + + //Write random block + off_t sizeBdev = ptrOrininal->Size(); + size_t blkszSectors = g_blksz >> SECTOR_SHIFT; + offset = static_cast(std::rand()) * blkszSectors * SECTOR_SIZE; + size = static_cast((std::rand() & 0x1F) + blkszSectors) * blkszSectors * SECTOR_SIZE; + if (offset > (sizeBdev - size)) + offset = offset % (sizeBdev - size); + FillBlocks(ptrGen, ptrOrininal, offset, size); + ss << (offset >> SECTOR_SHIFT) << ":" << (size >> SECTOR_SHIFT) << " "; + + logger.Detail(ss); + +#if 1 + // write some random blocks + logger.Info("- Fill some random blocks"); + ptrGen->IncSequence(); + FillRandomBlocks(ptrGen, ptrOrininal, 2); +#endif + logger.Info("- Check image corruption"); + + CheckAll(ptrGen, ptrImage, testSeqNumber, testSeqTime); + if (ptrGen->Fails() > 0) + { + isErrorFound = true; + + const std::vector& ranges = ptrGen->GetFails(); + corrupts.emplace_back(origDevName, ranges); + + LogCurruptedSectors(ptrImage->Name(), ranges); + } + + std::string errorMessage; + while (ptrSession->GetError(errorMessage)) + { + isErrorFound = true; + logger.Err(errorMessage); + } + + logger.Info("-- Destroy blksnap session"); + ptrSession.reset(); + } + + if (isErrorFound) + throw std::runtime_error("--- Failed: singlethread check corruption ---"); + + logger.Info("--- Success: check corruption ---"); +} + +void CheckCorruption(const std::string& origDevName, + const std::string& diffStorage, + const unsigned long long diffStorageLimit, + const int durationLimitSec, const bool isSync, const int blocksCountMax) { std::vector corrupts; @@ -236,6 +351,7 @@ void CheckCorruption(const std::string& origDevName, const std::string& diffStor logger.Info("version: " + blksnap::Version()); logger.Info("device: " + origDevName); logger.Info("diffStorage: " + diffStorage); + logger.Info("diffStorageLimit: " + std::to_string(diffStorageLimit) + " MiB"); logger.Info("duration: " + std::to_string(durationLimitSec) + " seconds"); auto ptrGen = std::make_shared(false); @@ -257,12 +373,12 @@ void CheckCorruption(const std::string& origDevName, const std::string& diffStor { logger.Info("-- Elapsed time: " + std::to_string(elapsed) + " seconds"); logger.Info("-- Create snapshot"); - auto ptrSession = blksnap::ISession::Create(devices, diffStorage); + auto ptrSession = blksnap::ISession::Create(devices, diffStorage, diffStorageLimit); + auto ptrCbt = blksnap::ICbt::Create(origDevName); { // get CBT information char generationIdStr[64]; - auto ptrCbt = blksnap::ICbt::Create(); - auto ptrCbtInfo = ptrCbt->GetCbtInfo(origDevName); + auto ptrCbtInfo = ptrCbt->GetCbtInfo(); if (ptrCbtInfoPrevious) { @@ -285,7 +401,7 @@ void CheckCorruption(const std::string& origDevName, const std::string& diffStor clock_t testSeqTime = std::clock(); logger.Info("test sequence time " + std::to_string(testSeqTime)); - std::string imageDevName = ptrSession->GetImageDevice(origDevName); + std::string imageDevName = ptrCbt->GetImage();; logger.Info("Found image block device [" + imageDevName + "]"); auto ptrImage = std::make_shared(imageDevName); @@ -354,7 +470,7 @@ void CheckCorruption(const std::string& origDevName, const std::string& diffStor { // Create snapshot and check corrupted ranges and cbt table content. logger.Info("-- Create snapshot at " + std::to_string(std::clock()) + " by CPU clock"); - auto ptrSession = blksnap::ISession::Create(devices, diffStorage); + auto ptrSession = blksnap::ISession::Create(devices, diffStorage, diffStorageLimit); CheckCbtCorrupt(ptrCbtInfoPrevious, corrupts); @@ -453,39 +569,22 @@ void CheckerThreadFunction(std::shared_ptr ptrCtx) ptrCtx->complete = true; }; -void CheckCbtCorrupt(const std::map>& previousCbtInfoMap, +void CheckCbtCorrupt(const std::map>& previousCbtInfoMap, const std::vector& corrupts) { - auto ptrCbt = blksnap::ICbt::Create(); + for (const SCorruptInfo& corruptInfo : corrupts) { + for (const SRange& range : corruptInfo.ranges) { + auto ptrCbt = blksnap::ICbt::Create(corruptInfo.original); - std::map> currentCbtInfoMap; - std::map> currentCbtDataMap; - for (const SCorruptInfo& corruptInfo : corrupts) - { - if (currentCbtInfoMap.count(corruptInfo.original) == 0) - { - auto ptrCbtInfo = ptrCbt->GetCbtInfo(corruptInfo.original); - - currentCbtInfoMap[corruptInfo.original] = ptrCbtInfo; - currentCbtDataMap[corruptInfo.original] = ptrCbt->GetCbtData(ptrCbtInfo); - ; - } - } - - for (const SCorruptInfo& corruptInfo : corrupts) - { - for (const SRange& range : corruptInfo.ranges) - { - const std::string& device = corruptInfo.original; - - IsChangedRegion(previousCbtInfoMap.at(device), currentCbtInfoMap.at(device), currentCbtDataMap.at(device), - range); + IsChangedRegion(previousCbtInfoMap.at(corruptInfo.original), + ptrCbt->GetCbtInfo(), ptrCbt->GetCbtData(), range); } } } void MultithreadCheckCorruption(const std::vector& origDevNames, const std::string& diffStorage, - const int durationLimitSec) + const unsigned long long diffStorageLimit, const int durationLimitSec) { std::map> genMap; std::vector genThreads; @@ -502,6 +601,7 @@ void MultithreadCheckCorruption(const std::vector& origDevNames, co logger.Info(mess); } logger.Info("diffStorage: " + diffStorage); + logger.Info("durationLimitSec: " + std::to_string(durationLimitSec)); logger.Info("duration: " + std::to_string(durationLimitSec) + " seconds"); for (const std::string& origDevName : origDevNames) @@ -529,16 +629,15 @@ void MultithreadCheckCorruption(const std::vector& origDevNames, co logger.Info("-- Elapsed time: " + std::to_string(elapsed) + " seconds"); logger.Info("-- Create snapshot at " + std::to_string(std::clock()) + " by CPU clock"); - auto ptrSession = blksnap::ISession::Create(origDevNames, diffStorage); + auto ptrSession = blksnap::ISession::Create(origDevNames, diffStorage, diffStorageLimit); { // get CBT information char generationIdStr[64]; - auto ptrCbt = blksnap::ICbt::Create(); previousCbtInfoMap.clear(); for (const std::string& origDevName : origDevNames) { - auto ptrCbtInfo = ptrCbt->GetCbtInfo(origDevName); + auto ptrCbtInfo = blksnap::ICbt::Create(origDevName)->GetCbtInfo(); if (!(previousCbtInfoMap.find(origDevName) == previousCbtInfoMap.end())) { @@ -565,7 +664,8 @@ void MultithreadCheckCorruption(const std::vector& origDevNames, co for (const std::string& origDevName : origDevNames) { auto ptrCtx = std::make_shared( - std::make_shared(ptrSession->GetImageDevice(origDevName)), genMap[origDevName]); + std::make_shared(blksnap::ICbt::Create(origDevName)->GetImage()), + genMap[origDevName]); checkerCtxs.push_back(ptrCtx); } @@ -626,7 +726,7 @@ void MultithreadCheckCorruption(const std::vector& origDevNames, co ptrCtx->errorMessages.pop_front(); } logger.Err(ss); - corrupts.emplace_back(ptrSession->GetOriginalDevice(ptrCtx->ptrBdev->Name()), + corrupts.emplace_back(ptrCtx->ptrBdev->Name(), ptrCtx->ptrGen->GetFails()); } ptrCtx->processed = true; @@ -681,7 +781,7 @@ void MultithreadCheckCorruption(const std::vector& origDevNames, co { // Create snapshot and check corrupted ranges and cbt table content. logger.Info("-- Create snapshot at " + std::to_string(std::clock()) + " by CPU clock"); - auto ptrSession = blksnap::ISession::Create(origDevNames, diffStorage); + auto ptrSession = blksnap::ISession::Create(origDevNames, diffStorage, diffStorageLimit); CheckCbtCorrupt(previousCbtInfoMap, corrupts); @@ -702,13 +802,16 @@ void Main(int argc, char* argv[]) desc.add_options() ("help,h", "Show usage information.") - ("log,l", po::value()->default_value("/var/log/blksnap_corrupt.log"),"Detailed log of all transactions.") + ("log,L", po::value()->default_value("/var/log/blksnap_corrupt.log"),"Detailed log of all transactions.") ("device,d", po::value>()->multitoken(), "Device name. It's multitoken for multithread test mod.") ("diff_storage,s", po::value(), - "Directory name for allocating diff storage files.") + "The name of the file to allocate the difference storage.") + ("diff_storage_limit,l", po::value()->default_value("1G"), + "The available limit for the size of the difference storage file. The suffixes M, K and G is allowed.") ("multithread", "Testing mode in which writings to the original devices and their checks are performed in parallel.") + ("simple", "Testing mode in which only one test cycle is performed with a very limited number of checks.") ("duration,u", po::value()->default_value(5), "The test duration limit in minutes.") ("sync", "Use O_SYNC for access to original device.") ("blksz", po::value()->default_value(512), "Align reads and writes to the block size.") @@ -744,6 +847,24 @@ void Main(int argc, char* argv[]) std::string diffStorage = vm["diff_storage"].as(); logger.Info("diff_storage: " + diffStorage); + + unsigned long long diffStorageLimit = 0; + unsigned long long multiple = 1; + std::string limit_str = vm["diff_storage_limit"].as(); + switch (limit_str.back()) + { + case 'G': + multiple *= 1024; + case 'M': + multiple *= 1024; + case 'K': + multiple *= 1024; + limit_str.resize(limit_str.size()-1); + default: + diffStorageLimit = std::stoll(limit_str.c_str()) * multiple; + } + logger.Info("diff_storage_limit: " + std::to_string(diffStorageLimit)); + int duration = vm["duration"].as(); logger.Info("duration: " + std::to_string(duration)); @@ -758,14 +879,19 @@ void Main(int argc, char* argv[]) std::srand(std::time(0)); if (!!vm.count("multithread")) - MultithreadCheckCorruption(origDevNames, diffStorage, duration * 60); - else - { + MultithreadCheckCorruption(origDevNames, diffStorage, + diffStorageLimit, duration * 60); + else { if (origDevNames.size() > 1) logger.Err("In singlethread test mode used only first device."); - CheckCorruption(origDevNames[0], diffStorage, duration * 60, - isSync, blocksCountMax); + if (!!vm.count("simple")) + SimpleCorruption(origDevNames[0], diffStorage, diffStorageLimit, + isSync); + else + CheckCorruption(origDevNames[0], diffStorage, diffStorageLimit, + duration * 60, isSync, blocksCountMax); + } } diff --git a/tests/cpp/diff_storage.cpp b/tests/cpp/diff_storage.cpp index 500fa230..477ef26a 100644 --- a/tests/cpp/diff_storage.cpp +++ b/tests/cpp/diff_storage.cpp @@ -1,7 +1,9 @@ // SPDX-License-Identifier: GPL-2.0+ +#if 0 #include #include #include +#include #include #include #include @@ -298,7 +300,7 @@ static void CheckDiffStorage(const std::string& origDevName, const int durationL clock_t testSeqTime = std::clock(); logger.Info("test sequence time " + std::to_string(testSeqTime)); - std::string imageDevName = ptrSession->GetImageDevice(origDevName); + std::string imageDevName = blksnap::ICbt::Create(origDevName)->GetImage(); logger.Info("Found image block device [" + imageDevName + "]"); auto ptrImage = std::make_shared(imageDevName); @@ -429,3 +431,4 @@ int main(int argc, char* argv[]) return 0; } +#endif diff --git a/tests/functions.sh b/tests/functions.sh index 82e22b52..84c4b423 100755 --- a/tests/functions.sh +++ b/tests/functions.sh @@ -7,8 +7,9 @@ imagefile_make() local FILEPATH=$1 local SIZE=$2 - dd if=/dev/zero of=${FILEPATH} count=${SIZE} bs=1M + dd if=/dev/zero of=${FILEPATH} count=${SIZE} bs=1M status=none mkfs.ext4 ${FILEPATH} + echo "new image file ${FILEPATH}" } imagefile_cleanup() @@ -23,7 +24,12 @@ loop_device_attach() local FILEPATH=$1 local DEVICE="" - DEVICE=$(losetup -f --show ${FILEPATH}) + if [ -z $2 ] + then + DEVICE=$(losetup --sector-size 4096 --direct-io=on -f --show ${FILEPATH}) + else + DEVICE=$(losetup --sector-size $2 --direct-io=on -f --show ${FILEPATH}) + fi echo "${DEVICE}" } @@ -34,27 +40,61 @@ loop_device_detach() losetup -d ${DEVICE} } -generate_files() +generate_file_magic() +{ + local FILE=$1 + local SECTORS=$2 + + rm -f ${FILE} + echo "file: ${FILE} size: ${SECTORS} sectors" + for ((ITER = 0 ; ITER < ${SECTORS} ; ITER++)) + do + echo "BLKSNAP" >> ${FILE} && dd if=/dev/urandom of=${FILE} count=1 bs=504 oflag=append conv=notrunc status=none + done +} + +generate_files_direct() { local TARGET_DIR=$1 local PREFIX=$2 local CNT=$3 - local GEN_FILE_PWD=$(pwd) + local DEVICE=$(findmnt -n -o SOURCE -T ${TARGET_DIR}) + local BLKSZ=$(lsblk -n -o PHY-SEC ${DEVICE} | sed 's/[[:space:]]//g') echo "generate files in ${TARGET_DIR}" - cd ${TARGET_DIR} for ((ITER = 0 ; ITER < ${CNT} ; ITER++)) do - local FILE="./${PREFIX}-${ITER}" + local FILE="${TARGET_DIR}/${PREFIX}-${ITER}" + local SZ=$RANDOM + + let "SZ = ${SZ} % 100 + 8" + echo "file: ${FILE} size: ${SZ} KiB" + let "SZ = ${SZ} * 1024 / ${BLKSZ}" + dd if=/dev/urandom of=${FILE} count=${SZ} bs=${BLKSZ} status=none oflag=direct + md5sum ${FILE} >> ${TARGET_DIR}/hash.md5 + done + echo "generate complete" +} + +generate_files_sync() +{ + local TARGET_DIR=$1 + local PREFIX=$2 + local CNT=$3 + + echo "generate files in ${TARGET_DIR}" + + for ((ITER = 0 ; ITER < ${CNT} ; ITER++)) + do + local FILE="${TARGET_DIR}/${PREFIX}-${ITER}" local SZ=$RANDOM let "SZ = ${SZ} % 100 + 8" - echo "file: ${FILE} size: ${SZ} sectors" - dd if=/dev/urandom of=${FILE} count=${SZ} bs=512 >/dev/null 2>&1 + echo "file: ${FILE} size: ${SZ} KiB" + dd if=/dev/urandom of=${FILE} count=${SZ} bs=1024 status=none oflag=sync md5sum ${FILE} >> ${TARGET_DIR}/hash.md5 done - cd ${GEN_FILE_PWD} echo "generate complete" } @@ -65,29 +105,27 @@ generate_block_MB() local SZ_MB=$3 local ITER_SZ_MB=0 local ITER=0 - local GEN_FILE_PWD=$(pwd) + local TOTAL_MB=0 echo "generate files in ${TARGET_DIR}" - cd ${TARGET_DIR} - while [ ${ITER_SZ_MB} -lt ${SZ_MB} ] do - local FILE="./${PREFIX}-${ITER}" + local FILE="${TARGET_DIR}/${PREFIX}-${ITER}" local SZ=${RANDOM:0:1} SZ=$((SZ + 1)) - echo "file: ${FILE} size: ${SZ} MiB" - dd if=/dev/urandom of=${FILE} count=${SZ} bs=1048576 >/dev/null 2>&1 + echo "file: ${FILE} size: "$((SZ * 1024))" KiB" + dd if=/dev/urandom of=${FILE} count=${SZ} bs=1M oflag=direct status=none md5sum ${FILE} >> ${TARGET_DIR}/hash.md5 ITER_SZ_MB=$((SZ + ITER_SZ_MB)) ITER=$((ITER + 1)) - echo "processed ${ITER_SZ_MB} MiB" + TOTAL_MB=$((TOTAL_MB + SZ)) done - cd ${GEN_FILE_PWD} echo "generate complete" + echo "was created ${TOTAL_MB} MiB" } check_files() @@ -107,3 +145,11 @@ drop_cache() { echo 3 > /proc/sys/vm/drop_caches } + +block_size_mnt() +{ + local DIR=$1 + local DEVICE=$(findmnt -n -o SOURCE -T ${DIR}) + + lsblk -n -o PHY-SEC ${DEVICE} +} diff --git a/tests/manual.sh b/tests/manual.sh index 43360c96..c08f4dd6 100755 --- a/tests/manual.sh +++ b/tests/manual.sh @@ -24,26 +24,19 @@ then fi echo "Difference will be storage on ${DIFF_STORAGE}" -DIFF_STORAGE_SIZE="1GiB" -echo "Difference storage size ${DIFF_STORAGE_SIZE}" - # check module is ready blksnap_version echo "Block device prepared, press ..." read -n 1 -blksnap_snapshot_create "${DEVICE}" - -DIFF_STORAGE=${DIFF_STORAGE}/diff_storage0 -rm -f ${DIFF_STORAGE}/diff_storage0 -fallocate --length ${DIFF_STORAGE_SIZE} ${DIFF_STORAGE} -blksnap_snapshot_append ${DIFF_STORAGE} - +DIFF_STORAGE=${DIFF_STORAGE}/diff_storage +fallocate --length 1GiB ${DIFF_STORAGE} +blksnap_snapshot_create "${DEVICE}" "${DIFF_STORAGE}" "1G" blksnap_snapshot_take echo "Snapshot was token" -blksnap_snapshot_collect_all +blksnap_snapshot_collect echo "Make your manual tests, then press ..." read -n 1 diff --git a/tests/perf-cow.sh b/tests/perf-cow.sh new file mode 100755 index 00000000..d001ec92 --- /dev/null +++ b/tests/perf-cow.sh @@ -0,0 +1,74 @@ +#!/bin/bash -e +# +# SPDX-License-Identifier: GPL-2.0+ + +# +# Requirement: +# The 'TESTDIR' directory and the 'DIFF_STORAGE_DIR' directory +# must be on different block devices. +# + +if [ -z $1 ] +then + TESTDIR="/home/user" +else + TESTDIR=$2 +fi +DEVICE_1=$(findmnt -n -o SOURCE -T ${TESTDIR}) +echo "Test directory ${TESTDIR} on device ${DEVICE_1}" + +if [ -z $2 ] +then + DIFF_STORAGE_DIR="/tmp" +else + DIFF_STORAGE_DIR=$1 +fi + +SOURCE_FILE=${DIFF_STORAGE_DIR}/test_file.dat +if [ ! -f ${SOURCE_FILE} ] +then + dd if=/dev/urandom of=${SOURCE_FILE} count=$((7 * 1024)) bs=1M oflag=direct status=none +fi +TARGET_FILE=${TESTDIR}/test_file.dat + +echo "Diff storage directory ${DIFF_STORAGE_DIR}" + +. ./functions.sh +. ./blksnap.sh + +echo "---" +echo "Perf COW test start" + +blksnap_load + +# check module is ready +blksnap_version + +DIFF_STORAGE="${DIFF_STORAGE_DIR}/diff_storage" +fallocate --length 1024MiB ${DIFF_STORAGE} + +blksnap_snapshot_create "${DEVICE_1}" "${DIFF_STORAGE}" "8G" +blksnap_snapshot_take + +#echo "Write something" > ${MOUNTPOINT_1}/something.txt +echo "Write to original" +# echo "press a key..." +# read -n 1 +perf record -ag dd if=${SOURCE_FILE} of=${TARGET_FILE} bs=1M iflag=direct oflag=direct + +#echo "Destroy snapshot, press ..." +#read -n 1 +blksnap_snapshot_destroy + +echo "Destroy device" +blksnap_detach ${DEVICE_1} + +rm ${TARGET_FILE} +rm ${DIFF_STORAGE} + +blksnap_unload + +echo "Perf COW test finish" +echo "---" + +perf report diff --git a/tests/perf-snapshot_read.sh b/tests/perf-snapshot_read.sh new file mode 100755 index 00000000..4c8ecd46 --- /dev/null +++ b/tests/perf-snapshot_read.sh @@ -0,0 +1,75 @@ +#!/bin/bash -e +# +# SPDX-License-Identifier: GPL-2.0+ + +# +# Requirement: +# The 'TESTDIR' directory and the 'DIFF_STORAGE_DIR' directory +# must be on different block devices. +# + +if [ -z $1 ] +then + TESTDIR="/home/user" +else + TESTDIR=$2 +fi +DEVICE_1=$(findmnt -n -o SOURCE -T ${TESTDIR}) +echo "Test directory ${TESTDIR} on device ${DEVICE_1}" + +if [ -z $2 ] +then + DIFF_STORAGE_DIR="/tmp" +else + DIFF_STORAGE_DIR=$1 +fi + +SOURCE_FILE=${DIFF_STORAGE_DIR}/test_file.dat +if [ ! -f ${SOURCE_FILE} ] +then + dd if=/dev/urandom of=${SOURCE_FILE} count=$((7 * 1024)) bs=1M +fi +TARGET_FILE=${TESTDIR}/test_file.dat + +echo "Diff storage directory ${DIFF_STORAGE_DIR}" + +. ./functions.sh +. ./blksnap.sh + +echo "---" +echo "Perf snapshot read test start" + +blksnap_load + +# check module is ready +blksnap_version + +DIFF_STORAGE="${DIFF_STORAGE_DIR}/diff_storage" +fallocate --length 1024MiB ${DIFF_STORAGE} + +blksnap_snapshot_create "${DEVICE_1}" "${DIFF_STORAGE}" "8G" +blksnap_snapshot_take + +#echo "Write something" > ${MOUNTPOINT_1}/something.txt +echo "Write to original" +dd if=${SOURCE_FILE} of=${TARGET_FILE} bs=1M oflag=direct + +echo "Read from snapshot " +perf record -ag dd if=${TARGET_FILE} of=/dev/null bs=1M iflag=direct + +#echo "Destroy snapshot, press ..." +#read -n 1 +blksnap_snapshot_destroy + +echo "Destroy device" +blksnap_detach ${DEVICE_1} + +rm ${TARGET_FILE} +rm ${DIFF_STORAGE} + +blksnap_unload + +echo "Perf snapshot read test finish" +echo "---" + +perf report diff --git a/tools/blksnap/CMakeLists.txt b/tools/blksnap/CMakeLists.txt index 03622efd..563aa044 100644 --- a/tools/blksnap/CMakeLists.txt +++ b/tools/blksnap/CMakeLists.txt @@ -17,10 +17,8 @@ endif () add_executable(${PROJECT_NAME} main.cpp) set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "blksnap") -target_compile_definitions(${PROJECT_NAME} PUBLIC BLK_SNAP_MODIFICATION) - -set(TOOLS_LIBS blksnap-dev ${LIBUUID_LIBRARY} Boost::filesystem Boost::program_options) +set(TOOLS_LIBS ${LIBUUID_LIBRARY} blksnap-dev Boost::filesystem Boost::program_options) target_link_libraries(${PROJECT_NAME} PRIVATE ${TOOLS_LIBS}) target_include_directories(${PROJECT_NAME} PRIVATE ./) -install(TARGETS ${PROJECT_NAME} DESTINATION sbin) +install(TARGETS ${PROJECT_NAME} DESTINATION /usr/sbin) diff --git a/tools/blksnap/main.cpp b/tools/blksnap/main.cpp index f266c045..0ead2a0d 100644 --- a/tools/blksnap/main.cpp +++ b/tools/blksnap/main.cpp @@ -1,14 +1,15 @@ // SPDX-License-Identifier: GPL-2.0+ #include #include +#include +#include #include +#include +#include #include #include -#include -#include #include #include -#include #include #include #include @@ -17,8 +18,7 @@ #include #include #include -#include -#include +#include #include namespace po = boost::program_options; @@ -77,7 +77,7 @@ namespace CBlksnapFileWrap() : m_blksnapFd(0) { - const char* blksnap_filename = "/dev/" BLK_SNAP_CTL; + const char* blksnap_filename = "/dev/" BLKSNAP_CTL; m_blksnapFd = ::open(blksnap_filename, O_RDWR); if (m_blksnapFd < 0) @@ -98,37 +98,143 @@ namespace }; - static inline struct blk_snap_dev deviceByName(const std::string& name) + class CDeviceCtl { - struct stat st; + public: + CDeviceCtl(const std::string& devicePath) + : m_fd(0) + { + m_fd = ::open(devicePath.c_str(), O_DIRECT, 0600); + if (m_fd < 0) + throw std::system_error(errno, std::generic_category(), "Failed to open block device ["+devicePath+"]"); + }; - if (::stat(name.c_str(), &st)) - throw std::system_error(errno, std::generic_category(), name); + virtual ~CDeviceCtl() + { + if (m_fd > 0) + ::close(m_fd); + }; + + unsigned int Ioctl(const unsigned int cmd, void *param) + { + int ret = ::ioctl(m_fd, cmd, param); + if (ret < 0) + throw std::system_error(errno, std::generic_category()); + return static_cast(ret); + } - struct blk_snap_dev device = { - .mj = major(st.st_rdev), - .mn = minor(st.st_rdev), + private: + int m_fd; + + }; + + class CBlkFilterCtl + { + public: + CBlkFilterCtl(const std::string& devicePath) + : deviceCtl(devicePath) + {}; + + bool Attach() + { + struct blkfilter_name name = { + .name = {'b','l','k','s','n','a','p', '\0'}, + }; + + try + { + deviceCtl.Ioctl(BLKFILTER_ATTACH, &name); + } + catch (std::system_error &ex) + { + if (ex.code() == std::error_code(EALREADY, std::generic_category())) + return false; + + throw std::runtime_error(std::string("Failed to attach filter to the block device: ") + ex.what()); + } + + return true; }; - return device; - } - static inline struct blk_snap_block_range parseRange(const std::string& str) + void Detach() + { + struct blkfilter_ctl name = { + .name = {'b','l','k','s','n','a','p', '\0'}, + }; + + try + { + deviceCtl.Ioctl(BLKFILTER_DETACH, &name); + } + catch (std::exception &ex) + { + throw std::runtime_error( + std::string("Failed to detach filter from the block device: ") + ex.what()); + } + }; + + unsigned int Control(const unsigned int cmd, void *buf, unsigned int len) + { + struct blkfilter_ctl ctl = { + .name = {'b','l','k','s','n','a','p', '\0'}, + .cmd = cmd, + .optlen = len, + .opt = (__u64)buf, + }; + + deviceCtl.Ioctl(BLKFILTER_CTL, &ctl); + + return ctl.optlen; + }; + private: + CDeviceCtl deviceCtl; + }; + + + class OpenFileHolder { - struct blk_snap_block_range range; + public: + OpenFileHolder(const std::string& filename, int flags, int mode = 0) + : m_fd(0) + { + int fd = mode ? ::open(filename.c_str(), flags, mode) : ::open(filename.c_str(), flags); + if (fd < 0) + throw std::system_error(errno, std::generic_category(), "Cannot open file"); + m_fd = fd; + }; + ~OpenFileHolder() + { + if (m_fd) { + ::close(m_fd); + m_fd = 0; + } + }; + + int Get() + { + return m_fd; + } ; + private: + int m_fd; + }; + + static inline struct blksnap_sectors parseRange(const std::string& str) + { + struct blksnap_sectors range; size_t pos; pos = str.find(':'); if (pos == std::string::npos) throw std::invalid_argument("Invalid format of range string."); - range.sector_offset = std::stoull(str.substr(0, pos)); - range.sector_count = std::stoull(str.substr(pos + 1)); + range.offset = std::stoull(str.substr(0, pos)); + range.count = std::stoull(str.substr(pos + 1)); return range; } - static void fiemapStorage(const std::string& filename, struct blk_snap_dev& dev_id, - std::vector& ranges) + static void fiemapStorage(const std::string& filename, std::string& devicePath, + std::vector& ranges) { int ret = 0; const char* errMessage; @@ -142,8 +248,10 @@ namespace throw std::system_error(errno, std::generic_category(), "Failed to get file size."); fileSize = st.st_size; - dev_id.mj = major(st.st_dev); - dev_id.mn = minor(st.st_dev); + devicePath = std::string("/dev/block/") + + std::to_string(major(st.st_dev)) + ":" + + std::to_string(minor(st.st_dev)); + std::cout << "device path: '" << devicePath << "'" << std::endl; fd = ::open(filename.c_str(), O_RDONLY | O_EXCL | O_LARGEFILE); if (fd < 0) @@ -177,7 +285,7 @@ namespace for (int i = 0; i < map->fm_mapped_extents; ++i) { - struct blk_snap_block_range rg; + struct blksnap_sectors rg; struct fiemap_extent* extent = map->fm_extents + i; if (extent->fe_physical & (SECTOR_SIZE - 1)) @@ -187,13 +295,13 @@ namespace goto out; } - rg.sector_offset = extent->fe_physical >> SECTOR_SHIFT; - rg.sector_count = extent->fe_length >> SECTOR_SHIFT; + rg.offset = extent->fe_physical >> SECTOR_SHIFT; + rg.count = extent->fe_length >> SECTOR_SHIFT; ranges.push_back(rg); fileOffset = extent->fe_logical + extent->fe_length; - std::cout << "allocate range: ofs=" << rg.sector_offset << " cnt=" << rg.sector_count << std::endl; + std::cout << "allocate range: ofs=" << rg.offset << " cnt=" << rg.count << std::endl; } } @@ -205,6 +313,16 @@ namespace if (ret) throw std::system_error(ret, std::generic_category(), errMessage); } + + bool isBlockFile(const std::string& path) + { + struct stat st; + + if (::stat(path.c_str(), &st)) + throw std::system_error(errno, std::generic_category(), "Failed to get status for '"+path+"'."); + + return S_ISBLK(st.st_mode); + } } // namespace class IArgsProc @@ -213,7 +331,7 @@ class IArgsProc IArgsProc() { m_desc.add_options() - ("help,h", "[TBD]Print usage for command."); + ("help,h", "Print usage for command."); }; virtual ~IArgsProc(){}; virtual void PrintUsage() const @@ -243,167 +361,131 @@ class IArgsProc std::string m_usage; }; -#ifdef BLK_SNAP_MODIFICATION -#pragma message "BLK_SNAP_MODIFICATION defined" -#endif - class VersionArgsProc : public IArgsProc { public: VersionArgsProc() : IArgsProc() { - m_usage = std::string("[TBD]Print module version."); + m_usage = std::string("Print module version."); m_desc.add_options() -#ifdef BLK_SNAP_MODIFICATION - ("modification,m", "[TBD]Print module modification name.") - ("compatibility,c", "[TBD]Print compatibility flag value in decimal form.") -#endif - ("json,j", "[TBD]Use json format for output."); + ("json,j", "Use json format for output."); }; void Execute(po::variables_map& vm) override { CBlksnapFileWrap blksnapFd; -#ifdef BLK_SNAP_MODIFICATION - bool isModification = (vm.count("modification") != 0); - bool isCompatibility = (vm.count("compatibility") != 0); - - if (isModification || isCompatibility) - { - struct blk_snap_mod param = {0}; - - if (::ioctl(blksnapFd.get(), IOCTL_BLK_SNAP_MOD, ¶m)) - throw std::system_error(errno, std::generic_category(), "Failed to get modification or compatibility information."); - - if (isModification) - std::cout << param.name << std::endl; - - if (isCompatibility) { - if (param.compatibility_flags & (1ull << blk_snap_compat_flag_debug_sector_state)) - std::cout << "debug_sector_state" << std::endl; - - if (param.compatibility_flags & (1ull << blk_snap_compat_flag_setlog)) - std::cout << "setlog" << std::endl; - } - return; - } -#endif - { - struct blk_snap_version param = {0}; + struct blksnap_version param = {0}; - if (::ioctl(blksnapFd.get(), IOCTL_BLK_SNAP_VERSION, ¶m)) - throw std::system_error(errno, std::generic_category(), "Failed to get version."); + if (::ioctl(blksnapFd.get(), IOCTL_BLKSNAP_VERSION, ¶m)) + throw std::system_error(errno, std::generic_category(), "Failed to get version."); - std::cout << param.major << "." << param.minor << "." << param.revision << "." << param.build << std::endl; - } + std::cout << param.major << "." << param.minor << "." << param.revision << "." << param.build << std::endl; }; }; -class TrackerRemoveArgsProc : public IArgsProc +class AttachArgsProc : public IArgsProc { public: - TrackerRemoveArgsProc() + AttachArgsProc() : IArgsProc() { - m_usage = std::string("[TBD]Remove block device from change tracking."); + m_usage = std::string("Attach blksnap tracker to block device."); m_desc.add_options() - ("device,d", po::value(), "[TBD]Device name."); + ("device,d", po::value(), "Device name."); }; void Execute(po::variables_map& vm) override { - CBlksnapFileWrap blksnapFd; - struct blk_snap_tracker_remove param; - if (!vm.count("device")) throw std::invalid_argument("Argument 'device' is missed."); - param.dev_id = deviceByName(vm["device"].as()); - - if (::ioctl(blksnapFd.get(), IOCTL_BLK_SNAP_TRACKER_REMOVE, ¶m)) - throw std::system_error(errno, std::generic_category(), - "Failed to remove block device from change tracking."); + if (CBlkFilterCtl(vm["device"].as()).Attach()) + std::cout << "Attached successfully" << std::endl; + else + std::cout << "Already was attached" << std::endl; }; }; -class TrackerCollectArgsProc : public IArgsProc +class DetachArgsProc : public IArgsProc { public: - TrackerCollectArgsProc() + DetachArgsProc() : IArgsProc() { - m_usage = std::string("[TBD]Collect block devices with change tracking."); + m_usage = std::string("Detach blksnap tracker from block device."); m_desc.add_options() - ("json,j", "[TBD]Use json format for output."); + ("device,d", po::value(), "Device name."); }; void Execute(po::variables_map& vm) override { - CBlksnapFileWrap blksnapFd; - struct blk_snap_tracker_collect param = {0}; - std::vector cbtInfoVector; + if (!vm.count("device")) + throw std::invalid_argument("Argument 'device' is missed."); - if (::ioctl(blksnapFd.get(), IOCTL_BLK_SNAP_TRACKER_COLLECT, ¶m)) - throw std::system_error(errno, std::generic_category(), - "[TBD]Failed to collect block devices with change tracking."); + CBlkFilterCtl(vm["device"].as()).Detach(); + }; +}; - cbtInfoVector.resize(param.count); - param.cbt_info_array = cbtInfoVector.data(); +class CbtInfoArgsProc : public IArgsProc +{ +public: + CbtInfoArgsProc() + : IArgsProc() + { + m_usage = std::string("Get change tracker information."); + m_desc.add_options() + ("device,d", po::value(), "Device name.") + ("json,j", "Use json format for output."); + }; + void Execute(po::variables_map& vm) override + { + if (!vm.count("device")) + throw std::invalid_argument("Argument 'device' is missed."); - if (::ioctl(blksnapFd.get(), IOCTL_BLK_SNAP_TRACKER_COLLECT, ¶m)) - throw std::system_error(errno, std::generic_category(), - "[TBD]Failed to collect block devices with change tracking."); + CBlkFilterCtl ctl(vm["device"].as()); if (vm.count("json")) throw std::invalid_argument("Argument 'json' is not supported yet."); - std::cout << "count=" << param.count << std::endl; + struct blksnap_cbtinfo info; + ctl.Control(blkfilter_ctl_blksnap_cbtinfo, &info, sizeof(info)); + + std::cout << "block_size=" << info.block_size << std::endl; + std::cout << "device_capacity=" << info.device_capacity << std::endl; + std::cout << "block_count=" << info.block_count << std::endl; char generationIdStr[64]; - for (int inx = 0; inx < param.count; inx++) - { - struct blk_snap_cbt_info* it = &cbtInfoVector[inx]; - - uuid_unparse(it->generation_id.b, generationIdStr); - std::cout << "," << std::endl; - std::cout << "device=" << it->dev_id.mj << ":" << it->dev_id.mn << std::endl; - std::cout << "blk_size=" << it->blk_size << std::endl; - std::cout << "device_capacity=" << it->device_capacity << std::endl; - std::cout << "blk_count=" << it->blk_count << std::endl; - std::cout << "generationId=" << std::string(generationIdStr) << std::endl; - std::cout << "snap_number=" << static_cast(it->snap_number) << std::endl; - } - std::cout << "." << std::endl; + uuid_unparse(info.generation_id.b, generationIdStr); + std::cout << "generation_id=" << std::string(generationIdStr) << std::endl; + std::cout << "changes_number=" << static_cast(info.changes_number) << std::endl; }; }; -class TrackerReadCbtMapArgsProc : public IArgsProc +class ReadCbtMapArgsProc : public IArgsProc { public: - TrackerReadCbtMapArgsProc() + ReadCbtMapArgsProc() : IArgsProc() { - m_usage = std::string("[TBD]Read change tracking map."); + m_usage = std::string("Read change tracking map."); m_desc.add_options() - ("device,d", po::value(), "[TBD]Device name.") - ("file,f", po::value(), "[TBD]File name for output.") - ("json,j", "[TBD]Use json format for output."); + ("device,d", po::value(), "Device name.") + ("file,f", po::value(), "File name for output.") + ("json,j", "Use json format for output."); }; void Execute(po::variables_map& vm) override { - CBlksnapFileWrap blksnapFd; - int ret; - struct blk_snap_tracker_read_cbt_bitmap param; - std::vector cbtmap(1024 * 1024); + unsigned int elapsed; if (!vm.count("device")) throw std::invalid_argument("Argument 'device' is missed."); - param.dev_id = deviceByName(vm["device"].as()); - param.offset = 0; - param.length = cbtmap.size(); - param.buff = cbtmap.data(); + CBlkFilterCtl ctl(vm["device"].as()); + + struct blksnap_cbtinfo info; + ctl.Control(blkfilter_ctl_blksnap_cbtinfo, &info, sizeof(info)); + elapsed = info.block_count; if (vm.count("json")) throw std::invalid_argument("Argument 'json' is not supported yet."); @@ -414,174 +496,263 @@ class TrackerReadCbtMapArgsProc : public IArgsProc std::ofstream output; output.open(vm["file"].as(), std::ofstream::out | std::ofstream::binary); - do - { - ret = ::ioctl(blksnapFd.get(), IOCTL_BLK_SNAP_TRACKER_READ_CBT_MAP, ¶m); - if (ret < 0) - throw std::system_error(errno, std::generic_category(), - "[TBD]Failed to read map of difference from change tracking."); - if (ret > 0) - { - output.write((char*)param.buff, ret); - param.offset += ret; - } - } while (ret); + std::vector buf(std::min(32*1024u, elapsed)); + struct blksnap_cbtmap arg = { + .offset = 0, + .buffer = (__u64)buf.data() + }; + while (elapsed) { + arg.length = std::min(static_cast(buf.size()), elapsed); + + ctl.Control(blkfilter_ctl_blksnap_cbtmap, &arg, sizeof(struct blksnap_cbtmap)); + + elapsed -= arg.length; + arg.offset += arg.length; + + output.write(reinterpret_cast(arg.buffer), arg.length); + }; output.close(); }; }; -class TrackerMarkDirtyBlockArgsProc : public IArgsProc +class MarkDirtyBlockArgsProc : public IArgsProc { public: - TrackerMarkDirtyBlockArgsProc() + MarkDirtyBlockArgsProc() : IArgsProc() { - m_usage = std::string("[TBD]Mark blocks as changed in change tracking map."); + m_usage = std::string("Mark blocks as changed in change tracking map."); m_desc.add_options() - ("file,f", po::value(), "[TBD]File name with dirty blocks.") - ("device,d", po::value(), "[TBD]Device name.") - ("ranges,r", po::value>()->multitoken(), "[TBD]Sectors range in format 'sector:count'. It's multitoken argument."); + ("file,f", po::value(), "File name with dirty blocks.") + ("device,d", po::value(), "Device name.") + ("ranges,r", po::value>()->multitoken(), "Sectors range in format 'sector:count'. It's multitoken argument."); }; void Execute(po::variables_map& vm) override { - CBlksnapFileWrap blksnapFd; - struct blk_snap_tracker_mark_dirty_blocks param; - std::vector ranges; + std::string devicePath; + std::vector ranges; if (vm.count("file")) { - fiemapStorage(vm["file"].as(), param.dev_id, ranges); + fiemapStorage(vm["file"].as(), devicePath, ranges); } else { if (!vm.count("device")) throw std::invalid_argument("Argument 'device' is missed."); - param.dev_id = deviceByName(vm["device"].as()); + + devicePath = vm["device"].as(); if (!vm.count("ranges")) throw std::invalid_argument("Argument 'ranges' is missed."); + for (const std::string& range : vm["ranges"].as>()) ranges.push_back(parseRange(range)); } - param.count = ranges.size(); - param.dirty_blocks_array = ranges.data(); - - if (::ioctl(blksnapFd.get(), IOCTL_BLK_SNAP_TRACKER_MARK_DIRTY_BLOCKS, ¶m)) - throw std::system_error(errno, std::generic_category(), - "[TBD]Failed to mark dirty blocks in change tracking map."); + CBlkFilterCtl ctl(devicePath); + struct blksnap_cbtdirty arg = { + .count = static_cast(ranges.size()), + .dirty_sectors = (__u64)ranges.data() + }; + ctl.Control(blkfilter_ctl_blksnap_cbtdirty, &arg, sizeof(arg)); } }; -class SnapshotCreateArgsProc : public IArgsProc +class SnapshotInfoArgsProc : public IArgsProc { public: - SnapshotCreateArgsProc() + SnapshotInfoArgsProc() : IArgsProc() { - m_usage = std::string("[TBD]Create snapshot object structure."); + m_usage = std::string("Get information about block device snapshot image."); m_desc.add_options() - ("device,d", po::value>()->multitoken(), "[TBD]Device for snapshot. It's multitoken argument."); + ("device,d", po::value(), "Device name.") + ("field,f", po::value(), "Out only selected field.") + ("json,j", "Use json format for output."); }; void Execute(po::variables_map& vm) override { - CBlksnapFileWrap blksnapFd; - struct blk_snap_snapshot_create param = {0}; - std::vector devices; - if (!vm.count("device")) throw std::invalid_argument("Argument 'device' is missed."); - for (const std::string& name : vm["device"].as>()) - devices.push_back(deviceByName(name)); - param.count = devices.size(); - param.dev_id_array = devices.data(); + CBlkFilterCtl ctl(vm["device"].as()); + + if (vm.count("json")) + throw std::invalid_argument("Argument 'json' is not supported yet."); - if (::ioctl(blksnapFd.get(), IOCTL_BLK_SNAP_SNAPSHOT_CREATE, ¶m)) - throw std::system_error(errno, std::generic_category(), "[TBD]Failed to create snapshot object."); + struct blksnap_snapshotinfo param; + ctl.Control(blkfilter_ctl_blksnap_snapshotinfo, ¶m, sizeof(param)); - char idStr[64]; - uuid_unparse(param.id.b, idStr); + std::vector image(IMAGE_DISK_NAME_LEN + 1); + strncpy(image.data(), reinterpret_cast(param.image), IMAGE_DISK_NAME_LEN); - std::cout << std::string(idStr) << std::endl; + if (vm.count("field")) { + std::string field=vm["field"].as(); + + if (field == "image") + std::cout << std::string("/dev/") + std::string(image.data()) << std::endl; + else if (field == "error_code") + std::cout << param.error_code << std::endl; + else + throw std::invalid_argument("Value '"+field+"' for argument '--field' is not supported."); + } + else + { + std::cout << "error_code=" << param.error_code << std::endl; + std::cout << "image=" << std::string("/dev/") + std::string(image.data()) << std::endl; + } }; }; -class SnapshotDestroyArgsProc : public IArgsProc +static inline void SnapshotAdd(const uuid_t& id, const std::string& devicePath) +{ + struct blksnap_snapshotadd param; + bool retry = false; + + uuid_copy(param.id.b, id); + CBlkFilterCtl ctl(devicePath); + + do { + try + { + ctl.Control(blkfilter_ctl_blksnap_snapshotadd, ¶m, sizeof(param)); + retry = false; + } + catch(std::system_error &ex) + { + if ((ex.code() == std::error_code(ENOENT, std::generic_category())) && !retry) + retry = true; + else + throw ex; + } + + if (retry) + ctl.Attach(); + } while (retry); +} + +class SnapshotAddArgsProc : public IArgsProc { public: - SnapshotDestroyArgsProc() + SnapshotAddArgsProc() : IArgsProc() { - m_usage = std::string("[TBD]Release snapshot and destroy snapshot object."); + m_usage = std::string("Add device for snapshot."); m_desc.add_options() - ("id,i", po::value(), "[TBD]Snapshot uuid."); + ("device,d", po::value(), "Device name.") + ("id,i", po::value(), "Snapshot uuid."); }; void Execute(po::variables_map& vm) override { - CBlksnapFileWrap blksnapFd; - struct blk_snap_snapshot_destroy param; + if (!vm.count("device")) + throw std::invalid_argument("Argument 'device' is missed."); if (!vm.count("id")) throw std::invalid_argument("Argument 'id' is missed."); - Uuid id(vm["id"].as()); - uuid_copy(param.id.b, id.Get()); - - if (::ioctl(blksnapFd.get(), IOCTL_BLK_SNAP_SNAPSHOT_DESTROY, ¶m)) - throw std::system_error(errno, std::generic_category(), "[TBD]Failed to destroy snapshot."); + SnapshotAdd(Uuid(vm["id"].as()).Get(), vm["device"].as()); }; }; -class SnapshotAppendStorageArgsProc : public IArgsProc +class SnapshotCreateArgsProc : public IArgsProc { public: - SnapshotAppendStorageArgsProc() + SnapshotCreateArgsProc() : IArgsProc() { - m_usage = std::string("[TBD]Append space in difference storage for snapshot."); + m_usage = std::string("Create snapshot."); m_desc.add_options() - ("id,i", po::value(), "[TBD]Snapshot uuid.") - ("device,d", po::value(), "[TBD]Device name.") - ("range,r", po::value>()->multitoken(), "[TBD]Sectors range in format 'sector:count'. It's multitoken argument.") - ("file,f", po::value(), "[TBD]File for diff storage instead --device."); + ("device,d", po::value>()->multitoken(), "Device name for snapshot. It's multitoken argument.") + ("file,f", po::value(), "File for difference storage.") + ("limit,l", po::value(), "The allowable limit for the size of the difference storage file. The suffixes M, K and G is allowed."); }; void Execute(po::variables_map& vm) override { CBlksnapFileWrap blksnapFd; - struct blk_snap_snapshot_append_storage param; - std::vector ranges; - struct blk_snap_dev dev_id = {0}; + struct blksnap_snapshot_create param = {0}; - if (!vm.count("id")) - throw std::invalid_argument("Argument 'id' is missed."); + if (!vm.count("file")) + throw std::invalid_argument("Argument 'file' is missed."); - Uuid id(vm["id"].as()); - uuid_copy(param.id.b, id.Get()); + int flags = O_RDWR | O_EXCL; + std::string filename = vm["file"].as(); + + if (fs::is_directory(filename)) { + /* A temporary file (flag O_TMPFILE) is being created. + * Not available for all file systems. + * See: man open. + */ + flags |= O_TMPFILE; + } else if (!fs::is_regular_file(filename) && !isBlockFile(filename)) + throw std::invalid_argument("The value '"+filename+"' for the argument '--file' should have been either the name of a regular file, the name of a block device, or the directory for creating a temporary file."); + + OpenFileHolder fd(filename, flags, 0600); + + unsigned long long limit = 0; + unsigned long long multiple = 1; + if (!vm.count("limit")) + throw std::invalid_argument("Argument 'limit' is missed."); + std::string limit_str = vm["limit"].as(); + switch (limit_str.back()) + { + case 'G': + multiple *= 1024; + case 'M': + multiple *= 1024; + case 'K': + multiple *= 1024; + limit_str.back() = '\0'; + default: + limit = std::stoll(limit_str.c_str()) * multiple; + } - if (vm.count("file")) - fiemapStorage(vm["file"].as(), dev_id, ranges); - else + param.diff_storage_limit_sect = limit / 512; + param.diff_storage_fd = fd.Get(); + if (::ioctl(blksnapFd.get(), IOCTL_BLKSNAP_SNAPSHOT_CREATE, ¶m)) + throw std::system_error(errno, std::generic_category(), "Failed to create snapshot object."); + + Uuid id(param.id.b); + std::cout << id.ToString() << std::endl; + + if (vm.count("device")) { - if (!vm.count("device")) - throw std::invalid_argument("Argument 'device' is missed."); - dev_id = deviceByName(vm["device"].as()); + std::vector devices = vm["device"].as>(); - if (!vm.count("ranges")) - throw std::invalid_argument("Argument 'ranges' is missed."); - for (const std::string& range : vm["range"].as>()) - ranges.push_back(parseRange(range)); + for (const std::string& devicePath : devices) + SnapshotAdd(id.Get(), devicePath); } - param.dev_id = dev_id; - param.count = ranges.size(); - param.ranges = ranges.data(); - if (::ioctl(blksnapFd.get(), IOCTL_BLK_SNAP_SNAPSHOT_APPEND_STORAGE, ¶m)) - throw std::system_error(errno, std::generic_category(), "[TBD]Failed to append storage for snapshot."); + }; +}; + +class SnapshotDestroyArgsProc : public IArgsProc +{ +public: + SnapshotDestroyArgsProc() + : IArgsProc() + { + m_usage = std::string("Release snapshot and destroy snapshot object."); + m_desc.add_options() + ("id,i", po::value(), "Snapshot uuid."); + }; + + void Execute(po::variables_map& vm) override + { + CBlksnapFileWrap blksnapFd; + struct blksnap_uuid param; + + if (!vm.count("id")) + throw std::invalid_argument("Argument 'id' is missed."); + + uuid_copy(param.b, Uuid(vm["id"].as()).Get()); + + if (::ioctl(blksnapFd.get(), IOCTL_BLKSNAP_SNAPSHOT_DESTROY, ¶m)) + throw std::system_error(errno, std::generic_category(), "Failed to destroy snapshot."); }; }; @@ -591,24 +762,23 @@ class SnapshotTakeArgsProc : public IArgsProc SnapshotTakeArgsProc() : IArgsProc() { - m_usage = std::string("[TBD]Take snapshot."); + m_usage = std::string("Take snapshot."); m_desc.add_options() - ("id,i", po::value(), "[TBD]Snapshot uuid."); + ("id,i", po::value(), "Snapshot uuid."); }; void Execute(po::variables_map& vm) override { CBlksnapFileWrap blksnapFd; - struct blk_snap_snapshot_take param; + struct blksnap_uuid param; if (!vm.count("id")) throw std::invalid_argument("Argument 'id' is missed."); - Uuid id(vm["id"].as()); - uuid_copy(param.id.b, id.Get()); + uuid_copy(param.b, Uuid(vm["id"].as()).Get()); - if (::ioctl(blksnapFd.get(), IOCTL_BLK_SNAP_SNAPSHOT_TAKE, ¶m)) - throw std::system_error(errno, std::generic_category(), "[TBD]Failed to take snapshot."); + if (::ioctl(blksnapFd.get(), IOCTL_BLKSNAP_SNAPSHOT_TAKE, ¶m)) + throw std::system_error(errno, std::generic_category(), "Failed to take snapshot"); }; }; @@ -618,29 +788,28 @@ class SnapshotWaitEventArgsProc : public IArgsProc SnapshotWaitEventArgsProc() : IArgsProc() { - m_usage = std::string("[TBD]Wait and read event from snapshot."); + m_usage = std::string("Wait and read event from snapshot."); m_desc.add_options() - ("id,i", po::value(), "[TBD]Snapshot uuid.") - ("timeout,t", po::value(), "[TBD]The allowed waiting time for the event in milliseconds.") - ("json,j", "[TBD]Use json format for output."); + ("id,i", po::value(), "Snapshot uuid.") + ("timeout,t", po::value(), "The allowed waiting time for the event in milliseconds.") + ("json,j", "Use json format for output."); }; void Execute(po::variables_map& vm) override { CBlksnapFileWrap blksnapFd; - struct blk_snap_snapshot_event param; + struct blksnap_snapshot_event param; if (!vm.count("id")) throw std::invalid_argument("Argument 'id' is missed."); - Uuid id(vm["id"].as()); - uuid_copy(param.id.b, id.Get()); + uuid_copy(param.id.b, Uuid(vm["id"].as()).Get()); if (!vm.count("timeout")) throw std::invalid_argument("Argument 'timeout' is missed."); param.timeout_ms = std::stoi(vm["timeout"].as()); - if (::ioctl(blksnapFd.get(), IOCTL_BLK_SNAP_SNAPSHOT_WAIT_EVENT, ¶m)) + if (::ioctl(blksnapFd.get(), IOCTL_BLKSNAP_SNAPSHOT_WAIT_EVENT, ¶m)) { if (errno == ENOENT) { @@ -656,8 +825,14 @@ class SnapshotWaitEventArgsProc : public IArgsProc std::cout << "result=interrupted" << std::endl; } - else - throw std::system_error(errno, std::generic_category(), "[TBD]Failed to get event from snapshot."); + else if (errno == ESRCH) + { + if (vm.count("json")) + throw std::invalid_argument("Argument 'json' is not supported yet."); + + std::cout << "result=not found" << std::endl; + } else + throw std::system_error(errno, std::generic_category(), "Failed to get event from snapshot"); } else { @@ -669,11 +844,7 @@ class SnapshotWaitEventArgsProc : public IArgsProc switch (param.code) { - case blk_snap_event_code_low_free_space: - std::cout << "event=low_free_space" << std::endl; - std::cout << "requested_nr_sect=" << *(__u64*)(param.data) << std::endl; - break; - case blk_snap_event_code_corrupted: + case blksnap_event_code_corrupted: std::cout << "event=corrupted" << std::endl; break; default: @@ -683,191 +854,87 @@ class SnapshotWaitEventArgsProc : public IArgsProc }; }; + class SnapshotCollectArgsProc : public IArgsProc { private: - void CollectSnapshots(const CBlksnapFileWrap& blksnapFd, std::vector& ids) + void CollectSnapshots(std::vector& ids) { - struct blk_snap_snapshot_collect param = {0}; + CBlksnapFileWrap blksnapFd; + struct blksnap_snapshot_collect param = {0}; - if (::ioctl(blksnapFd.get(), IOCTL_BLK_SNAP_SNAPSHOT_COLLECT, ¶m)) - throw std::system_error(errno, std::generic_category(), "[TBD]Failed to get list of active snapshots."); + if (::ioctl(blksnapFd.get(), IOCTL_BLKSNAP_SNAPSHOT_COLLECT, ¶m)) + throw std::system_error(errno, std::generic_category(), "Failed to get list of active snapshots"); if (param.count == 0) return; - std::vector id_array(param.count); - param.ids = id_array.data(); + std::vector id_array(param.count); + param.ids = (__u64)id_array.data(); - if (::ioctl(blksnapFd.get(), IOCTL_BLK_SNAP_SNAPSHOT_COLLECT, ¶m)) - throw std::system_error(errno, std::generic_category(), "[TBD]Failed to get list of snapshots."); + if (::ioctl(blksnapFd.get(), IOCTL_BLKSNAP_SNAPSHOT_COLLECT, ¶m)) + throw std::system_error(errno, std::generic_category(), "Failed to get list of snapshots"); for (size_t inx = 0; inx < param.count; inx++) ids.emplace_back(id_array[inx].b); }; - void CollectImages(const CBlksnapFileWrap& blksnapFd, const Uuid& id, std::vector& imageInfoVector) - { - struct blk_snap_snapshot_collect_images param = {0}; - - uuid_copy(param.id.b, id.Get()); - if (::ioctl(blksnapFd.get(), IOCTL_BLK_SNAP_SNAPSHOT_COLLECT_IMAGES, ¶m)) - throw std::system_error(errno, std::generic_category(), - "[TBD]Failed to get device collection for snapshot images."); - - if (param.count == 0) - return; - - imageInfoVector.resize(param.count); - param.image_info_array = imageInfoVector.data(); - - if (::ioctl(blksnapFd.get(), IOCTL_BLK_SNAP_SNAPSHOT_COLLECT_IMAGES, ¶m)) - throw std::system_error(errno, std::generic_category(), - "[TBD]Failed to get device collection for snapshot images."); - }; - public: SnapshotCollectArgsProc() : IArgsProc() { - m_usage = std::string("[TBD]Get collection of devices and his snapshot images."); + m_usage = std::string("Get collection of snapshots."); m_desc.add_options() - ("id,i", po::value(), "[TBD]Optional parameter snapshot uuid.") - ("json,j", "[TBD]Use json format for output."); + ("json,j", "Use json format for output."); }; void Execute(po::variables_map& vm) override { - CBlksnapFileWrap blksnapFd; - std::vector ids; - if (vm.count("json")) throw std::invalid_argument("Argument 'json' is not supported yet."); - if (vm.count("id")) - ids.emplace_back(vm["id"].as()); - else - CollectSnapshots(blksnapFd, ids); - + std::vector ids; + CollectSnapshots(ids); for (const Uuid& id : ids) - { - std::cout << "snapshot=" << id.ToString() << std::endl; - - std::vector imageInfoVector; - CollectImages(blksnapFd, id, imageInfoVector); - - std::cout << "count=" << imageInfoVector.size() << std::endl; - for (struct blk_snap_image_info& info : imageInfoVector) - { - std::cout << "," << std::endl; - std::cout << "orig_dev_id=" << info.orig_dev_id.mj << ":" << info.orig_dev_id.mn << std::endl; - std::cout << "image_dev_id=" << info.image_dev_id.mj << ":" << info.image_dev_id.mn << std::endl; - } - std::cout << "." << std::endl; - } + std::cout << id.ToString() << " " << std::endl; + std::cout << std::endl; }; }; -class StretchSnapshotArgsProc : public IArgsProc +class SnapshotWatcherArgsProc : public IArgsProc { private: Uuid m_id; std::string m_path; - std::vector m_allocated_sectFiles; int m_counter; - unsigned long long m_allocated_sect; - unsigned long long m_limit_sect; private: - void ProcessLowFreeSpace(const CBlksnapFileWrap& blksnapFd, unsigned int time_label, struct blk_snap_event_low_free_space* data) + void ProcessEventCorrupted(unsigned int time_label, struct blksnap_event_corrupted* data) { - std::string filename; - int fd; - - std::cout << time_label << " - Low free space in diff storage. Requested " << data->requested_nr_sect - << " sectors." << std::endl; - - if (m_allocated_sect > m_limit_sect) - { - std::cerr << "The diff storage limit has been achieved." << std::endl; - return; - } - - fs::path filepath(m_path); - filepath += "diff_storage#"; - filepath += std::to_string(m_counter++); - if (fs::exists(filepath)) - fs::remove(filepath); - filename = filepath.string(); - - fd = ::open(filename.c_str(), O_CREAT | O_RDWR | O_EXCL | O_LARGEFILE, 0600); - if (fd < 0) - throw std::system_error(errno, std::generic_category(), "[TBD]Failed to create file for diff storage."); - m_allocated_sectFiles.push_back(filename); - - if (::fallocate64(fd, 0, 0, data->requested_nr_sect * SECTOR_SIZE)) - { - int err = errno; - - ::close(fd); - throw std::system_error(err, std::generic_category(), "[TBD]Failed to allocate file for diff storage."); - } - ::close(fd); - m_allocated_sect += data->requested_nr_sect; - - std::vector ranges; - struct blk_snap_dev dev_id = {0}; - fiemapStorage(filename, dev_id, ranges); - - struct blk_snap_snapshot_append_storage param; - uuid_copy(param.id.b, m_id.Get()); - param.dev_id = dev_id; - param.count = ranges.size(); - param.ranges = ranges.data(); - - if (::ioctl(blksnapFd.get(), IOCTL_BLK_SNAP_SNAPSHOT_APPEND_STORAGE, ¶m)) - throw std::system_error(errno, std::generic_category(), "[TBD]Failed to append storage for snapshot."); + std::cout << time_label << " - The snapshot was corrupted for device [" << data->dev_id_mj << ":" + << data->dev_id_mn << "] with error \"" << std::strerror(data->err_code) << "\"." << std::endl; }; - - void ProcessEventCorrupted(unsigned int time_label, struct blk_snap_event_corrupted* data) - { - std::cout << time_label << " - The snapshot was corrupted for device [" << data->orig_dev_id.mj << ":" - << data->orig_dev_id.mn << "] with error \"" << std::strerror(data->err_code) << "\"." << std::endl; - }; - public: - StretchSnapshotArgsProc() + SnapshotWatcherArgsProc() : IArgsProc() { - m_usage = std::string("[TBD]Start stretch snapshot service."); + m_usage = std::string("Start snapshot watcher service."); m_desc.add_options() - ("id,i", po::value(), "[TBD]Snapshot uuid.") - ("path,p", po::value(), "[TBD]Path for diff storage files.") - ("limit,l", po::value(), "[TBD]Available diff storage size in MiB."); + ("id,i", po::value(), "Snapshot uuid."); }; void Execute(po::variables_map& vm) override { CBlksnapFileWrap blksnapFd; bool terminate = false; - struct blk_snap_snapshot_event param; + struct blksnap_snapshot_event param; if (!vm.count("id")) throw std::invalid_argument("Argument 'id' is missed."); m_id.FromString(vm["id"].as()); - if (!vm.count("path")) - throw std::invalid_argument("Argument 'path' is missed."); - m_path = vm["path"].as(); - - if (vm.count("limit")) - m_limit_sect = (1024ULL * 1024 / SECTOR_SIZE) * vm["limit"].as(); - else - m_limit_sect = -1ULL; - - std::cout << "Stretch snapshot service started." << std::endl; - + std::cout << "Start snapshot watcher." << std::endl; try { uuid_copy(param.id.b, m_id.Get()); @@ -875,132 +942,65 @@ class StretchSnapshotArgsProc : public IArgsProc m_counter = 0; while (!terminate) { - if (::ioctl(blksnapFd.get(), IOCTL_BLK_SNAP_SNAPSHOT_WAIT_EVENT, ¶m)) + if (::ioctl(blksnapFd.get(), IOCTL_BLKSNAP_SNAPSHOT_WAIT_EVENT, ¶m)) { int err = errno; if ((err == ENOENT) || (err == EINTR)) continue; - throw std::system_error(err, std::generic_category(), "[TBD]Failed to get event from snapshot."); + throw std::system_error(err, std::generic_category(), "Failed to get event from snapshot"); } switch (param.code) { - case blk_snap_event_code_low_free_space: - ProcessLowFreeSpace(blksnapFd, param.time_label, (struct blk_snap_event_low_free_space*)param.data); - break; - case blk_snap_event_code_corrupted: - ProcessEventCorrupted(param.time_label, (struct blk_snap_event_corrupted*)param.data); + case blksnap_event_code_corrupted: + ProcessEventCorrupted(param.time_label, + (struct blksnap_event_corrupted*)param.data); terminate = true; break; default: std::cout << param.time_label << " - unsupported event #" << param.code << "." << std::endl; } } - - for (const std::string& filename : m_allocated_sectFiles) - if (::remove(filename.c_str())) - std::cout << "Failed to cleanup diff storage file \"" << filename << "\". " << std::strerror(errno) - << std::endl; + } + catch (std::system_error& ex){ + if (ex.code() == std::error_code(ESRCH, std::generic_category())) + std::cout << "The snapshot no longer exists." << std::endl; + else { + std::cerr << ex.what() << std::endl; + throw std::runtime_error("Snapshot watcher failed."); + } } catch (std::exception& ex) { std::cerr << ex.what() << std::endl; - throw std::runtime_error("Stretch snapshot service failed."); + throw std::runtime_error("Snapshot watcher failed."); } - std::cout << "Stretch snapshot service finished." << std::endl; + std::cout << "Snapshot watcher finished." << std::endl; }; }; -#ifdef BLK_SNAP_MODIFICATION -static int CalculateTzMinutesWest() -{ - time_t local_time = time(NULL); - - struct tm *gm_tm = gmtime(&local_time); - gm_tm->tm_isdst = -1; - time_t gm_time = mktime(gm_tm); - - return static_cast(difftime(local_time, gm_time) / 60); -} - -static inline int getTzMinutesWest() -{ - static int tzMinutesWest = -1; - - if (tzMinutesWest != -1) - return tzMinutesWest; - else - return (tzMinutesWest = CalculateTzMinutesWest()); -} - -class SetlogArgsProc : public IArgsProc -{ -public: - SetlogArgsProc() - : IArgsProc() - { - m_usage = std::string("Set module additional logging."); - m_desc.add_options() - ("level,l", po::value()->default_value(6), "3 - only errors, 4 - warnings, 5 - notice, 6 - info (default), 7 - debug.") - ("path,p", po::value(), "Full path for log file.") - ("disable", "Disable additional logging."); - }; - void Execute(po::variables_map& vm) override - { - CBlksnapFileWrap blksnapFd; - struct blk_snap_setlog param = {0}; - - if (vm.count("disable")) { - param.level = -1; - param.filepath = NULL; - if (::ioctl(blksnapFd.get(), IOCTL_BLK_SNAP_SETLOG, ¶m)) - throw std::system_error(errno, std::generic_category(), "Failed to disable logging."); - return; - } - - param.tz_minuteswest = getTzMinutesWest(); - param.level = vm["level"].as(); - - if (!vm.count("path")) - throw std::invalid_argument("Argument 'path' is missed."); - - std::string path = vm["path"].as(); - - std::vector<__u8> filepathBuffer(path.size()); - memcpy(filepathBuffer.data(), path.c_str(), path.size()); - - param.filepath = filepathBuffer.data(); - param.filepath_size = filepathBuffer.size(); - - if (::ioctl(blksnapFd.get(), IOCTL_BLK_SNAP_SETLOG, ¶m)) - throw std::system_error(errno, std::generic_category(), "Failed to set logging."); - }; -}; -#endif - static std::map> argsProcMap{ {"version", std::make_shared()}, - {"tracker_remove", std::make_shared()}, - {"tracker_collect", std::make_shared()}, - {"tracker_readcbtmap", std::make_shared()}, - {"tracker_markdirtyblock", std::make_shared()}, + {"attach", std::make_shared()}, + {"detach", std::make_shared()}, + {"cbtinfo", std::make_shared()}, + {"readcbtmap", std::make_shared()}, + {"markdirtyblock", std::make_shared()}, + {"snapshot_info", std::make_shared()}, + {"snapshot_add", std::make_shared()}, {"snapshot_create", std::make_shared()}, {"snapshot_destroy", std::make_shared()}, - {"snapshot_appendstorage", std::make_shared()}, {"snapshot_take", std::make_shared()}, {"snapshot_waitevent", std::make_shared()}, {"snapshot_collect", std::make_shared()}, - {"stretch_snapshot", std::make_shared()}, -#ifdef BLK_SNAP_MODIFICATION - {"setlog", std::make_shared()}, -#endif + {"snapshot_watcher", std::make_shared()}, }; static void printUsage() { - std::cout << "[TBD]Usage:" << std::endl; + std::cout << "Usage:" << std::endl; std::cout << "--help, -h or help:" << std::endl; std::cout << "\tPrint this usage." << std::endl; std::cout << " [arguments]:" << std::endl; @@ -1017,7 +1017,7 @@ static void printUsage() static void process(int argc, char** argv) { if (argc < 2) - throw std::runtime_error("[TBD]Command not found."); + throw std::runtime_error("Command not found."); std::string commandName(argv[1]); diff --git a/tools/include_guard_gen.sh b/tools/include_guard_gen.sh deleted file mode 100755 index 6739be1a..00000000 --- a/tools/include_guard_gen.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash -e - -FILE=$(basename $1) -GUARD=${FILE/"."/"_"} - -if [ -z $2 ] -then - GUARD="__LINUX_"${GUARD^^} -else - GUARD="__$2_"${GUARD^^} -fi - -sed -i -e "s/#pragma once/#ifndef ${GUARD}\n#define ${GUARD}\n/g" $1 -echo "#endif /* ${GUARD} */" >> $1