diff options
author | Michael Scherle | 2022-02-17 18:58:25 +0100 |
---|---|---|
committer | Simon Rettberg | 2024-05-13 18:25:14 +0200 |
commit | d92d1d3faf7cde517d8c3fe914301eb5aa872f62 (patch) | |
tree | ff2eaa0a640223b36dfabd3ffe29772e44e50733 | |
parent | [KERNEL] Fix build on 5.15 kernels >= 5.15.132 (diff) | |
download | dnbd3-d92d1d3faf7cde517d8c3fe914301eb5aa872f62.tar.gz dnbd3-d92d1d3faf7cde517d8c3fe914301eb5aa872f62.tar.xz dnbd3-d92d1d3faf7cde517d8c3fe914301eb5aa872f62.zip |
[FUSE] basic cow implementation & rudimentary tests
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rw-r--r-- | inc/dnbd3/config.h | 16 | ||||
-rw-r--r-- | src/CMakeLists.txt | 4 | ||||
-rw-r--r-- | src/cowtest/CMakeLists.txt | 27 | ||||
-rw-r--r-- | src/cowtest/main.c | 1165 | ||||
-rw-r--r-- | src/cowtest/readme.md | 105 | ||||
-rw-r--r-- | src/fuse/CMakeLists.txt | 16 | ||||
-rw-r--r-- | src/fuse/connection.c | 49 | ||||
-rw-r--r-- | src/fuse/connection.h | 11 | ||||
-rw-r--r-- | src/fuse/cowDoc/img/datastructure.jpg | bin | 0 -> 397688 bytes | |||
-rw-r--r-- | src/fuse/cowDoc/img/readrequest.svg | 4 | ||||
-rw-r--r-- | src/fuse/cowDoc/readme.md | 341 | ||||
-rw-r--r-- | src/fuse/cowfile.c | 1535 | ||||
-rw-r--r-- | src/fuse/cowfile.h | 136 | ||||
-rw-r--r-- | src/fuse/main.c | 227 | ||||
-rw-r--r-- | src/fuse/main.h | 39 |
17 files changed, 3607 insertions, 70 deletions
@@ -4,3 +4,4 @@ build/ *.swp .autotools .idea +.vscode/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 69459dd..0e473fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,7 @@ project(dnbd3 OPTION(DNBD3_KERNEL_MODULE "Build the dnbd3 Linux kernel module" ON) OPTION(DNBD3_BENCHMARK "Enable build of dnbd3-bench" OFF) OPTION(DNBD3_CLIENT_FUSE "Enable build of dnbd3-fuse" ON) +OPTION(DNBD3_CLIENT_FUSE_COW_TEST "Enable build of dnbd3-fuse-cow-test" OFF) OPTION(DNBD3_SERVER "Enable build of dnbd3-server" ON) OPTION(DNBD3_SERVER_FUSE "Enable FUSE-Integration for dnbd3-server" OFF) OPTION(DNBD3_SERVER_AFL "Build dnbd3-server for usage with afl-fuzz" OFF) diff --git a/inc/dnbd3/config.h b/inc/dnbd3/config.h index eb4b8b1..df9d0e0 100644 --- a/inc/dnbd3/config.h +++ b/inc/dnbd3/config.h @@ -40,4 +40,20 @@ // +++++ Block Device +++++ #define DNBD3_BLOCK_SIZE ((uint64_t)4096) // NEVER CHANGE THIS OR THE WORLD WILL END! +// +++++ COW +++++ +#define COW_BITFIELD_SIZE 40 // NEVER CHANGE THIS OR THE WORLD WILL ALSO END! +#define COW_FILE_META_MAGIC_VALUE ((uint64_t)0xEBE44D6E72F7825E) // Magic Value to recognize a Cow meta file +#define COW_FILE_DATA_MAGIC_VALUE ((uint64_t)0xEBE44D6E72F7825F) // Magic Value to recognize a Cow data file +#define COW_MIN_UPLOAD_DELAY 60 // in seconds +#define COW_STATS_UPDATE_TIME 5 // time in seconds the cow status files gets updated (while uploading blocks) +#define COW_MAX_PARALLEL_UPLOADS 10 // maximum number of parallel uploads +#define COW_MAX_PARALLEL_BACKGROUND_UPLOADS 2 // maximum number of parallel uploads while the image is still mounted +#define COW_URL_STRING_SIZE 500 // Max string size for an url +#define COW_SHOW_UL_SPEED 1 // enable display of ul speed in cow status file +#define COW_MAX_IMAGE_SIZE 1000LL * 1000LL * 1000LL * 1000LL; // Maximum size an image can have(tb*gb*mb*kb) +// +++++ COW API Endpoints +++++ +#define COW_API_CREATE "%s/api/File/Create" +#define COW_API_UPDATE "%s/api/File/Update?guid=%s&BlockNumber=%lu" +#define COW_API_START_MERGE "%s/api/File/StartMerge" + #endif /* CONFIG_H_ */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 96ffcae..bea33ed 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -17,6 +17,10 @@ if(DNBD3_CLIENT_FUSE) add_subdirectory(fuse) endif(DNBD3_CLIENT_FUSE) +if(DNBD3_CLIENT_FUSE_COW_TEST) + add_subdirectory(cowtest) +endif(DNBD3_CLIENT_FUSE_COW_TEST) + if(DNBD3_SERVER) add_subdirectory(server) endif(DNBD3_SERVER) diff --git a/src/cowtest/CMakeLists.txt b/src/cowtest/CMakeLists.txt new file mode 100644 index 0000000..235a371 --- /dev/null +++ b/src/cowtest/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.10) + +# set the project name +project(dnbd3-fuse-cow-test + LANGUAGES C) + + +# find atomic library required by DNBD3_CLIENT_FUSE_COW_TEST +#find_package(Stdatomic REQUIRED) +#find_package(Libatomic REQUIRED) + +# add compile option to enable enhanced POSIX pthread features +add_definitions(-D_GNU_SOURCE) + +set(DNBD3_CLIENT_FUSE_COW_TEST_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/main.c) +#set(DNBD3_CLIENT_FUSE_COW_TEST_HEADER_FILES ) + + +add_executable(dnbd3-fuse-cow-test ${DNBD3_CLIENT_FUSE_COW_TEST_SOURCE_FILES}) +target_link_libraries(dnbd3-fuse-cow-test dnbd3-version dnbd3-shared ${CMAKE_THREAD_LIBS_INIT}) + + +install(TARGETS dnbd3-fuse-cow-test RUNTIME DESTINATION bin + COMPONENT cowtest) + +#add_linter(dnbd3-fuse-lint "${DNBD3_CLIENT_FUSE_COW_TEST_SOURCE_FILES}" "${DNBD3_CLIENT_FUSE_COW_TEST_HEADER_FILES}") +#add_linter_fix(dnbd3-fuse-lint-fix "${DNBD3_CLIENT_FUSE_COW_TEST_SOURCE_FILES}" "${DNBD3_CLIENT_FUSE_COW_TEST_HEADER_FILES}") diff --git a/src/cowtest/main.c b/src/cowtest/main.c new file mode 100644 index 0000000..9b67cda --- /dev/null +++ b/src/cowtest/main.c @@ -0,0 +1,1165 @@ +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <stdbool.h> +#include <stdio.h> +#include <string.h> +#include <sys/stat.h> +#include <stdint.h> +#include <dnbd3/types.h> +#include <stdatomic.h> +#include <time.h> +#include <pthread.h> +#include <getopt.h> + +typedef bool ( *func_ptr )(); +typedef struct verify_test +{ + off_t offset; + size_t size; + func_ptr test; +} verify_test_t; + +typedef struct special_test +{ + off_t offset; + size_t size; + +} special_test_t; + +typedef struct random_write_args +{ + char *mountedImage; + char *normalImage; + float minSizePercent; + float maxSizePercent; +} random_write_args_t; + +const size_t l2Size = 1024; +const size_t bitfieldByteSize = 40; +const size_t l2Capacity = l2Size * DNBD3_BLOCK_SIZE * bitfieldByteSize * 8; +const size_t testFileSize = l2Capacity * 2.9L; + +#define RND_MAX_WRITE_SIZE 4096 * 320 +#define RND_TRUNCATE_PROBABILITY 5 +#define RND_UNALIGNED_WRITE_PROBABILITY 5 +#define RND_DEFAULT_MIN_SIZE_PERCENT 0.9f +#define RND_DEFAULT_MAX_SIZE_PERCENT 1.1f +#define BASE_DATA (char)42 +#define CLAMP( x, min, max ) MAX( MIN( x, min ), max ) + +int delay = 0; +static char filePath[400]; +static int fh = 0; + +bool printOnError = true; +/** + * @brief generates a Test file + * + * @param path Location where the file is created + * @param size Size of the file in byte + */ + +bool generateTestFile( char *path, size_t size ) +{ + int fh; + if ( ( fh = open( path, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR ) ) == -1 ) { + perror( "Could not create test file: " ); + return false; + } + /* + if ( ftruncate( fh, size ) == -1 ) { + perror( "Error while expanding test file: " ); + return; + } + */ + + ssize_t writtenSize = 0; + char buf[DNBD3_BLOCK_SIZE * 50]; + memset( buf, BASE_DATA, DNBD3_BLOCK_SIZE * 50 ); + while ( writtenSize < (ssize_t)size ) { + size_t sizeToWrite = MIN( DNBD3_BLOCK_SIZE * 50, size - writtenSize ); + ssize_t tmp = pwrite( fh, buf, sizeToWrite, writtenSize ); + if ( tmp == 0 ) { + printf( "Error while populating the test file: " ); + return false; + } + if ( tmp == -1 ) { + perror( "Error while populating the test file: " ); + return false; + } + writtenSize += tmp; + } + + close( fh ); + printf( "Generated Test File of size: %zu bytes. \n", size ); + return true; +} + + +void printCharInHexadecimal( const char *str, int len ) +{ + for ( int i = 0; i < len; ++i ) { + printf( "0x%02x ", (int)str[i] ); + } + printf( "\n" ); +} + +bool compare( char buff[], char expected[], size_t size, char errorMessage[] ) +{ + if ( memcmp( buff, expected, size ) != 0 ) { + printf( "%s", errorMessage ); + if ( printOnError ) { + printf( "Expected: \n" ); + printCharInHexadecimal( expected, (int)size ); + printf( "Got: \n " ); + printCharInHexadecimal( buff, (int)size ); + } + return false; + } + return true; +} + + +bool readSizeTested( int fh, char *buf, ssize_t size, off_t off, char *error ) +{ + ssize_t readSize = pread( fh, buf, size, off ); + if ( readSize == -1 ) { + perror( "Read failed: " ); + } else if ( readSize < size ) { + printf( "%s \n size read: %zu\n Expected %zu\n", error, readSize, size ); + return false; + } + return true; +} + +bool writeSizeTested( int fh, char *buf, ssize_t size, off_t off, char *error ) +{ + ssize_t writeSize = pwrite( fh, buf, size, off ); + if ( writeSize == 1 ) + perror( "write failed: " ); + if ( writeSize < size ) { + printf( "%s", error ); + return false; + } + return true; +} + +bool writeTwoFilesTested( int fhm, int fhn, char *buf, ssize_t size, off_t off ) +{ + printf( "write offset: %zu size: %zu\n", off, size ); + + if ( !writeSizeTested( fhm, buf, size, off, "failed to write on mounted image" ) ) { + return false; + } + if ( !writeSizeTested( fhn, buf, size, off, "failed to write on normal image" ) ) { + return false; + } + return true; +} +bool changeFileSizeAndVerify( char *filePath, size_t size ) +{ + if ( truncate( filePath, size ) != 0 ) { + perror( "truncate failed: " ); + return false; + } + // verify + struct stat st; + stat( filePath, &st ); + size_t newSize = st.st_size; + + if ( size != newSize ) { + printf( "truncate failed, wrong file size\n expectedSize: %zu\n got: %zu\n", size, newSize ); + return false; + } + return true; +} + +bool changeTwoFileSizeAndVerify( char *filePath, char *filePath2, size_t size ) +{ + printf( "change filesize to: %zu\n", size ); + return changeFileSizeAndVerify( filePath, size ) && changeFileSizeAndVerify( filePath2, size ); +} + +bool verifySingleBit() +{ + char buff[DNBD3_BLOCK_SIZE]; + char expected[DNBD3_BLOCK_SIZE]; + memset( expected, BASE_DATA, DNBD3_BLOCK_SIZE ); + expected[0] = 1; + if ( !readSizeTested( fh, buff, DNBD3_BLOCK_SIZE, 0, "SingleBit test Failed: first read to small" ) ) + return false; + if ( !compare( buff, expected, DNBD3_BLOCK_SIZE, "SingleBit test Failed: first write not as expected" ) ) + return false; + + expected[0] = BASE_DATA; + expected[DNBD3_BLOCK_SIZE / 2] = 1; + if ( !readSizeTested( fh, buff, DNBD3_BLOCK_SIZE, DNBD3_BLOCK_SIZE, "SingleBit test Failed: second read to small" ) ) + return false; + if ( !compare( buff, expected, DNBD3_BLOCK_SIZE, "SingleBit test Failed: second write not as expected" ) ) + return false; + printf( "testSingleBit successful!\n" ); + return true; +} + +bool testSingleBit() +{ + char buff[DNBD3_BLOCK_SIZE]; + char expected[DNBD3_BLOCK_SIZE]; + memset( expected, BASE_DATA, DNBD3_BLOCK_SIZE ); + if ( !readSizeTested( fh, buff, DNBD3_BLOCK_SIZE, 0, "SingleBit test Failed: first read to small" ) ) + return false; + + if ( !compare( buff, expected, DNBD3_BLOCK_SIZE, "SingleBit test Failed: initial read" ) ) + return false; + expected[0] = 1; + if ( !writeSizeTested( fh, expected, DNBD3_BLOCK_SIZE, 0, "SingleBit test Failed: first write failed" ) ) + return false; + + expected[0] = BASE_DATA; + if ( !readSizeTested( fh, buff, DNBD3_BLOCK_SIZE, DNBD3_BLOCK_SIZE, "SingleBit test Failed: second read to small" ) ) + return false; + if ( !compare( buff, expected, DNBD3_BLOCK_SIZE, "SingleBit test Failed: second read" ) ) + return false; + expected[0] = 1; + if ( !writeSizeTested( + fh, expected, 1, DNBD3_BLOCK_SIZE + DNBD3_BLOCK_SIZE / 2, "SingleBit test Failed: second write failed" ) ) + return false; + return verifySingleBit(); +} + +bool verifyWriteOverTwoBlocks() +{ + char buff[DNBD3_BLOCK_SIZE * 2]; + char expected[DNBD3_BLOCK_SIZE * 2]; + memset( expected, 1, DNBD3_BLOCK_SIZE * 2 ); + if ( !readSizeTested( + fh, buff, DNBD3_BLOCK_SIZE * 2, DNBD3_BLOCK_SIZE * 3, "writeOverTwoBlocks test Failed: read to small" ) ) + return false; + if ( !compare( buff, expected, DNBD3_BLOCK_SIZE * 2, "OverTwoBlocks test Failed: write not as expected" ) ) + return false; + printf( "writeOverTwoBlocks successful!\n" ); + return true; +} + +bool writeOverTwoBlocks() +{ + char buff[DNBD3_BLOCK_SIZE * 2]; + char expected[DNBD3_BLOCK_SIZE * 2]; + memset( expected, BASE_DATA, DNBD3_BLOCK_SIZE * 2 ); + if ( !readSizeTested( + fh, buff, DNBD3_BLOCK_SIZE * 2, DNBD3_BLOCK_SIZE * 3, "writeOverTwoBlocks test Failed: read to small" ) ) + return false; + if ( !compare( buff, expected, DNBD3_BLOCK_SIZE * 2, "OverTwoBlocks test Failed: initial read" ) ) + return false; + memset( expected, 1, DNBD3_BLOCK_SIZE * 2 ); + if ( !writeSizeTested( fh, expected, DNBD3_BLOCK_SIZE * 2, DNBD3_BLOCK_SIZE * 3, + "writeOverTwoBlocks test Failed: write failed" ) ) + return false; + return verifyWriteOverTwoBlocks(); +} + + +bool verifyWriteOverL2() +{ + char buff[DNBD3_BLOCK_SIZE * 2]; + char expected[DNBD3_BLOCK_SIZE * 2]; + + memset( expected, 1, DNBD3_BLOCK_SIZE * 2 ); + size_t offset = l2Capacity * 2 - DNBD3_BLOCK_SIZE; + if ( !readSizeTested( fh, buff, DNBD3_BLOCK_SIZE * 2, offset, "writeOverL2 test Failed: read to small" ) ) { + return false; + } + if ( !compare( buff, expected, DNBD3_BLOCK_SIZE * 2, "writeOverL2 test Failed: write not as expected" ) ) { + return false; + } + printf( "writeOverL2 successful!\n" ); + return true; +} + +bool writeOverL2() +{ + char buff[DNBD3_BLOCK_SIZE * 2]; + char expected[DNBD3_BLOCK_SIZE * 2]; + memset( expected, BASE_DATA, DNBD3_BLOCK_SIZE * 2 ); + size_t offset = l2Capacity * 2 - DNBD3_BLOCK_SIZE; + if ( !readSizeTested( fh, buff, DNBD3_BLOCK_SIZE * 2, offset, "writeOverL2 test Failed: read to small" ) ) + return false; + if ( !compare( buff, expected, DNBD3_BLOCK_SIZE * 2, "writeOverL2 test Failed: initial read" ) ) + return false; + memset( expected, 1, DNBD3_BLOCK_SIZE * 2 ); + if ( !writeSizeTested( fh, expected, DNBD3_BLOCK_SIZE * 2, offset, "writeOverL2 test Failed: write failed" ) ) + return false; + + return verifyWriteOverL2(); +} + + +bool verifyWriteNotOnBlockBorder() +{ + char buff[DNBD3_BLOCK_SIZE * 2]; + char expected[DNBD3_BLOCK_SIZE * 2]; + memset( expected, 1, DNBD3_BLOCK_SIZE * 2 ); + size_t offset = DNBD3_BLOCK_SIZE * 11 - DNBD3_BLOCK_SIZE / 2; + if ( !readSizeTested( fh, buff, DNBD3_BLOCK_SIZE * 2, offset, "writeNotOnBlockBorder test Failed: read to small" ) ) + return false; + if ( !compare( buff, expected, DNBD3_BLOCK_SIZE * 2, "writeNotOnBlockBorder test Failed: write not as expected" ) ) + return false; + printf( "writeNotOnBlockBorder successful!\n" ); + return true; +} + +// perhaps do some initial markers on the file +bool writeNotOnBlockBorder() +{ + char buff[DNBD3_BLOCK_SIZE * 2]; + char expected[DNBD3_BLOCK_SIZE * 2]; + memset( expected, BASE_DATA, DNBD3_BLOCK_SIZE * 2 ); + size_t offset = DNBD3_BLOCK_SIZE * 11 - DNBD3_BLOCK_SIZE / 2; + if ( !readSizeTested( fh, buff, DNBD3_BLOCK_SIZE * 2, offset, "writeNotOnBlockBorder test Failed: read to small" ) ) + return false; + if ( !compare( buff, expected, DNBD3_BLOCK_SIZE * 2, "writeNotOnBlockBorder test Failed: initial read" ) ) + return false; + memset( expected, 1, DNBD3_BLOCK_SIZE * 2 ); + if ( !writeSizeTested( + fh, expected, DNBD3_BLOCK_SIZE * 2, offset, "writeNotOnBlockBorder test Failed: write failed" ) ) + return false; + return verifyWriteNotOnBlockBorder(); +} + +bool verifyLongNonAlignedPattern() +{ + int size = DNBD3_BLOCK_SIZE * 10; + char buffer[size]; + char expected[size]; + for ( int i = 0; i < size; i++ ) { + expected[i] = (char)( i % 255 ); + } + + off_t offset = l2Capacity * 3 - 1; + size_t totalSize = l2Capacity + 2; + off_t endOffset = offset + totalSize; + + while ( offset < endOffset ) { + size_t sizeToRead = MIN( size, endOffset - offset ); + if ( !readSizeTested( fh, buffer, sizeToRead, offset, "writeLongNonAlignedPattern test Failed: read failed" ) ) { + return false; + } + if ( !compare( buffer, expected, sizeToRead, "writeLongNonAlignedPattern test Failed: read failed" ) ) + return false; + offset += sizeToRead; + } + printf( "LongNonAlignedPattern successful!\n" ); + return true; +} + +bool writeLongNonAlignedPattern() +{ + int size = DNBD3_BLOCK_SIZE * 10; + char buffer[size]; + + for ( int i = 0; i < size; i++ ) { + buffer[i] = (char)( i % 255 ); + } + + off_t offset = l2Capacity * 3 - 1; + size_t totalSize = l2Capacity + 2; + off_t endOffset = offset + totalSize; + + while ( offset < endOffset ) { + size_t sizeToWrite = MIN( size, endOffset - offset ); + if ( !writeSizeTested( + fh, buffer, sizeToWrite, offset, "writeLongNonAlignedPattern test Failed: write failed" ) ) { + return false; + } + offset += sizeToWrite; + } + return verifyLongNonAlignedPattern(); +} + +//l2Capacity * 2.9L * 0.9 +bool verifyFileSizeChanges() +{ + printf( "verify size changes...\n" ); + char buff[DNBD3_BLOCK_SIZE * 2]; + char expected[DNBD3_BLOCK_SIZE * 2]; + + memset( expected, BASE_DATA, DNBD3_BLOCK_SIZE ); + memset( expected + DNBD3_BLOCK_SIZE, 0, DNBD3_BLOCK_SIZE ); + off_t offset = (size_t)( ( (double)testFileSize ) * 0.9 ) - DNBD3_BLOCK_SIZE; + + if ( !readSizeTested( + fh, buff, DNBD3_BLOCK_SIZE * 2, offset, "verifyFileSizeChanges test Failed: read to small\n" ) ) + return false; + if ( !compare( buff, expected, DNBD3_BLOCK_SIZE * 2, + "verifyFileSizeChanges test Failed: increased data not as expected.\n" ) ) + return false; + offset += DNBD3_BLOCK_SIZE * 2; + + offset = offset - ( offset % DNBD3_BLOCK_SIZE ); + + memset( expected, 0, DNBD3_BLOCK_SIZE ); + + while ( offset < (off_t) (l2Capacity * 3 - 1 )) { + size_t sizeToRead = MIN( DNBD3_BLOCK_SIZE * 2, ( l2Capacity * 3 - 1 ) - offset ); + if ( !readSizeTested( fh, buff, sizeToRead, offset, "verifyFileSizeChanges test Failed: read to small" ) ) + return false; + + if ( !compare( buff, expected, sizeToRead, "verifyFileSizeChanges test Failed: data not 0.\n" ) ) + return false; + offset += sizeToRead; + } + printf( "verified fileSizeChanges.\n" ); + return true; +} +bool fileSizeChanges() +{ + // check if increased is 0 + char buff[DNBD3_BLOCK_SIZE * 10]; + char expected[DNBD3_BLOCK_SIZE * 10]; + memset( expected, 0, DNBD3_BLOCK_SIZE * 10 ); + + // decrease FileSize + printf( "Decrease Filesize to: %zu\n", (size_t)( ( (double)testFileSize ) * 0.9 ) ); + if ( !changeFileSizeAndVerify( filePath, (size_t)( ( (double)testFileSize ) * 0.9 ) ) ) { + return false; + } + + printf( "increase Filesize to: %zu\n", testFileSize ); + if ( !changeFileSizeAndVerify( filePath, testFileSize ) ) { + return false; + } + + memset( expected, BASE_DATA, DNBD3_BLOCK_SIZE ); + if ( !readSizeTested( fh, buff, DNBD3_BLOCK_SIZE * 10, (size_t)( ( (double)testFileSize ) * 0.9 ) - DNBD3_BLOCK_SIZE, + "fileSizeChanges test Failed: read to small" ) ) + return false; + if ( !compare( buff, expected, DNBD3_BLOCK_SIZE * 10, "fileSizeChanges test Failed: increased not as expected.\n" ) ) + return false; + + memset( expected, 0, DNBD3_BLOCK_SIZE ); + // increase filesize + + if ( !changeFileSizeAndVerify( filePath, testFileSize + 2 * l2Capacity ) ) { + return false; + } + + + if ( !readSizeTested( fh, buff, DNBD3_BLOCK_SIZE * 10, testFileSize + l2Capacity, + "fileSizeChanges test Failed: read to small" ) ) + return false; + if ( !compare( buff, expected, DNBD3_BLOCK_SIZE * 10, "fileSizeChanges test Failed: increased data not 0" ) ) + return false; + printf( "increased data is 0 as expected\n" ); + // write on increased blocks + memset( expected, 1, DNBD3_BLOCK_SIZE * 10 ); + if ( !writeSizeTested( + fh, expected, DNBD3_BLOCK_SIZE * 10, testFileSize, "fileSizeChanges test Failed: write failed" ) ) + return false; + if ( !readSizeTested( fh, buff, DNBD3_BLOCK_SIZE * 10, testFileSize, "fileSizeChanges test Failed: read to small" ) ) + return false; + if ( !compare( + buff, expected, DNBD3_BLOCK_SIZE * 10, "fileSizeChanges test Failed: write on increased size failed" ) ) + return false; + printf( "writes to new Block Ok\n" ); + // decrease filesize + printf( "Truncate file to: %zu \n", testFileSize ); + if ( !changeFileSizeAndVerify( filePath, testFileSize ) ) { + return false; + } + printf( "size verified\n" ); + // increase again, check its 0 again + printf( "Truncate file to: %zu\n", testFileSize + 2 * l2Capacity ); + if ( !changeFileSizeAndVerify( filePath, testFileSize + 2 * l2Capacity ) ) { + return false; + } + + printf( "size verified\n" ); + memset( expected, 0, DNBD3_BLOCK_SIZE * 10 ); + + + if ( !readSizeTested( fh, buff, DNBD3_BLOCK_SIZE * 10, testFileSize, "fileSizeChanges test Failed: read to small" ) ) + return false; + if ( !compare( buff, expected, DNBD3_BLOCK_SIZE * 2, + "fileSizeChanges test Failed: increased data (second time) not 0" ) ) + return false; + + + return verifyFileSizeChanges(); +} + + +bool verifyInterleavedTest() +{ + char buff[DNBD3_BLOCK_SIZE * 10]; + char expected[DNBD3_BLOCK_SIZE * 10]; + off_t offset = 35 * DNBD3_BLOCK_SIZE; + memset( expected, BASE_DATA, DNBD3_BLOCK_SIZE * 10 ); + memset( expected, 10, DNBD3_BLOCK_SIZE ); + memset( ( expected + ( DNBD3_BLOCK_SIZE * 2 ) ), 12, DNBD3_BLOCK_SIZE ); + memset( ( expected + ( DNBD3_BLOCK_SIZE * 4 ) ), 14, DNBD3_BLOCK_SIZE ); + memset( ( expected + ( DNBD3_BLOCK_SIZE * 5 ) ), 15, DNBD3_BLOCK_SIZE ); + memset( ( expected + ( DNBD3_BLOCK_SIZE * 8 ) ), 18, DNBD3_BLOCK_SIZE ); + if ( !readSizeTested( fh, buff, DNBD3_BLOCK_SIZE * 10, offset, "interleavedTest test Failed: read 2 to small" ) ) + return false; + if ( !compare( buff, expected, DNBD3_BLOCK_SIZE * 10, "interleavedTest test Failed: read not as expected" ) ) + return false; + printf( "interleavedTest successful!\n" ); + return true; +} + +bool interleavedTest() +{ + printf( "starting interleavedTest \n" ); + char buff[DNBD3_BLOCK_SIZE * 10]; + char expected[DNBD3_BLOCK_SIZE * 10]; + off_t offset = 35 * DNBD3_BLOCK_SIZE; + memset( expected, BASE_DATA, DNBD3_BLOCK_SIZE * 10 ); + if ( !readSizeTested( fh, buff, DNBD3_BLOCK_SIZE * 10, offset, "interleavedTest test Failed: read to small" ) ) + return false; + if ( !compare( buff, expected, DNBD3_BLOCK_SIZE * 10, "interleavedTest test Failed: read data not 0" ) ) + return false; + + memset( expected, 10, DNBD3_BLOCK_SIZE ); + if ( !writeSizeTested( fh, expected, DNBD3_BLOCK_SIZE, offset, "interleavedTest test Failed: write 1 failed" ) ) + return false; + + memset( ( expected + ( DNBD3_BLOCK_SIZE * 2 ) ), 12, DNBD3_BLOCK_SIZE ); + if ( !writeSizeTested( fh, ( expected + ( DNBD3_BLOCK_SIZE * 2 ) ), DNBD3_BLOCK_SIZE, offset + DNBD3_BLOCK_SIZE * 2, + "interleavedTest test Failed: write 2 failed" ) ) + return false; + + memset( ( expected + ( DNBD3_BLOCK_SIZE * 4 ) ), 14, DNBD3_BLOCK_SIZE ); + memset( ( expected + ( DNBD3_BLOCK_SIZE * 5 ) ), 15, DNBD3_BLOCK_SIZE ); + + if ( !writeSizeTested( fh, ( expected + ( DNBD3_BLOCK_SIZE * 4 ) ), DNBD3_BLOCK_SIZE * 2, + offset + DNBD3_BLOCK_SIZE * 4, "interleavedTest test Failed: write 3 failed" ) ) + return false; + + memset( ( expected + ( DNBD3_BLOCK_SIZE * 8 ) ), 18, DNBD3_BLOCK_SIZE ); + if ( !writeSizeTested( fh, ( expected + ( DNBD3_BLOCK_SIZE * 8 ) ), DNBD3_BLOCK_SIZE, offset + DNBD3_BLOCK_SIZE * 8, + "interleavedTest test Failed: write 4 failed" ) ) + return false; + return verifyInterleavedTest(); +} + +bool verifyMultipleWrites() +{ + size_t size = DNBD3_BLOCK_SIZE * 10 * bitfieldByteSize; + char buff[size]; + char expected[size]; + off_t offset = 100 * DNBD3_BLOCK_SIZE * bitfieldByteSize; + memset( expected, 3, size ); + if ( !readSizeTested( fh, buff, size, offset, "multipleWrites test Failed: read to small" ) ) + return false; + if ( !compare( buff, expected, size, "multipleWrites: read incorrect data" ) ) + return false; + printf( "MultipleWrites successful!\n" ); + return true; +} + +bool multipleWrites() +{ + printf( "starting multipleWrites\n" ); + size_t size = DNBD3_BLOCK_SIZE * 10 * bitfieldByteSize; + char buff[size]; + char expected[size]; + off_t offset = 100 * DNBD3_BLOCK_SIZE * bitfieldByteSize; + + for ( int i = 1; i <= 3; i++ ) { + printf( "multipleWrites: %i/3 \n", i ); + + memset( expected, i, size ); + if ( !writeSizeTested( fh, expected, size, offset, "multipleWrites: write Failed" ) ) + return false; + if ( !readSizeTested( fh, buff, size, offset, "multipleWrites test Failed: read to small" ) ) + return false; + if ( !compare( buff, expected, size, "multipleWrites: read incorrect data" ) ) + return false; + if ( delay > 0 && i < 3 ) { + printf( "waiting %is\n", delay ); + sleep( delay ); + } + } + return verifyMultipleWrites(); +} + + +bool runTest( char *path ) +{ + if ( ( fh = open( path, O_RDWR, S_IRUSR | S_IWUSR ) ) == -1 ) { + perror( "Could not open test file" ); + printf( "Given path: %s \n", path ); + return; + } + strcpy( filePath, path ); + printf( "file opened: %s\n", path ); + + if ( !testSingleBit() ) + return false; + if ( !writeOverTwoBlocks() ) + return false; + + if ( !writeNotOnBlockBorder() ) + return false; + + if ( !writeOverL2() ) + return false; + if ( !fileSizeChanges() ) + return false; + if ( !interleavedTest() ) + return false; + if ( !multipleWrites() ) { + return false; + } + if ( !writeLongNonAlignedPattern() ) { + return false; + } + + + printf( "All test's successful.\n" ); + return true; +} + + +void verifyTests( verify_test_t *tests ) +{ + // offset, size, function + + off_t fileSizeOffset = (size_t)( ( (double)testFileSize * 0.9 ) - DNBD3_BLOCK_SIZE ); + size_t fileSizeSize = ( l2Capacity * 3 - 1 ) - fileSizeOffset; + tests[0] = ( verify_test_t ){ 0, 2 * DNBD3_BLOCK_SIZE, verifySingleBit }; + tests[1] = ( verify_test_t ){ DNBD3_BLOCK_SIZE * 3, DNBD3_BLOCK_SIZE * 3, verifyWriteOverTwoBlocks }; + tests[2] = ( verify_test_t ){ DNBD3_BLOCK_SIZE * 11 - DNBD3_BLOCK_SIZE / 2, DNBD3_BLOCK_SIZE * 2, + verifyWriteNotOnBlockBorder }; + tests[3] = ( verify_test_t ){ 35 * DNBD3_BLOCK_SIZE, DNBD3_BLOCK_SIZE * 10, verifyInterleavedTest }; + tests[4] = ( verify_test_t ){ 100 * DNBD3_BLOCK_SIZE * bitfieldByteSize, DNBD3_BLOCK_SIZE * 10 * bitfieldByteSize, + verifyMultipleWrites }; + tests[5] = ( verify_test_t ){ l2Capacity * 2 - DNBD3_BLOCK_SIZE, DNBD3_BLOCK_SIZE * 2, verifyWriteOverL2 }; + tests[6] = ( verify_test_t ){ fileSizeOffset, fileSizeSize, verifyFileSizeChanges }; + tests[7] = ( verify_test_t ){ l2Capacity * 3 - 1, l2Capacity + 2, verifyLongNonAlignedPattern }; +} + +bool verifyFinalFile( char *path ) +{ + if ( ( fh = open( path, O_RDWR, S_IRUSR | S_IWUSR ) ) == -1 ) { + perror( "Could not open test file" ); + printf( "Given path: %s \n", path ); + return false; + } + // verify file size + + size_t fileSize = testFileSize + 2 * l2Capacity; + struct stat st; + stat( path, &st ); + size_t size = st.st_size; + if ( size != fileSize ) { + printf( "verify Failed, wrong file size\n expectedSize: %zu\n got: %zu\n", fileSize, size ); + return false; + } + + // read to whole file + + int maxReadSize = DNBD3_BLOCK_SIZE * COW_BITFIELD_SIZE * 8; + char buffer[maxReadSize]; + char emptyData[maxReadSize]; + memset( emptyData, BASE_DATA, maxReadSize ); + size_t offset = 0; + + + int numberOfTests = 8; + verify_test_t tests[numberOfTests]; + verifyTests( tests ); + + int currentTest = 0; + bool swapToIncreased = false; + + + while ( offset < fileSize ) { + size_t sizeToRead = MIN( (size_t)maxReadSize, fileSize - offset ); + if ( currentTest < numberOfTests ) { + sizeToRead = MIN( sizeToRead, tests[currentTest].offset - offset ); + } + if ( currentTest < numberOfTests && tests[currentTest].offset == (off_t)offset ) { + if ( !tests[currentTest].test() ) { + return false; + } + offset += tests[currentTest].size; + currentTest++; + } else { + // if offset > testFileSize filler data is 0 + if ( !swapToIncreased && offset > testFileSize ) { + memset( emptyData, 0, maxReadSize ); + } + ssize_t sizeRead = pread( fh, buffer, sizeToRead, offset ); + if ( sizeRead <= 0 ) { + perror( "Error while reading data: " ); + printf( "verify failed. \n" ); + return false; + } + if ( !compare( buffer, emptyData, sizeRead, "verify failed. Expected 0 data" ) ) { + printf( "Offset: %zu \n", offset ); + return false; + } + offset += (size_t)sizeRead; + } + } + + printf( "file verified successful.\n" ); + return true; + } + + +void generateRandomData( int fhr, char *dest, size_t size ) +{ + read( fhr, dest, size ); +} + + +atomic_bool randomTestLoop = true; + +void printProgress( float progress ) +{ + progress = MIN( 1, progress ); + progress = MAX( 0, progress ); + int barWidth = 50; + char buf[barWidth + 1]; + buf[barWidth] = 0; + int pos = (int)( (float)barWidth * progress ); + memset( buf, '=', pos ); + memset( ( buf + pos ), ' ', ( barWidth - pos ) ); + printf( "\033[F[%s] %i%%\n", buf, (int)( progress * 100 ) ); +} + +off_t findDiffOffset( char *buf1, char *buf2, size_t size ) +{ + for ( size_t i = 0; i < size; i++ ) { + if ( buf1[i] != buf2[i] ) { + return i; + } + } + return -1; +} + +bool compareTwoFiles( char *mountedImagePath, char *normalImagePath, int fhm, int fhn ) +{ + char buf[RND_MAX_WRITE_SIZE]; + char exBuf[RND_MAX_WRITE_SIZE]; + off_t offset = 0; + struct stat st; + stat( mountedImagePath, &st ); + size_t sizeMounted = st.st_size; + stat( normalImagePath, &st ); + size_t sizeNormal = st.st_size; + + if ( sizeMounted != sizeNormal ) { + printf( "Error size difference, mounted: %zu normal: %zu \n", sizeMounted, sizeNormal ); + return false; + } + printf( "\n" ); + while ( offset < (off_t)sizeMounted ) { + size_t sizeToRead = MIN( RND_MAX_WRITE_SIZE, sizeMounted - offset ); + read( fhm, buf, sizeToRead ); + + read( fhn, exBuf, sizeToRead ); + + if ( memcmp( buf, exBuf, sizeToRead ) != 0 ) { + off_t dif = findDiffOffset( buf, exBuf, sizeToRead ); + printf( "Error: Different data, offset: %zu \n expected: %i got %i \n", offset + dif, (int)exBuf[dif], + (int)buf[dif] ); + return false; + } + + offset += sizeToRead; + printProgress( ( (float)offset ) / ( (float)sizeMounted ) ); + } + printf( "\nTest successful !!!\n" ); + return true; +} + +bool startCompareTwoFiles( char *mountedImagePath, char *normalImagePath ) +{ + int fhm, fhn; + bool ok = true; + if ( ( fhm = open( mountedImagePath, O_RDWR, S_IRUSR | S_IWUSR ) ) == -1 ) { + perror( "Could not open mounted Image" ); + printf( "Given path: %s \n", mountedImagePath ); + ok = false; + } + if ( ( fhn = open( normalImagePath, O_RDWR, S_IRUSR | S_IWUSR ) ) == -1 ) { + perror( "Could not open normal Image" ); + printf( "Given path: %s \n", normalImagePath ); + ok = false; + } + if(!ok){ + return false; + } + return compareTwoFiles( mountedImagePath, normalImagePath, fhm, fhn ); +} + + +bool specialTwoFilesTest( char *mountedImagePath, char *normalImagePath ) +{ + int fhm; + int fhn; + int fhr; + char buf[RND_MAX_WRITE_SIZE]; + bool ok = true; + if ( ( fhm = open( mountedImagePath, O_RDWR, S_IRUSR | S_IWUSR ) ) == -1 ) { + perror( "Could not open mounted Image" ); + printf( "Given path: %s \n", mountedImagePath ); + ok = false; + } + if ( ( fhn = open( normalImagePath, O_RDWR, S_IRUSR | S_IWUSR ) ) == -1 ) { + perror( "Could not open normal Image" ); + printf( "Given path: %s \n", normalImagePath ); + ok = false; + } + if ( ( fhr = open( "/dev/urandom", O_RDONLY ) ) == -1 ) { + perror( "Could not open /dev/urandom" ); + ok = false; + } + if(!ok){ + return false; + } + special_test_t tests[] = { + {976314368, 569344}, + {970432512, 1253376}, + {959447040, 692224}, + {782128012, 0}, + {945591351, 0}, + {956534784, 344064}, + { 966615040, 397312 }, { 906517288, 0 }, + {2062985199, 0}, + { 966663420, 1097920 }, + {969617408, 327680}, + {957513728, 1105920}, + {964941207, 1183680}, + {958701568, 741376}, + {958701568, 102400}, + {970027008, 20480}, + }; + + for ( int i = 0; i < (int) (sizeof( tests ) / sizeof( special_test_t )); i++ ) { + if ( tests[i].size == 0 ) { + changeTwoFileSizeAndVerify( mountedImagePath, normalImagePath, tests[i].offset ); + } else { + generateRandomData( fhr, buf, tests[i].size ); + writeTwoFilesTested( fhm, fhn, buf, tests[i].size, tests[i].offset ); + } + } + + printf( "\n" ); + return compareTwoFiles( mountedImagePath, normalImagePath, fhm, fhn ); +} + +void *randomWriteTest( void *args ) +{ + char *mountedImagePath = ( (random_write_args_t *)args )->mountedImage; + char *normalImagePath = ( (random_write_args_t *)args )->normalImage; + float minSizePercent = ( (random_write_args_t *)args )->minSizePercent; + float maxSizePercent = ( (random_write_args_t *)args )->maxSizePercent; + + int fhm; + int fhn; + int fhr; + srand( (unsigned)time( NULL ) ); + + struct stat st; + stat( normalImagePath, &st ); + size_t startFileSize = st.st_size; + size_t maxOffset = (size_t)( startFileSize * 1.1L ); + double minFileSize = (double)startFileSize * minSizePercent; + double fileSizeVariation = (double)startFileSize * ( maxSizePercent - minFileSize ); + + + char buf[RND_MAX_WRITE_SIZE]; + + printf( "===starting random write test ===\n" ); + printf( "mounted image path %s\n", mountedImagePath ); + printf( "normal image path %s\n", normalImagePath ); + + bool ok = true; + if ( ( fhm = open( mountedImagePath, O_RDWR, S_IRUSR | S_IWUSR ) ) == -1 ) { + perror( "Could not open mounted Image" ); + printf( "Given path: %s \n", mountedImagePath ); + ok = false; + } + if ( ( fhn = open( normalImagePath, O_RDWR, S_IRUSR | S_IWUSR ) ) == -1 ) { + perror( "Could not open normal Image" ); + printf( "Given path: %s \n", normalImagePath ); + ok = false; + } + if ( ( fhr = open( "/dev/urandom", O_RDONLY ) ) == -1 ) { + perror( "Could not open /dev/urandom" ); + ok = false; + } + if(!ok){ + return (void*) false; + } + // RANDOM WRITE LOOP + printf( "Press any key to cancel\n" ); + while ( randomTestLoop ) { + //select test + int r = rand() % 100; + + if ( r < RND_TRUNCATE_PROBABILITY ) { + // truncate both images + size_t size = (size_t)( ( rand() % (int)( fileSizeVariation ) ) + minFileSize ); + + printf( "change filesize to: %zu\n", size ); + if ( !changeFileSizeAndVerify( mountedImagePath, size ) ) { + return (void*) false; + } + if ( !changeFileSizeAndVerify( normalImagePath, size ) ) { + return (void*) false; + } + + } else { + off_t offset = rand() % maxOffset; + size_t size = rand() % RND_MAX_WRITE_SIZE; + size = MAX( size, 1 ); + if ( r > RND_TRUNCATE_PROBABILITY + RND_UNALIGNED_WRITE_PROBABILITY ) { + offset = offset - ( offset % 4096 ); + size = MAX( size - ( size % 4096 ), 4096 ); + } + + generateRandomData( fhr, buf, size ); + printf( "write offset: %zu size: %zu\n", offset, size ); + if ( !writeSizeTested( fhm, buf, size, offset, "failed to write on mounted image" ) ) + return (void*) false; + if ( !writeSizeTested( fhn, buf, size, offset, "failed to write on normal image" ) ) + return (void*) false; + } + } + + // COMPARE BOTH IMAGES + printf( "comparing both files: \n\n" ); + compareTwoFiles( mountedImagePath, normalImagePath, fhm, fhn ); + + return (void*) true; +} + + +bool startRandomWriteTest( char *mountedImagePath, char *normalImagePath, float minSizePercent, float maxSizePercent ) +{ + // start Thread + + + if ( minSizePercent < 0 || maxSizePercent < minSizePercent || maxSizePercent < 0.1 ) { + printf( "minSizePercent or maxSizePercent of wrong value, reverting to default.\n" ); + minSizePercent = RND_DEFAULT_MIN_SIZE_PERCENT; + maxSizePercent = RND_DEFAULT_MAX_SIZE_PERCENT; + } + printf( "minSizePercent: %.1f%% maxSizePercent: %.1f%%\n", minSizePercent * 100, maxSizePercent * 100 ); + pthread_t tid; + random_write_args_t *args = malloc( sizeof( random_write_args_t ) ); + + args->mountedImage = mountedImagePath; + args->normalImage = normalImagePath; + args->minSizePercent = minSizePercent; + args->maxSizePercent = maxSizePercent; + + bool *result; + pthread_create( &tid, NULL, &randomWriteTest, args ); + // wait for key + getchar(); + randomTestLoop = false; + + pthread_join( tid, (void*) &result ); + free( args ); + return result; +} + +static const char *optString = "d:c:t:v:r:x:y:z:w:h:"; +static const struct option longOpts[] = { { "delay", required_argument, NULL, 'd' }, + { "testFile", optional_argument, NULL, 'c' }, { "test", required_argument, NULL, 't' }, + { "verify", required_argument, NULL, 'v' }, { "specialTwoFiles", required_argument, NULL, 'w' }, + { "randomTest", required_argument, NULL, 'r' }, { "compare", required_argument, NULL, 'x' }, + { "minSizePercent", required_argument, NULL, 'y' }, { "maxSizePercent", required_argument, NULL, 'z' }, + { "help", required_argument, NULL, 'h' }, { 0, 0, 0, 0 } }; + + +void printUsageStandardTest() +{ + printf( "Todo information about standard test...\n" ); + printf( "\n" ); + printf( "Instructions on how to use the standard test: \n" ); + printf( + "1. Generate the test image with -c <path> and copy it to the image location of the dnbd3 server. Also make sure that the cow servers OriginalImageDirectory points to the same Directory or copied in that Directory too. This step is only needed once for setting up.\n" ); + printf( "2. Start the dnbd3 and cow server.\n" ); + printf( "3. Mount the image in cow mode.\n" ); + printf( "4. Run the test with -t <path>, where the path points to the mounted image.\n" ); + printf( "5. Optional verify again with -v <path>.\n" ); + printf( + "6. Optional unmount the image and then load it again (with -L <path> in the fuse client). Then verify the loaded image with -v <path>.\n" ); + printf( "7. Unmount and merge the image (to merge the image use -m on the fuse client).\n" ); + printf( "8. Verify the merged image from the cow server with -v <path>.\n" ); +} + +void printUsageRandomTest() +{ + printf( "Todo information about random test...\n" ); + printf( "\n" ); + printf( "Instructions on how to use the random test: \n" ); + printf( + "1. Generate the test image with -c <path> and copy it to the image location of the dnbd3 server. Also make sure that the cow servers OriginalImageDirectory points to the same Directory or copied in that Directory too. This step is only needed once for setting up.\n" ); + printf( "2. Copy the generated image to another location.\n" ); + printf( "3. Start the dnbd3 and cow server.\n" ); + printf( "4. Mount the image in cow mode.\n" ); + printf( + "5. Run the test with -t <mountedFile> <normalFile>, where the <mountedFile> points to the mounted image and <normalFile> points to the copied image on the disk.\n" ); + printf( "6. After some time press enter and both images will be compared for equalness." ); + printf( "7. Unmount the image and merge.\n" ); + printf( + "8. Run -x <mergedFile> <normalFile> where the <mergedFile> points to the merged image and <normalFile> points to the copied image on the disk. This will verify that the merged image is equal to the image on the disk.\n" ); +} + +void printUsage() +{ + printf( "There are two test variants, the standard test in which different edcases are tested and " + "a random test in which data or size changes are randomly made in a mounted file and a file " + "located on the normal file system and then compared. " + "To get information about the two tests and how to run them use -h test and -h randomTest.\n" ); + printf( "Usage: \n" ); + printf( " -c --testFile <file> Creates test file at the path. \n" ); + printf( + " -d --delay <seconds> Delay in Seconds for multiple block write in the standard test.\n" ); + printf( " -t --test <file> Runs the standard test procedure. \n" ); + printf( " -v --verify <file> verifies a file. \n" ); + printf( + " -r --randomTest <mountedFile> <normalFile> randomly writes in both file's and after cancel(ENTER) compares them if they are equal.\n" ); + printf( + " -y --minSizePercent <percent> sets the minimum size in percent(integer) the file will be reduced to in the random test.\n" ); + printf( + " -z --maxSizePercent <percent> sets the maximum size in percent(integer) the file will be enlarged to in the random test.\n" ); + printf( " -x --compare <mountedFile> <normalFile> compares two files for equalness.\n" ); +} + +int main( int argc, char *argv[] ) +{ + if ( argc <= 1 || strcmp( argv[1], "--help" ) == 0 || strcmp( argv[1], "--usage" ) == 0 ) { + printUsage(); + return 0; + } + int opt, lidx; + + bool runTestFile = false; + bool runStandardTest = false; + bool runVerifyTest = false; + bool runRandomTest = false; + bool runCompare = false; + bool runSpecialTwoFiles = false; + char fileCreationPath[400]; + char *standardTestPath; + char *verifyPath; + char *rndMountedPath; + char *rndNormalPath; + size_t generateFileSize = testFileSize; + float minSizePercent = RND_DEFAULT_MIN_SIZE_PERCENT; + float maxSizePercent = RND_DEFAULT_MAX_SIZE_PERCENT; + + while ( ( opt = getopt_long( argc, argv, optString, longOpts, &lidx ) ) != -1 ) { + char *pEnd; + switch ( opt ) { + case 'd': + + delay = (int)strtol( optarg, &pEnd, 10 ); + printf( "Delay set to %i\n", delay ); + break; + case 'c': + strncpy( fileCreationPath, optarg, 400 ); + if ( optind >= argc && argv[optind] != NULL && argv[optind][0] != '-' ) { + generateFileSize = (size_t)strtol( argv[optind], &pEnd, 10 ); + ++optind; + } + + runTestFile = true; + break; + case 't': + standardTestPath = optarg; + runStandardTest = true; + break; + case 'v': + verifyPath = optarg; + runVerifyTest = true; + break; + case 'r': + printf( "\nasd\n", opt ); + if ( optind >= argc && argv[optind] != NULL && argv[optind][0] != '-' ) { + printUsage(); + return 0; + } + rndMountedPath = optarg; + rndNormalPath = argv[optind]; + optind++; + runRandomTest = true; + + break; + case 'x': + if ( optind >= argc && argv[optind] != NULL && argv[optind][0] != '-' ) { + printUsage(); + return 0; + } + rndMountedPath = optarg; + rndNormalPath = argv[optind]; + optind++; + runCompare = true; + break; + case 'w': + if ( optind >= argc && argv[optind] != NULL && argv[optind][0] != '-' ) { + printUsage(); + return 0; + } + rndMountedPath = optarg; + rndNormalPath = argv[optind]; + optind++; + runSpecialTwoFiles = true; + break; + case 'y': minSizePercent = ( (float)strtol( optarg, &pEnd, 10 ) ) / 100; break; + case 'z': maxSizePercent = ( (float)strtol( optarg, &pEnd, 10 ) ) / 100; break; + + case 'h': + if ( strcmp( optarg, "test" ) == 0 ) { + printUsageStandardTest(); + return 0; + } else if ( strcmp( optarg, "randomTest" ) == 0 ) { + printUsageRandomTest(); + return 0; + } else { + printUsage(); + return 0; + } + break; + default: + printUsage(); + return 0; + break; + } + } + bool result = true; + if ( runTestFile ) { + result = generateTestFile( fileCreationPath, generateFileSize ); + } else if ( runStandardTest ) { + printf( "starting standard test\n" ); + result = runTest( standardTestPath ); + } else if ( runVerifyTest ) { + printf( "verifying file \n" ); + result = verifyFinalFile( verifyPath ); + } else if ( runRandomTest ) { + result = startRandomWriteTest( rndMountedPath, rndNormalPath, minSizePercent, maxSizePercent ); + } else if ( runCompare ) { + result = startCompareTwoFiles( rndMountedPath, rndNormalPath ); + } else if ( runSpecialTwoFiles ) { + result = specialTwoFilesTest( rndMountedPath, rndNormalPath ); + } else { + printUsage(); + } + if(!result ){ + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} diff --git a/src/cowtest/readme.md b/src/cowtest/readme.md new file mode 100644 index 0000000..876bf79 --- /dev/null +++ b/src/cowtest/readme.md @@ -0,0 +1,105 @@ +# Cowtest + +### Table of Contents +1. [Introduction](#introduction) +2. [Usage](#usage) +3. [Tests](#tests) + + + +# Introduction + +This test suit is for verifying that the fuse cow implementation works correctly. It can verify that read and writes on the cow fuse client work correctly and that the cow server merges the image correctly. + + +# Usage + +### Parameters +- `-c <path>` generates a test image at given path. This image should be loaded for the tests to work. +- `-t <path>` runs the tests on the image at the given past. +- `-v <path>` verifies that previous tests on the image were successful (also reads the image completely). +- `-r <mountedImagePath> <normalImagePath>` random writes and changes size of two images. After a key press both images are compared for equalness. +- `-x <mergedImagePath> <normalImagePath>` Checks both images are compared for equalness. +### Example usage for standard test + +1. Generate the test image with `-c <path>` and copy it to the image location of the dnbd3 server. Also make sure that the cow servers `OriginalImageDirectory` points to the same Directory or copied in that Directory too. This step is only needed once for setting up. +2. Start the dnbd3 and cow server. +3. Mount the image in cow mode. +4. Run the test with `-t <path>`, where the path points to the mounted image. +5. Optional verify again with `-v <path>`. +6. Optional unmount the image and then load it again (with `-L <path>` in the fuse client). Then verify the loaded image with `-v <path>`. +7. Unmount and merge the image. +8. Verify the merged image from the cow server with `-v <path>`. + +### Example usage for random writes +1. Generate the test image with `-c <path>` and copy it to the image location of the dnbd3 server. Also make sure that the cow servers `OriginalImageDirectory` points to the same Directory or copied in that Directory too. This step is only needed once for setting up. +2. Copy the generated image to another location. +3. Start the dnbd3 and cow server. +4. Mount the image in cow mode. +5. Run the test with `-t <mountedImagePath> <normalImagePath>`, where the `<mountedImagePath>` points to the mounted image and `<normalImagePath>` points to the copied image on the disk. +6. After some time press enter and both images will be compared for equalness. +7. Unmount the image and merge. +8. Run `-x <mergedImagePath> <normalImagePath>` where the `<mergedImagePath>` points to the merged image and `<normalImagePath>` points to the copied image on the disk. This will verify that the merged image is equal to the image on the disk. + + + +# Tests + +### TestSingleBit +Reads the first block and verifies that all bits are 0. Then it sets the first bit to 1 and writes it. +This test, tests basic functionality and verifies that the image is still 'clean'. +Then it sets a single Bit in the second block to 1, to verify padding works correctly. + +| offset | size | +| -------| -----| +| 0 | 2 * DNBD3_BLOCK_SIZE| + + +### WriteOverTwoBlocks +Tests that continuous writes over two DNBD3_BLOCK's are possible. + +| offset | size | +| -------| -----| +| DNBD3_BLOCK_SIZE * 3| size: DNBD3_BLOCK_SIZE * 2| + + +### WriteNotOnBlockBorder +Verifies that writes not aligned to block borders (multiples of 4096). + +| offset | size | +| -------| -----| +| DNBD3_BLOCK_SIZE * 11 - DNBD3_BLOCK_SIZE / 2| DNBD3_BLOCK_SIZE * 2 | + + +### InterleavedTest + +| offset | size | +| -------| -----| +|DNBD3_BLOCK_SIZE * 35 | DNBD3_BLOCK_SIZE * 10| + +### WriteOverL2 +Tests that continuous writes over L2 borders are possible. + +| offset | size | +| -------| -----| +|l2Capacity * 2 - DNBD3_BLOCK_SIZE | DNBD3_BLOCK_SIZE * 2 | + + +### MultipleWrites +Writes multiple times on the same Blocks different data. The separate writes can be delayed with the `-d` parameter. This is useful to test if uploading the same blocks multiple times works as intended. + +| offset | size | +| -------| -----| +| 100 * DNBD3_BLOCK_SIZE * bitfieldByteSize | DNBD3_BLOCK_SIZE * 10 * bitfieldByteSize | + + +### fileSizeChanges +Tests file size changes. First in increases the file size with a truncate by 2 * l2Capacity. It then checks that all the bits in the new allocated space are set to 0. Then it writes data to it to verify writes are possible. After that it truncates it back to the original size. Then it truncates it back to +the original size + 2 * l2Capacity and verifies that the again all bits in the new allocated space are 0 (so that the before written data is set to 0 again). + +### LongNonAlignedPattern +This test writes writes an long pattern over 3 l2 borders. The pattern repeats chars from 0 to 254, so it's not a multiple of 4096, which therefore results that all Blocks are filled with different data. Also, this test is not block aligned. + +| offset | size | +| -------| -----| +|l2Capacity * 3 - 1|l2Capacity + 2| diff --git a/src/fuse/CMakeLists.txt b/src/fuse/CMakeLists.txt index be062f0..05b3fcd 100644 --- a/src/fuse/CMakeLists.txt +++ b/src/fuse/CMakeLists.txt @@ -10,18 +10,24 @@ find_package(Fuse REQUIRED) find_package(Stdatomic REQUIRED) find_package(Libatomic REQUIRED) +# find curl for cow +find_package(CURL REQUIRED) + # add compile option to enable enhanced POSIX pthread features add_definitions(-D_GNU_SOURCE) -set(DNBD3_FUSE_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/connection.c +set(DNBD3_FUSE_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/cowfile.c + ${CMAKE_CURRENT_SOURCE_DIR}/connection.c ${CMAKE_CURRENT_SOURCE_DIR}/helper.c ${CMAKE_CURRENT_SOURCE_DIR}/main.c) -set(DNBD3_FUSE_HEADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/connection.h - ${CMAKE_CURRENT_SOURCE_DIR}/helper.h) +set(DNBD3_FUSE_HEADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/cowfile.h + ${CMAKE_CURRENT_SOURCE_DIR}/connection.h + ${CMAKE_CURRENT_SOURCE_DIR}/helper.h + ${CMAKE_CURRENT_SOURCE_DIR}/main.h) add_executable(dnbd3-fuse ${DNBD3_FUSE_SOURCE_FILES}) -target_include_directories(dnbd3-fuse PRIVATE ${FUSE_INCLUDE_DIRS}) -target_link_libraries(dnbd3-fuse dnbd3-build dnbd3-version dnbd3-shared ${FUSE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) +target_include_directories(dnbd3-fuse PRIVATE ${FUSE_INCLUDE_DIRS} ${CURL_INCLUDE_DIR} ) +target_link_libraries(dnbd3-fuse dnbd3-build dnbd3-version dnbd3-shared ${FUSE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${CURL_LIBRARIES} ) install(TARGETS dnbd3-fuse RUNTIME DESTINATION bin COMPONENT fuse) diff --git a/src/fuse/connection.c b/src/fuse/connection.c index e760d98..018ad89 100644 --- a/src/fuse/connection.c +++ b/src/fuse/connection.c @@ -6,6 +6,8 @@ #include <dnbd3/shared/sockhelper.h> #include <dnbd3/shared/log.h> +#include "main.h" +#include "cowfile.h" #include <stdlib.h> #include <pthread.h> #include <string.h> @@ -108,6 +110,7 @@ static bool hasAltServer( dnbd3_host_t *host ); static void addAltServers( void ); static void sortAltServers(); static void probeAltServers(); +static size_t receiveRequest(const int sock, dnbd3_async_t* request ); static void switchConnection( int sockFd, alt_server_t *srv ); static void requestAltServers( void ); static bool sendAltServerRequest( int sock ); @@ -349,6 +352,17 @@ bool connection_initThreads() return success; } +char * connection_getImageName() +{ + return image.name; +} + +uint16_t connection_getImageRID() +{ + return image.rid; +} + + uint64_t connection_getImageSize() { return image.size; @@ -492,7 +506,7 @@ static void* connection_receiveThreadMain( void *sockPtr ) } } else { // Found a match - const ssize_t ret = sock_recv( sockFd, request->buffer, request->length ); + const ssize_t ret = receiveRequest( sockFd, request); if ( ret != (ssize_t)request->length ) { logadd( LOG_DEBUG1, "receiving payload for a block reply failed" ); connection_read( request ); @@ -513,8 +527,13 @@ static void* connection_receiveThreadMain( void *sockPtr ) } unlock_rw( &altLock ); } - fuse_reply_buf( request->fuse_req, request->buffer, request->length ); - free( request ); + if( useCow ) { + cowfile_handleCallback( request ); + } + else { + fuse_reply_buf( request->fuse_req, container_of( request, dnbd3_async_parent_t, request )->buffer, request->length ); + free( request ); + } } } else if ( reply.cmd == CMD_GET_SERVERS ) { // List of known alt servers @@ -812,7 +831,7 @@ static void probeAltServers() } if ( request != NULL && removeRequest( request ) != NULL ) { // Request successfully removed from queue - const ssize_t ret = sock_recv( sock, request->buffer, request->length ); + ssize_t const ret = receiveRequest( sock, request); if ( ret != (ssize_t)request->length ) { logadd( LOG_DEBUG1, "%s probe: receiving payload for a block reply failed", hstr ); // Failure, add to queue again @@ -820,8 +839,13 @@ static void probeAltServers() goto fail; } // Success, reply to fuse - fuse_reply_buf( request->fuse_req, request->buffer, request->length ); - free( request ); + if( useCow ) { + cowfile_handleCallback( request ); + } + else { + fuse_reply_buf( request->fuse_req, container_of( request, dnbd3_async_parent_t, request )->buffer, request->length ); + free( request ); + } logadd( LOG_DEBUG1, "%s probe: Successful direct probe", hstr ); } else { // Wasn't a request that's in our request queue @@ -931,6 +955,19 @@ fail: } } +static size_t receiveRequest(const int sock, dnbd3_async_t* request ) { + if(useCow){ + cow_sub_request_t * cow_request = container_of( request, cow_sub_request_t, dRequest ); + if( cow_request->callback == readRemoteData){ + return sock_recv( sock, cow_request->buffer, request->length ); + } else{ + return sock_recv( sock, &cow_request->writeBuffer, request->length ); + } + } else { + return sock_recv( sock, container_of( request, dnbd3_async_parent_t, request )->buffer, request->length ); + } +} + static void switchConnection( int sockFd, alt_server_t *srv ) { struct sockaddr_storage addr; diff --git a/src/fuse/connection.h b/src/fuse/connection.h index b869ac6..b22e3ce 100644 --- a/src/fuse/connection.h +++ b/src/fuse/connection.h @@ -12,6 +12,7 @@ #include <fuse_lowlevel.h> + extern atomic_bool keepRunning; struct _dnbd3_async; @@ -21,15 +22,23 @@ typedef struct _dnbd3_async { uint64_t offset; uint32_t length; fuse_req_t fuse_req; - char buffer[]; // Must be last member! } dnbd3_async_t; +typedef struct _dnbd3_async_parent { + dnbd3_async_t request; + char buffer[]; // Must be last member! +} dnbd3_async_parent_t; + bool connection_init( const char *hosts, const char *image, const uint16_t rid, const bool learnNewServers ); bool connection_initThreads(); uint64_t connection_getImageSize(); +char * connection_getImageName(); + +uint16_t connection_getImageRID(); + bool connection_read( dnbd3_async_t *request ); void connection_close(); diff --git a/src/fuse/cowDoc/img/datastructure.jpg b/src/fuse/cowDoc/img/datastructure.jpg Binary files differnew file mode 100644 index 0000000..d471d2a --- /dev/null +++ b/src/fuse/cowDoc/img/datastructure.jpg diff --git a/src/fuse/cowDoc/img/readrequest.svg b/src/fuse/cowDoc/img/readrequest.svg new file mode 100644 index 0000000..a16f95c --- /dev/null +++ b/src/fuse/cowDoc/img/readrequest.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Do not edit this file with editors other than diagrams.net --> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="719px" height="701px" viewBox="-0.5 -0.5 719 701" content="<mxfile host="app.diagrams.net" modified="2022-06-27T15:42:31.682Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36 OPR/87.0.4390.45" etag="_RkRrWUXEJff4oZ7Tmkg" version="20.0.3"><diagram id="otKvBIWVLEUd5MDrhZ-X" name="Page-1">5VpZc5swEP4tffBjOiAO48fm6jHpTGfcmTSPspENLSAihI376yuBZA45NrGxoclMxmFXu5LY3W8lrRgZN2H2mcDY+45dFIyA5mYj43YEgG4CMOJ/mrspOOOJVTCWxHeFUMmY+n+RYGqCm/ouSmqCFOOA+nGdOcdRhOa0xoOE4HVdbIGD+qgxXCKFMZ3DQOU++i71Cq4DxiX/C/KXnhxZtydFSwilsHiTxIMuXldYxt3IuCEY0+IpzG5QwI0n7VLo3b/Qup0YQRFto4BW8ZRkP52Hr4+mt/LJ+ttmdqWbRTcrGKTijcVs6UaaALnMIoLEhHp4iSMY3JXca4LTyEV8HI1RpcwDxjFj6oz5G1G6Ee6FKcWM5dEwEK0LHFHRqPMuijnwgV98WcFKcErmaM8bGiJoIFkiukdusnUJi2WEQ0TJhukRFEDqr+rzgCKolls5ofqJELipCMTYj2hS6fkHZzABgQ/LFMEh0KHbNR+yh6JHSVWmVrJyP7/C54bicoKgm7/rc4oSqgRA6V7uq7XnUzSNYW71NUN93ZWQzIUnTbD15AoRirL9vlRtLxRMp24kabN1iUddgsyrYFHK7fJWxcJHGFBTLLiAQYLeGHLGLZEDToXOSc4Y70hgdsDme53EMKr5w35OebLNbXaV5Eb7xAR0Lc5yy8l29rTk/2cBnv9hAlyZ62uMhnxCTACGPO6LX5XMx58R2RFeLBJEq4pBMY6GIlc22oJ3X8y70FSx6OFwliaHcdgB8GQ22gKvLfKccyEPOIq3KUkHATxmU7L5JRpz4ol3/tGS5G0mBiuojaQyn27V2HNFi1GlEiekTocgd9qC3OwT5AZ4Fxm3rTOMF3B7GWeoGOw84354l/nWAkPLt7qKu6Hk2w5hN2kJO71X2BmT4ebA7eJXWe+easvd7sWvByca4xOdeNRBz9Tq0J40zuqK+AnStnVIfK/8eQ6davB2tmj8p9neaJ79Qe/Z3n4P2V6XZcSD6d7qM90Da7jpvg9v9HsAkdO8ZJlh+CmrdSnufClrV/nnbQHEagmQU/c1p/lBzVYhZoPy2xr2E6GML9Ai0hv+uXwhy2gbt/bZCllqEb6vpbbcu7+ucKWcAUQhSz5fuJDVtlp98qq+e1PduKOw9EbkFPMXSo3g6WB7DdSt2xvLg8Bs6WCn112bep86oDzYLDD1nweNYcbtsTmxy3hvW9Do5eK6me4OVCga4he55gZqyUHcc+fwS8qNdu/7EcvuHYfD/A7k2Eu1DnFotD2g9oLDMXgdEJvyl/ngRD08N5G4IDjk3kCEAWsAK+MAEKmebIXRXBypB4Wev9FpVgZsp6X9jvhIh5HlV3NFjJbfHhp3/wA=</diagram></mxfile>" style="background-color: rgb(255, 255, 255);"><defs/><g><path d="M 520 50 L 520 130 L 446.37 130" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 441.12 130 L 448.12 126.5 L 446.37 130 L 448.12 133.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="460" y="10" width="120" height="40" rx="16.8" ry="16.8" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 30px; margin-left: 461px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">read request</div></div></div></foreignObject><text x="520" y="34" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">read request</text></switch></g><path d="M 200 490 L 200 523.63" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 200 528.88 L 196.5 521.88 L 200 523.63 L 203.5 521.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 510px; margin-left: 200px;"><div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">false</div></div></div></foreignObject><text x="200" y="513" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="10px" text-anchor="middle">false</text></switch></g><path d="M 200 410 L 260 450 L 200 490 L 140 450 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 450px; margin-left: 141px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><span style="font-size: 10px;">block == local &&<br />offset < endoffset</span></div></div></div></foreignObject><text x="200" y="454" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">block == local &&...</text></switch></g><path d="M 620 450 L 640 450 L 640 340 L 626.37 340" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 621.12 340 L 628.12 336.5 L 626.37 340 L 628.12 343.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 395px; margin-left: 640px;"><div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">true</div></div></div></foreignObject><text x="640" y="398" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="10px" text-anchor="middle">true</text></switch></g><path d="M 560 490 L 560 523.63" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 560 528.88 L 556.5 521.88 L 560 523.63 L 563.5 521.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 510px; margin-left: 560px;"><div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">false</div></div></div></foreignObject><text x="560" y="513" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="10px" text-anchor="middle">false</text></switch></g><path d="M 560 410 L 620 450 L 560 490 L 500 450 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 450px; margin-left: 501px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><span style="font-size: 10px;">block != local &&<br />offset < endoffset</span></div></div></div></foreignObject><text x="560" y="454" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">block != local &&...</text></switch></g><path d="M 380 170 L 380 203.63" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 380 208.88 L 376.5 201.88 L 380 203.63 L 383.5 201.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 190px; margin-left: 380px;"><div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">true</div></div></div></foreignObject><text x="380" y="193" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="10px" text-anchor="middle">true</text></switch></g><path d="M 380 90 L 380 60 L 20 60 L 20 620 L 380 620 L 380 643.63" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 380 648.88 L 376.5 641.88 L 380 643.63 L 383.5 641.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 340px; margin-left: 20px;"><div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">false</div></div></div></foreignObject><text x="20" y="343" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="10px" text-anchor="middle">false</text></switch></g><path d="M 380 90 L 440 130 L 380 170 L 320 130 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 130px; margin-left: 321px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><span style="font-size: 10px;">offset < endoffset</span></div></div></div></foreignObject><text x="380" y="134" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">offset < endoffset</text></switch></g><path d="M 320 250 L 200 250 L 200 303.63" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 200 308.88 L 196.5 301.88 L 200 303.63 L 203.5 301.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 250px; margin-left: 230px;"><div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">true</div></div></div></foreignObject><text x="230" y="253" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="10px" text-anchor="middle">true</text></switch></g><path d="M 440 250 L 560 250 L 560 303.63" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 560 308.88 L 556.5 301.88 L 560 303.63 L 563.5 301.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 250px; margin-left: 530px;"><div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">false</div></div></div></foreignObject><text x="530" y="253" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="10px" text-anchor="middle">false</text></switch></g><path d="M 380 210 L 440 250 L 380 290 L 320 250 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 250px; margin-left: 321px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><span style="font-size: 10px;">block == local</span></div></div></div></foreignObject><text x="380" y="254" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">block == local</text></switch></g><path d="M 200 370 L 200 403.63" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 200 408.88 L 196.5 401.88 L 200 403.63 L 203.5 401.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="140" y="310" width="120" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 340px; margin-left: 141px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">move to next block</div></div></div></foreignObject><text x="200" y="344" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">move to next block</text></switch></g><path d="M 140 450 L 120 450 L 120 340 L 133.63 340" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 138.88 340 L 131.88 343.5 L 133.63 340 L 131.88 336.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 395px; margin-left: 120px;"><div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">true</div></div></div></foreignObject><text x="120" y="398" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="10px" text-anchor="middle">true</text></switch></g><path d="M 560 370 L 560 403.63" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 560 408.88 L 556.5 401.88 L 560 403.63 L 563.5 401.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="500" y="310" width="120" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 340px; margin-left: 501px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">move to next block</div></div></div></foreignObject><text x="560" y="344" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">move to next block</text></switch></g><path d="M 140 560 L 60 560 L 60 130 L 313.63 130" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 318.88 130 L 311.88 133.5 L 313.63 130 L 311.88 126.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="140" y="530" width="120" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 560px; margin-left: 141px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">read blocks local</div></div></div></foreignObject><text x="200" y="564" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">read blocks local</text></switch></g><path d="M 620 560 L 700 560 L 700 130 L 446.37 130" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 441.12 130 L 448.12 126.5 L 446.37 130 L 448.12 133.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="500" y="530" width="120" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 560px; margin-left: 501px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">read blocks from server</div></div></div></foreignObject><text x="560" y="564" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">read blocks from ser...</text></switch></g><rect x="320" y="650" width="120" height="40" rx="16.8" ry="16.8" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 670px; margin-left: 321px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">read done</div></div></div></foreignObject><text x="380" y="674" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">read done</text></switch></g></g><switch><g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/><a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank"><text text-anchor="middle" font-size="10px" x="50%" y="100%">Text is not SVG - cannot display</text></a></switch></svg>
\ No newline at end of file diff --git a/src/fuse/cowDoc/readme.md b/src/fuse/cowDoc/readme.md new file mode 100644 index 0000000..9ad7fa2 --- /dev/null +++ b/src/fuse/cowDoc/readme.md @@ -0,0 +1,341 @@ + +# Fuse Copy on Write (CoW) + +### Table of Contents +1. [Introduction](#introduction) +2. [Usage](#usage) +3. [Implementation Details](#implementation-details) +4. [REST Api](#rest-api) + + +# Introduction + +This extension of the fuse dnbd3 client makes it possible to mount images in a writable way. The changes are saved in a separate file ) on the client computer (also called Copy on Write, cow for short). These changes are uploaded to the cow server in the background. As soon as the user unmounts the image, all remaining changes are uploaded. Once all have been uploaded, the changes can be merged into a copy of the original image on the cow server (this can be set in the start parameters). + + +A typical use case is updating or adding software to an existing image. + +# Usage + +### New Parameters +- `-c <path>` Enables the cow functionality. The `path` parameter sets the path for the temporary `meta` and `data` files in which the changes are saved. +- `-C <address>` sets the address of the cow server. The Cow server is responsible for merging the original image with the client's changes. + +- `- L <path>` Similar to `-c <path>`, but instead of creating a new session, an existing one is loaded from the specified path. +- `-m` the client requests a merge after the image has been unmounted and all changes have been uploaded. + +- `cowStatFile` creates a status file at the same location as the data and meta file. The file contains information about the current session, for more information see [here](#status). +- `--cowStatStdout` similar to `--cowStatFile` but the information will be printed in the stdout. + +Example parameters for creating a new cow session: +``` +./dnbd3-fuse "/home/user/VMs/mount" -f -h localhost -i imagename -c "/home/user/temp" -C "192.168.178.20:5000" --cowStatStdout -m + +``` + +# Implementation Details + + + + + +## Data structure + +The data structure is divided into two main parts. The actual data of the writing on the image and the corresponding metadata. It is also important to distinguish between a dnbd3 block, which is 4096 bytes in size, and a cow block, which combines 320 dnbd3 blocks. A cow block has a `cow_block_metadata_t` structure that contains the corresponding metadata. The metadata is used to determine if a dnbd3 block has been written to, where that block is stored in the `data` file, when it was last modified and when it was uploaded. But more on this later. + + +### Blockmetadata + +![Datastructure](img/datastructure.jpg) + +TThe data structure for storing "cow_block_metadata_t" contains a layer 1 (L1) and a layer 2 (L2). L1 contains pointers to the L2's. +The entire L1 array is initialised at the beginning and cannot be resized, therefore the size of the L1 array limits the total size of the image. +The L2's are created dynamically as they are needed. So at the beginning all L1 pointers are zero. The L2's are arrays containing 1024 +`cow_block_metadata_t` structs. + +```C +typedef struct cow_block_metadata +{ + atomic_int_least64_t offset; + atomic_uint_least64_t timeChanged; + atomic_uint_least64_t uploads; + atomic_char bitfield[40]; +} cow_block_metadata_t; +``` +Each `cow_block_metadata_t` contains a 40 byte, 320 bit bit field. The bit field indicates whether the corresponding dnbd3 block contains data or not. If, for example, the bit field begins with 01... the first 4096 contain no data and the next 4096 contain data. +So each `cow_block_metadata_t` stores the metadata of up to 320*4096 bytes if all bits are set to 1. The offset field is the offset where in the data file the corresponding data is stored. The property timeChanged contains the Unix time when the block was last changed. It is 0 if it has never been changed or if the last changes have already been uploaded. + + +The L2 arrays and `cow_block_metadata_t` are sorted according to the original offsets of the image. Thus the first L1 pointer, i.e. the first L2 array, addresses the first 1024 * 320 * 4096 bytes (L2Size * bitfieldsize * DNBD3Blocksize) of data and so on. + + +For example, to get the "cow_block_metadata_t" for offset 4033085440, one would take L1[3], since +``` +4033085440 / ( COW_L2_STORAGE_CAPACITY ) ≈ 3.005 +``` + + Then one would take the fifth `cow_block_metadata_t` in the L2 array because of +``` +(4033085440 mod COW_L2_STORAGE_CAPACITY) / COW_METADATA_STORAGE_CAPACITY = 5 +``` +Where: +``` +COW_L2_STORAGE_CAPACITY = 1024 * 320 * 4096 +COW_METADATA_STORAGE_CAPACITY = 320 * 4096 +``` + + + +### Read Request + + + +When a read request is made, it is checked for each 4096-byte block whether the block already exists locally on the computer (i.e. has already been written once). If so, it is read from the hard disk, otherwise it is requested from the dnbd3 server. To increase performance, several subsequent blocks that are also local/non-local are combined into a larger read from the hard disk or request from the server. + +![readrequest](img/readrequest.svg) + +The diagram above is somewhat simplified for clarity. The server's read operations are asynchronous. This means that it does not wait for a response from the server, but continues with the next blocks. As soon as the server's response is complete, the data is written to the fuse buffer. +Each request to the dnbd3 server increases the variable `workCounter` by one, and each time a request is completed, it is decreased by one. As soon as `workCounter` is 0 again, fuse_request is returned. This is done to ensure that all asynchronous requests are completed before the request is returned. + + + +For local blocks, the loop must be interrupted as soon as the end of a `cow_block_metadata_t` is reached, as the next data offset of the next `cow_block_metadata_t` is most likely not directly after it in the data file. + +### Write Request +If, in a write request, the beginning or end does not match a multiple of 4096, the beginning and/or end block must be padded. +This is because each 4096-byte block requires complete data, because if the bit in the bit field for that block is set, all data is read locally. +To fill the block, if it is still within the range of the original image size, the missing bytes are requested from the dnbd3 server. If it is outside the original image size (because the image has become larger), the missing bytes are filled with 0. +The write request calculates the corresponding `cow_block_metadata_t` from the offset. If the corresponding `cow_block_metadata_t` does not yet exist, it is created. The data will be written to the data file, at the offset stored in the `cow_block_metadata_t`. +Then the corresponding bit in the bit fields is set and the `timeChanged` is updated. If there is more data to write, the next `cow_block_metadata_t` is calculated and the above steps are repeated. +The variable `workCounter` is also used here to ensure that the padding of the data occurs before the Fuse request returns. + + +### Block Upload +For uploading blocks, there is a background thread that periodically loops over all Cow blocks and checks whether `timeChanged` is not 0 and the time difference between now and `timeChanged` is greater than `COW_MIN_UPLOAD_DELAY`. If so, the block is uploaded. The `timeChanged` before the upload is buffered. After the upload, `timeChanged` is set to 0 if it still has the same time as the temporarily stored one (if not, there was a change during the upload and it has to be uploaded again). Once the image is unmounted, `COW_MIN_UPLOAD_DELAY` is ignored and all blocks that have a time other than 0 are uploaded. The upload is done via a [rest request](#/api/file/update). There are two different limits for the number of parallel uploads in the [config.h](#config-variables). + +## Files +When a new CoW session is started, a new `meta`, `data` and, if so set in the command line arguments, a `status.txt` file is created. + +### status +The file `status.txt` can be activated with the command line parameter `--cowStatFile`. + +The file will contain the following: + +``` +uuid=<uuid> +state=backgroundUpload +inQueue=0 +modifiedBlocks=0 +idleBlocks=0 +totalBlocksUploaded=0 +activeUploads:0 +ulspeed=0.00 +``` +- The `uuid` is the session uuid used by the Cow server to identify the session. + +- The `status` is `backgroundUpload` when the image is still mounted and cow blocks are uploaded in the background. +It is `uploading` when the image has been unmounted and all blocks that have not yet been uploaded are uploaded. +It is `done` when the image has been unmounted and all blocks have been uploaded. +- `Queue` are the cow blocks that are currently being uploaded or are waiting for a free slot. +- `ModifiedBlocks` are cow blocks that have changes that have not yet been uploaded to the server because the changes are too recent. +- `totalBlocksUploaded` the total amount of cow blocks uploaded since the image was mounted. +- `activeUploads` is the number of blocks currently being uploaded. +- `ulspeed` the current upload speed in kb/s. + +Once all blocks have been uploaded, the status is set to `done`. +If you define `COW_DUMP_BLOCK_UPLOADS`, a list of all blocks, sorted by the number of uploads, is copied to the status.txt file after the block upload is completed. + +With the command line parameter `--cowStatStdout` the same output of the stats file will be printed in stdout. + +### meta +The `meta` file contains the following header: +```C +// cowfile.h +typedef struct cowfile_metadata_header +{ + uint64_t magicValue; // 8byte + atomic_uint_least64_t imageSize; // 8byte + int32_t version; // 4byte + int32_t blocksize; // 4byte + uint64_t originalImageSize; // 8byte + uint64_t metaDataStart; // 8byte + int32_t bitfieldSize; // 4byte + int32_t nextL2; // 4byte + atomic_uint_least64_t metadataFileSize; // 8byte + atomic_uint_least64_t dataFileSize; // 8byte + uint64_t maxImageSize; // 8byte + uint64_t creationTime; // 8byte + char uuid[40]; // 40byte + char imageName[200]; // 200byte +} cowfile_metadata_header_t; +``` +After this header, the above-mentioned l1 and then the l2 data structure begins at byte 8192. +### data +The `data` files contain the magicValue and at the 40 * 8 * 4096 offset (capacity of a cowfile_metadata_header_t) the first data block starts. + + + +### magic values in the file headers +The magic values in both files are used to ensure that an appropriate file is read and that the machine has the correct endianness. +```C +//config.h +#define COW_FILE_META_MAGIC_VALUE ((uint64_t)0xEBE44D6E72F7825E) // Magic Value to recognize a Cow meta file +#define COW_FILE_DATA_MAGIC_VALUE ((uint64_t)0xEBE44D6E72F7825F) // Magic Value to recognize a Cow data file +``` + + +### Threads +This extension uses two new threads: +``` +tidCowUploader +tidStatUpdater +``` +```tidCowUploader``` is the thread that uploads blocks to the cow server. + +```tidStatUpdater``` updates the stats in stdout or the stats files (depending on parameters). + +### Locks + +This extension uses a new lock ```cow.l2CreateLock```. It is used when a new L2 array is allocated. + + + +### Config Variables +The following configuration variables have been added to ```config.h```. +```c +//config.h +// +++++ COW +++++ +#define COW_BITFIELD_SIZE 40 // NEVER CHANGE THIS OR THE WORLD WILL ALSO END! +#define COW_FILE_META_MAGIC_VALUE ((uint64_t)0xEBE44D6E72F7825E) // Magic Value to recognize a Cow meta file +#define COW_FILE_DATA_MAGIC_VALUE ((uint64_t)0xEBE44D6E72F7825F) // Magic Value to recognize a Cow data file +#define COW_MIN_UPLOAD_DELAY 60 // in seconds +#define COW_STATS_UPDATE_TIME 5 // time in seconds the cow status files gets updated (while uploading blocks) +#define COW_MAX_PARALLEL_UPLOADS 10 // maximum number of parallel uploads +#define COW_MAX_PARALLEL_BACKGROUND_UPLOADS 2 // maximum number of parallel uploads while the image is still mounted +#define COW_URL_STRING_SIZE 500 // Max string size for an url +#define COW_SHOW_UL_SPEED 1 // enable display of ul speed in cow status file +#define COW_MAX_IMAGE_SIZE 1000LL * 1000LL * 1000LL * 1000LL; // Maximum size an image can have(tb*gb*mb*kb) +// +++++ COW API Endpoints +++++ +#define COW_API_CREATE "%s/api/File/Create" +#define COW_API_UPDATE "%s/api/File/Update?guid=%s&BlockNumber=%lu" +#define COW_API_START_MERGE "%s/api/File/StartMerge" +``` + +- ```COW_MIN_UPLOAD_DELAY``` sets the minimum time in seconds that must have elapsed since the last change to a cow block before it is uploaded. This value can be fine-tuned. A larger value usually reduces the double uploading of blocks. A smaller value reduces the time for the final upload after the image has been unmounted. If you set `COW_DUMP_BLOCK_UPLOADS` and set the command line parameter `--cowStatFile`, then a list of all blocks, sorted by the number of uploads, will be written to the status.txt file after the block upload is complete. This can help in fine-tuning `COW_MIN_UPLOAD_DELAY`. +- ```COW_STATS_UPDATE_TIME``` defines the update frequency of the stdout output/statistics file in seconds. Setting it too low could affect performance as a loop runs over all blocks. +- ```COW_MAX_PARALLEL_BACKGROUND_UPLOADS``` defines the maximum number of parallel block uploads. This number is used when the image is still mounted and the user is still using it. +- ```COW_MAX_PARALLEL_UPLOADS``` defines the maximum number of parallel block uploads. This number is used once the image has been unmounted to upload the remaining modified blocks. + + + +# REST Api +The following Rest API is used to transmit the data and commands to the cow server: + +### /api/File/Create + +#### POST +##### Responses + +| Code | Description | +| ---- | ----------- | +| 200 | Success | + +This request is used as soon as a new cow session is created. The returned guid is used in all subsequent requests to identify the session. + + +### /api/File/Update + +#### POST +##### Parameters + +| Name | Located in | Description | Required | Schema | +| ---- | ---------- | ----------- | -------- | ---- | +| guid | query | | Yes | string (uuid) | +| blockNumber | query | | Yes | integer | + +##### Responses + +| Code | Description | +| ---- | ----------- | +| 200 | Success | + +Used to upload a data block. The block number is the absolute block number. The body contains an "application/octet-stream", where the first bytes are the bit field, directly followed by the actual block data. + + +### /api/File/StartMerge + +#### POST +##### Parameters + +| Name | Located in | Description | Required | Schema | +| ---- | ---------- | ----------- | -------- | ---- | +| guid | Form | | Yes | string (uuid) | +| originalFileSize | Form | | Yes | integer | +| newFileSize | Form | | Yes | integer | +##### Responses + +| Code | Description | +| ---- | ----------- | +| 200 | Success | +Used to start the merge on the server. + +### /api/File/GetTopModifiedBlocks + +#### GET +##### Parameters + +| Name | Located in | Description | Required | Schema | +| ---- | ---------- | ----------- | -------- | ---- | +| guid | query | | Yes | string (uuid) | +| amount | query | | Yes | integer | + +##### Responses + +| Code | Description | +| ---- | ----------- | +| 200 | Success | + +This request returns a list containing the block IDs and the number of uploads of this block, sorted by the number of uploads. This is useful to adjust the `COW_MIN_UPLOAD_DELAY`. + +### /api/File/Status + +#### GET +##### Parameters + +| Name | Located in | Description | Required | Schema | +| ---- | ---------- | ----------- | -------- | ---- | +| guid | query | | Yes | string (uuid) | + +##### Responses + +| Code | Description | +| ---- | ----------- | +| 200 | Success | + +Returns the SessionStatus model that provides information about the session. + +### Models + +#### BlockStatistics + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| blockNumber | integer | | Yes | +| modifications | integer | | Yes | + +#### SessionState + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| SessionState | string | | | + +#### SessionStatus + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| state | string | _Enum:_ `"Copying"`, `"Active"`, `"Merging"`, `"Done"`, `"Failed"` | Yes | +| imageName | string | | Yes | +| originalImageVersion | integer | | Yes | +| newImageVersion | integer | | Yes | +| mergedBlocks | integer | | Yes | +| totalBlocks | integer | | Yes | diff --git a/src/fuse/cowfile.c b/src/fuse/cowfile.c new file mode 100644 index 0000000..965e6f4 --- /dev/null +++ b/src/fuse/cowfile.c @@ -0,0 +1,1535 @@ +#include "cowfile.h" +#include "math.h" +extern void image_ll_getattr( fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi ); + +static int cowFileVersion = 1; +static bool statStdout; +static bool statFile; +static pthread_t tidCowUploader; +static pthread_t tidStatUpdater; +static char *cowServerAddress; +static CURL *curl; +static cowfile_metadata_header_t *metadata = NULL; +static atomic_uint_fast64_t bytesUploaded; +static uint64_t totalBlocksUploaded = 0; +static atomic_int activeUploads = 0; +atomic_bool uploadLoop = true; +atomic_bool uploadLoopDone = false; + +static struct cow +{ + pthread_mutex_t l2CreateLock; + int fhm; + int fhd; + int fhs; + char *metadata_mmap; + l1 *l1; + l2 *firstL2; + size_t maxImageSize; + size_t l1Size; //size of l1 array + +} cow; + +/** + * @brief Computes the l1 offset from the absolute file offset + * + * @param offset absolute file offset + * @return int l2 offset + */ +static int getL1Offset( size_t offset ) +{ + return (int)( offset / COW_L2_STORAGE_CAPACITY ); +} + +/** + * @brief Computes the l2 offset from the absolute file offset + * + * @param offset absolute file offset + * @return int l2 offset + */ +static int getL2Offset( size_t offset ) +{ + return (int)( ( offset % COW_L2_STORAGE_CAPACITY ) / COW_METADATA_STORAGE_CAPACITY ); +} + +/** + * @brief Computes the bit in the bitfield from the absolute file offset + * + * @param offset absolute file offset + * @return int bit(0-319) in the bitfield + */ +static int getBitfieldOffset( size_t offset ) +{ + return (int)( offset / DNBD3_BLOCK_SIZE ) % ( COW_BITFIELD_SIZE * 8 ); +} + +/** + * @brief Sets the specified bits in the specified range threadsafe to 1. + * + * @param byte of a bitfield + * @param from start bit + * @param to end bit + * @param value set bits to 1 or 0 + */ +static void setBits( atomic_char *byte, int from, int to, bool value ) +{ + char mask = (char)( ( 255 >> ( 7 - ( to - from ) ) ) << from ); + if ( value ) { + atomic_fetch_or( byte, mask ); + } else { + atomic_fetch_and( byte, ~mask ); + } +} + +/** + * @brief Sets the specified bits in the specified range threadsafe to 1. + * + * @param bitfield of a cow_block_metadata + * @param from start bit + * @param to end bit + * @param value set bits to 1 or 0 + */ +static void setBitsInBitfield( atomic_char *bitfield, int from, int to, bool value ) +{ + assert( from >= 0 || to < COW_BITFIELD_SIZE * 8 ); + int start = from / 8; + int end = to / 8; + + for ( int i = start; i <= end; i++ ) { + setBits( ( bitfield + i ), from - i * 8, MIN( 7, to - i * 8 ), value ); + from = ( i + 1 ) * 8; + } +} + +/** + * @brief Checks if the n bit of a bit field is 0 or 1. + * + * @param bitfield of a cow_block_metadata + * @param n the bit which should be checked + */ +static bool checkBit( atomic_char *bitfield, int n ) +{ + return ( atomic_load( ( bitfield + ( n / 8 ) ) ) >> ( n % 8 ) ) & 1; +} + + +/** + * @brief Implementation of CURLOPT_WRITEFUNCTION , this function will be called when + * the server sends back data. + * for more details see: https://curl.se/libcurl/c/CURLOPT_WRITEFUNCTION .html + * + * @param buffer that contains the response data from the server + * @param itemSize size of one item + * @param nitems number of items + * @param response userdata which will later contain the uuid + * @return size_t size that have been read + */ +size_t curlCallbackCreateSession( char *buffer, size_t itemSize, size_t nitems, void *response ) +{ + size_t bytes = itemSize * nitems; + if ( strlen( response ) + bytes != 36 ) { + logadd( LOG_INFO, "strlen(response): %lu bytes: %lu \n", strlen( response ), bytes ); + return bytes; + } + + strncat( response, buffer, 36 ); + return bytes; +} + +/** + * @brief Create a Session with the cow server and gets the session guid. + * + * @param imageName + * @param version of the original Image + */ +bool createSession( const char *imageName, uint16_t version ) +{ + CURLcode res; + char url[COW_URL_STRING_SIZE]; + snprintf( url, COW_URL_STRING_SIZE, COW_API_CREATE, cowServerAddress ); + logadd( LOG_INFO, "COW_API_CREATE URL: %s", url ); + curl_easy_setopt( curl, CURLOPT_POST, 1L ); + curl_easy_setopt( curl, CURLOPT_URL, url ); + + curl_mime *mime; + curl_mimepart *part; + mime = curl_mime_init( curl ); + part = curl_mime_addpart( mime ); + curl_mime_name( part, "imageName" ); + curl_mime_data( part, imageName, CURL_ZERO_TERMINATED ); + part = curl_mime_addpart( mime ); + curl_mime_name( part, "version" ); + char buf[sizeof( int ) * 3 + 2]; + snprintf( buf, sizeof buf, "%d", version ); + curl_mime_data( part, buf, CURL_ZERO_TERMINATED ); + + part = curl_mime_addpart( mime ); + curl_mime_name( part, "bitfieldSize" ); + snprintf( buf, sizeof buf, "%d", metadata->bitfieldSize ); + curl_mime_data( part, buf, CURL_ZERO_TERMINATED ); + + curl_easy_setopt( curl, CURLOPT_MIMEPOST, mime ); + + metadata->uuid[0] = '\0'; + curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, curlCallbackCreateSession ); + curl_easy_setopt( curl, CURLOPT_WRITEDATA, &metadata->uuid ); + + res = curl_easy_perform( curl ); + curl_mime_free( mime ); + + /* Check for errors */ + if ( res != CURLE_OK ) { + logadd( LOG_ERROR, "COW_API_CREATE failed: %s\n", curl_easy_strerror( res ) ); + return false; + } + + long http_code = 0; + curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &http_code ); + if ( http_code != 200 ) { + logadd( LOG_ERROR, "COW_API_CREATE failed http: %ld\n", http_code ); + return false; + } + curl_easy_reset( curl ); + metadata->uuid[36] = '\0'; + logadd( LOG_DEBUG1, "Cow session started, guid: %s\n", metadata->uuid ); + return true; +} + +/** + * @brief Implementation of CURLOPT_READFUNCTION, this function will first send the bit field and + * then the block data in one bitstream. this function is usually called multiple times per block, + * because the buffer is usually not large for one block and its bitfield. + * for more details see: https://curl.se/libcurl/c/CURLOPT_READFUNCTION.html + * + * @param ptr to the buffer + * @param size of one element in buffer + * @param nmemb number of elements in buffer + * @param userdata from CURLOPT_READFUNCTION + * @return size_t size written in buffer + */ +size_t curlReadCallbackUploadBlock( char *ptr, size_t size, size_t nmemb, void *userdata ) +{ + cow_curl_read_upload_t *uploadBlock = (cow_curl_read_upload_t *)userdata; + size_t len = 0; + if ( uploadBlock->position < (size_t)metadata->bitfieldSize ) { + size_t lenCpy = MIN( metadata->bitfieldSize - uploadBlock->position, size * nmemb ); + memcpy( ptr, uploadBlock->block->bitfield + uploadBlock->position, lenCpy ); + uploadBlock->position += lenCpy; + len += lenCpy; + } + if ( uploadBlock->position >= (size_t)metadata->bitfieldSize ) { + size_t lenRead = MIN( COW_METADATA_STORAGE_CAPACITY - ( uploadBlock->position - ( metadata->bitfieldSize ) ), + ( size * nmemb ) - len ); + off_t inBlockOffset = uploadBlock->position - metadata->bitfieldSize; + size_t lengthRead = pread( cow.fhd, ( ptr + len ), lenRead, uploadBlock->block->offset + inBlockOffset ); + + if ( lenRead != lengthRead ) { + // fill up since last block may not be a full block + lengthRead = lenRead; + } + uploadBlock->position += lengthRead; + len += lengthRead; + } + return len; +} + + +/** + * @brief Requests the merging of the image on the cow server. + + */ +bool mergeRequest() +{ + CURLcode res; + curl_easy_setopt( curl, CURLOPT_POST, 1L ); + + char url[COW_URL_STRING_SIZE]; + snprintf( url, COW_URL_STRING_SIZE, COW_API_START_MERGE, cowServerAddress ); + curl_easy_setopt( curl, CURLOPT_URL, url ); + + + curl_mime *mime; + curl_mimepart *part; + mime = curl_mime_init( curl ); + + part = curl_mime_addpart( mime ); + curl_mime_name( part, "guid" ); + curl_mime_data( part, metadata->uuid, CURL_ZERO_TERMINATED ); + + part = curl_mime_addpart( mime ); + curl_mime_name( part, "originalFileSize" ); + char buf[21]; + snprintf( buf, sizeof buf, "%" PRIu64, metadata->originalImageSize ); + curl_mime_data( part, buf, CURL_ZERO_TERMINATED ); + + part = curl_mime_addpart( mime ); + curl_mime_name( part, "newFileSize" ); + snprintf( buf, sizeof buf, "%" PRIu64, metadata->imageSize ); + curl_mime_data( part, buf, CURL_ZERO_TERMINATED ); + + curl_easy_setopt( curl, CURLOPT_MIMEPOST, mime ); + + + res = curl_easy_perform( curl ); + if ( res != CURLE_OK ) { + logadd( LOG_WARNING, "COW_API_START_MERGE failed: %s\n", curl_easy_strerror( res ) ); + curl_easy_reset( curl ); + return false; + } + long http_code = 0; + curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &http_code ); + if ( http_code != 200 ) { + logadd( LOG_WARNING, "COW_API_START_MERGE failed http: %ld\n", http_code ); + curl_easy_reset( curl ); + return false; + } + curl_easy_reset( curl ); + curl_mime_free( mime ); + return true; +} + +/** + * @brief Wrapper for mergeRequest so if its fails it will be tried again. + * + */ +void startMerge() +{ + int fails = 0; + bool success = false; + success = mergeRequest(); + while ( fails <= 5 && !success ) { + fails++; + logadd( LOG_WARNING, "Trying again. %i/5", fails ); + mergeRequest(); + } +} + +/** + * @brief Implementation of the CURLOPT_XFERINFOFUNCTION. + * For more infos see: https://curl.se/libcurl/c/CURLOPT_XFERINFOFUNCTION.html + * + * Each active transfer callbacks this function. + * This function computes the uploaded bytes between each call and adds it to + * bytesUploaded, which is used to compute the kb/s uploaded over all transfers. + * + * @param clientp + * @param ulNow number of bytes uploaded by this transfer so far. + * @return int always returns 0 to continue the callbacks. + */ +int progress_callback( void *clientp, __attribute__( ( unused ) ) curl_off_t dlTotal, + __attribute__( ( unused ) ) curl_off_t dlNow, __attribute__( ( unused ) ) curl_off_t ulTotal, curl_off_t ulNow ) +{ + CURL *eh = (CURL *)clientp; + cow_curl_read_upload_t *curlUploadBlock; + CURLcode res; + res = curl_easy_getinfo( eh, CURLINFO_PRIVATE, &curlUploadBlock ); + if ( res != CURLE_OK ) { + logadd( LOG_ERROR, "ERROR" ); + return 0; + } + bytesUploaded += ( ulNow - curlUploadBlock->ulLast ); + curlUploadBlock->ulLast = ulNow; + return 0; +} + +/** + * @brief Updates the status to the stdout/statfile depending on the startup parameters. + * + * @param inQueue Blocks that have changes old enough to be uploaded. + * @param modified Blocks that have been changed but whose changes are not old enough to be uploaded. + * @param idle Blocks that do not contain changes that have not yet been uploaded. + * @param speedBuffer ptr to char array that contains the current upload speed. + */ + +void updateCowStatsFile( uint64_t inQueue, uint64_t modified, uint64_t idle, char *speedBuffer ) +{ + char buffer[300]; + char state[30]; + if ( uploadLoop ) { + snprintf( state, 30, "%s", "backgroundUpload" ); + } else if ( !uploadLoopDone ) { + snprintf( state, 30, "%s", "uploading" ); + } else { + snprintf( state, 30, "%s", "done" ); + } + + int len = snprintf( buffer, 300, + "state=%s\n" + "inQueue=%" PRIu64 "\n" + "modifiedBlocks=%" PRIu64 "\n" + "idleBlocks=%" PRIu64 "\n" + "totalBlocksUploaded=%" PRIu64 "\n" + "activeUploads:%i\n" + "%s=%s", + state, inQueue, modified, idle, totalBlocksUploaded, activeUploads, COW_SHOW_UL_SPEED ? "ulspeed" : "", + speedBuffer ); + + if ( statStdout ) { + logadd( LOG_INFO, "%s", buffer ); + } + + if ( statFile ) { + if ( pwrite( cow.fhs, buffer, len, 43 ) != len ) { + logadd( LOG_WARNING, "Could not update cow status file" ); + } + if ( ftruncate( cow.fhs, 43 + len ) ) { + logadd( LOG_WARNING, "Could not truncate cow status file" ); + } +#ifdef COW_DUMP_BLOCK_UPLOADS + if ( !uploadLoop && uploadLoopDone ) { + dumpBlockUploads(); + } +#endif + } +} +int cmpfunc( const void *a, const void *b ) +{ + return (int)( ( (cow_block_upload_statistics_t *)b )->uploads - ( (cow_block_upload_statistics_t *)a )->uploads ); +} +/** + * @brief Writes all block numbers sorted by the number of uploads into the statsfile. + * + */ +void dumpBlockUploads() +{ + long unsigned int l1MaxOffset = 1 + ( ( metadata->imageSize - 1 ) / COW_L2_STORAGE_CAPACITY ); + + cow_block_upload_statistics_t blockUploads[l1MaxOffset * COW_L2_SIZE]; + uint64_t currentBlock = 0; + for ( long unsigned int l1Offset = 0; l1Offset < l1MaxOffset; l1Offset++ ) { + if ( cow.l1[l1Offset] == -1 ) { + continue; + } + for ( int l2Offset = 0; l2Offset < COW_L2_SIZE; l2Offset++ ) { + cow_block_metadata_t *block = ( cow.firstL2[cow.l1[l1Offset]] + l2Offset ); + + blockUploads[currentBlock].uploads = block->uploads; + blockUploads[currentBlock].blocknumber = ( l1Offset * COW_L2_SIZE + l2Offset ); + currentBlock++; + } + } + qsort( blockUploads, currentBlock, sizeof( cow_block_upload_statistics_t ), cmpfunc ); + lseek( cow.fhs, 0, SEEK_END ); + + dprintf( cow.fhs, "\n\nblocknumber: uploads\n==Block Upload Dump===\n" ); + for ( uint64_t i = 0; i < currentBlock; i++ ) { + dprintf( cow.fhs, "%" PRIu64 ": %" PRIu64 " \n", blockUploads[i].blocknumber, blockUploads[i].uploads ); + } +} + +/** + * @brief Starts the upload of a given block. + * + * @param cm Curl_multi + * @param curlUploadBlock containing the data for the block to upload. + */ +bool addUpload( CURLM *cm, cow_curl_read_upload_t *curlUploadBlock, struct curl_slist *headers ) +{ + CURL *eh = curl_easy_init(); + + char url[COW_URL_STRING_SIZE]; + + snprintf( url, COW_URL_STRING_SIZE, COW_API_UPDATE, cowServerAddress, metadata->uuid, curlUploadBlock->blocknumber ); + + curl_easy_setopt( eh, CURLOPT_URL, url ); + curl_easy_setopt( eh, CURLOPT_POST, 1L ); + curl_easy_setopt( eh, CURLOPT_READFUNCTION, curlReadCallbackUploadBlock ); + curl_easy_setopt( eh, CURLOPT_READDATA, (void *)curlUploadBlock ); + curl_easy_setopt( eh, CURLOPT_PRIVATE, (void *)curlUploadBlock ); + // min upload speed of 1kb/s over 10 sec otherwise the upload is canceled. + curl_easy_setopt( eh, CURLOPT_LOW_SPEED_TIME, 10L ); + curl_easy_setopt( eh, CURLOPT_LOW_SPEED_LIMIT, 1000L ); + + curl_easy_setopt( + eh, CURLOPT_POSTFIELDSIZE_LARGE, (long)( metadata->bitfieldSize + COW_METADATA_STORAGE_CAPACITY ) ); + if ( COW_SHOW_UL_SPEED ) { + curlUploadBlock->ulLast = 0; + curl_easy_setopt( eh, CURLOPT_NOPROGRESS, 0L ); + curl_easy_setopt( eh, CURLOPT_XFERINFOFUNCTION, progress_callback ); + curl_easy_setopt( eh, CURLOPT_XFERINFODATA, eh ); + } + curl_easy_setopt( eh, CURLOPT_HTTPHEADER, headers ); + curl_multi_add_handle( cm, eh ); + + return true; +} + +/** + * @brief After an upload completes, either successful or unsuccessful this + * function cleans everything up. If unsuccessful and there are some tries left + * retries to upload the block. + * + * @param cm Curl_multi + * @param msg CURLMsg + * @return true returned if the upload was successful or retries are still possible. + * @return false returned if the upload was unsuccessful. + */ +bool finishUpload( CURLM *cm, CURLMsg *msg, struct curl_slist *headers ) +{ + bool status = true; + cow_curl_read_upload_t *curlUploadBlock; + CURLcode res; + CURLcode res2; + res = curl_easy_getinfo( msg->easy_handle, CURLINFO_PRIVATE, &curlUploadBlock ); + + long http_code = 0; + res2 = curl_easy_getinfo( msg->easy_handle, CURLINFO_RESPONSE_CODE, &http_code ); + + if ( res != CURLE_OK || res2 != CURLE_OK || http_code != 200 || msg->msg != CURLMSG_DONE ) { + curlUploadBlock->fails++; + logadd( LOG_ERROR, "COW_API_UPDATE failed %i/5: %s\n", curlUploadBlock->fails, + curl_easy_strerror( msg->data.result ) ); + if ( curlUploadBlock->fails <= 5 ) { + addUpload( cm, curlUploadBlock, headers ); + goto CLEANUP; + } + free( curlUploadBlock ); + status = false; + goto CLEANUP; + } + + // everything went ok, update timeChanged + atomic_compare_exchange_strong( &curlUploadBlock->block->timeChanged, &curlUploadBlock->time, 0 ); + + curlUploadBlock->block->uploads++; + + totalBlocksUploaded++; + free( curlUploadBlock ); +CLEANUP: + curl_multi_remove_handle( cm, msg->easy_handle ); + curl_easy_cleanup( msg->easy_handle ); + return status; +} + +/** + * @brief + * + * @param cm Curl_multi + * @param activeUploads ptr to integer which holds the number of current uploads + * @param breakIfNotMax will return as soon as there are not all upload slots used, so they can be filled up. + * @param foregroundUpload used to determine the number of max uploads. If true COW_MAX_PARALLEL_UPLOADS will be the limit, + * else COW_MAX_PARALLEL_BACKGROUND_UPLOADS. + * @return true returned if all upload's were successful + * @return false returned if one ore more upload's failed. + */ +bool MessageHandler( + CURLM *cm, atomic_int *activeUploads, bool breakIfNotMax, bool foregroundUpload, struct curl_slist *headers ) +{ + CURLMsg *msg; + int msgsLeft = -1; + bool status = true; + do { + curl_multi_perform( cm, activeUploads ); + + while ( ( msg = curl_multi_info_read( cm, &msgsLeft ) ) ) { + if ( !finishUpload( cm, msg, headers ) ) { + status = false; + } + } + if ( breakIfNotMax + && *activeUploads + < ( foregroundUpload ? COW_MAX_PARALLEL_UPLOADS : COW_MAX_PARALLEL_BACKGROUND_UPLOADS ) ) { + break; + } + // ony wait if there are active uploads + if ( *activeUploads ) { + curl_multi_wait( cm, NULL, 0, 1000, NULL ); + } + + } while ( *activeUploads ); + return status; +} + +/** + * @brief loops through all blocks and uploads them. + * + * @param ignoreMinUploadDelay If true uploads all blocks that have changes while + * ignoring COW_MIN_UPLOAD_DELAY + * @param cm Curl_multi + * @return true if all blocks uploaded successful + * @return false if one ore more blocks failed to upload + */ +bool uploaderLoop( bool ignoreMinUploadDelay, CURLM *cm ) +{ + bool success = true; + struct curl_slist *headers = NULL; + headers = curl_slist_append( headers, "Content-Type: application/octet-stream" ); + + long unsigned int l1MaxOffset = 1 + ( ( metadata->imageSize - 1 ) / COW_L2_STORAGE_CAPACITY ); + for ( long unsigned int l1Offset = 0; l1Offset < l1MaxOffset; l1Offset++ ) { + if ( cow.l1[l1Offset] == -1 ) { + continue; + } + for ( int l2Offset = 0; l2Offset < COW_L2_SIZE; l2Offset++ ) { + cow_block_metadata_t *block = ( cow.firstL2[cow.l1[l1Offset]] + l2Offset ); + if ( block->offset == -1 ) { + continue; + } + if ( block->timeChanged != 0 ) { + if ( ( time( NULL ) - block->timeChanged > COW_MIN_UPLOAD_DELAY ) || ignoreMinUploadDelay ) { + do { + if ( !MessageHandler( cm, &activeUploads, true, ignoreMinUploadDelay, headers ) ) { + success = false; + } + } while ( !( activeUploads < ( ignoreMinUploadDelay ? COW_MAX_PARALLEL_UPLOADS + : COW_MAX_PARALLEL_BACKGROUND_UPLOADS ) ) + && activeUploads ); + cow_curl_read_upload_t *b = malloc( sizeof( cow_curl_read_upload_t ) ); + b->block = block; + b->blocknumber = ( l1Offset * COW_L2_SIZE + l2Offset ); + b->fails = 0; + b->position = 0; + b->time = block->timeChanged; + addUpload( cm, b, headers ); + if ( !ignoreMinUploadDelay && !uploadLoop ) { + goto DONE; + } + } + } + } + } +DONE: + while ( activeUploads > 0 ) { + MessageHandler( cm, &activeUploads, false, ignoreMinUploadDelay, headers ); + } + curl_slist_free_all( headers ); + return success; +} + + +/** + * @brief Computes the data for the status to the stdout/statfile every COW_STATS_UPDATE_TIME seconds. + * + */ + +void *cowfile_statUpdater( __attribute__( ( unused ) ) void *something ) +{ + uint64_t lastUpdateTime = time( NULL ); + + while ( !uploadLoopDone ) { + sleep( COW_STATS_UPDATE_TIME ); + int modified = 0; + int inQueue = 0; + int idle = 0; + long unsigned int l1MaxOffset = 1 + ( ( metadata->imageSize - 1 ) / COW_L2_STORAGE_CAPACITY ); + uint64_t now = time( NULL ); + for ( long unsigned int l1Offset = 0; l1Offset < l1MaxOffset; l1Offset++ ) { + if ( cow.l1[l1Offset] == -1 ) { + continue; + } + for ( int l2Offset = 0; l2Offset < COW_L2_SIZE; l2Offset++ ) { + cow_block_metadata_t *block = ( cow.firstL2[cow.l1[l1Offset]] + l2Offset ); + if ( block->offset == -1 ) { + continue; + } + if ( block->timeChanged != 0 ) { + if ( !uploadLoop || now > block->timeChanged + COW_MIN_UPLOAD_DELAY ) { + inQueue++; + } else { + modified++; + } + } else { + idle++; + } + } + } + char speedBuffer[20]; + + if ( COW_SHOW_UL_SPEED ) { + now = time( NULL ); + uint64_t bytes = atomic_exchange( &bytesUploaded, 0 ); + snprintf( speedBuffer, 20, "%.2f", (double)( ( bytes ) / ( 1 + now - lastUpdateTime ) / 1000 ) ); + + lastUpdateTime = now; + } + + + updateCowStatsFile( inQueue, modified, idle, speedBuffer ); + } +} + +/** + * @brief main loop for blockupload in the background + */ +void *cowfile_uploader( __attribute__( ( unused ) ) void *something ) +{ + CURLM *cm; + + cm = curl_multi_init(); + curl_multi_setopt( + cm, CURLMOPT_MAXCONNECTS, (long)MAX( COW_MAX_PARALLEL_UPLOADS, COW_MAX_PARALLEL_BACKGROUND_UPLOADS ) ); + + + while ( uploadLoop ) { + uploaderLoop( false, cm ); + sleep( 2 ); + } + logadd( LOG_DEBUG1, "start uploading the remaining blocks." ); + + // force the upload of all remaining blocks because the user dismounted the image + if ( !uploaderLoop( true, cm ) ) { + logadd( LOG_ERROR, "one or more blocks failed to upload" ); + curl_multi_cleanup( cm ); + uploadLoopDone = true; + return NULL; + } + uploadLoopDone = true; + curl_multi_cleanup( cm ); + logadd( LOG_DEBUG1, "all blocks uploaded" ); + if ( cow_merge_after_upload ) { + startMerge(); + logadd( LOG_DEBUG1, "Requesting merge." ); + } + return NULL; +} + +/** + * @brief Create a Cow Stats File an inserts the session guid + * + * @param path where the file is created + * @return true + * @return false if failed to create or to write into the file + */ +bool createCowStatsFile( char *path ) +{ + char pathStatus[strlen( path ) + 12]; + + snprintf( pathStatus, strlen( path ) + 12, "%s%s", path, "/status.txt" ); + + char buffer[100]; + int len = snprintf( buffer, 100, "uuid=%s\nstate: active\n", metadata->uuid ); + if ( statStdout ) { + logadd( LOG_INFO, "%s", buffer ); + } + if ( statFile ) { + if ( ( cow.fhs = open( pathStatus, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR ) ) == -1 ) { + logadd( LOG_ERROR, "Could not create cow status file. Bye.\n" ); + return false; + } + + if ( pwrite( cow.fhs, buffer, len, 0 ) != len ) { + logadd( LOG_ERROR, "Could not write to cow status file. Bye.\n" ); + return false; + } + } + return true; +} + +/** + * @brief initializes the cow functionality, creates the data & meta file. + * + * @param path where the files should be stored + * @param image_Name name of the original file/image + * @param imageSizePtr + */ +bool cowfile_init( char *path, const char *image_Name, uint16_t imageVersion, atomic_uint_fast64_t **imageSizePtr, + char *serverAddress, bool sStdout, bool sfile ) +{ + statStdout = sStdout; + statFile = sfile; + char pathMeta[strlen( path ) + 6]; + char pathData[strlen( path ) + 6]; + + snprintf( pathMeta, strlen( path ) + 6, "%s%s", path, "/meta" ); + snprintf( pathData, strlen( path ) + 6, "%s%s", path, "/data" ); + + if ( ( cow.fhm = open( pathMeta, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR ) ) == -1 ) { + logadd( LOG_ERROR, "Could not create cow meta file. Bye.\n %s \n", pathMeta ); + return false; + } + + if ( ( cow.fhd = open( pathData, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR ) ) == -1 ) { + logadd( LOG_ERROR, "Could not create cow data file. Bye.\n" ); + return false; + } + + int maxPageSize = 8192; + + + size_t metaDataSizeHeader = sizeof( cowfile_metadata_header_t ) + strlen( image_Name ); + + + cow.maxImageSize = COW_MAX_IMAGE_SIZE; + cow.l1Size = ( ( cow.maxImageSize + COW_L2_STORAGE_CAPACITY - 1LL ) / COW_L2_STORAGE_CAPACITY ); + + // size of l1 array + number of l2's * size of l2 + size_t metadata_size = cow.l1Size * sizeof( l1 ) + cow.l1Size * sizeof( l2 ); + + // compute next fitting multiple of getpagesize() + size_t meta_data_start = ( ( metaDataSizeHeader + maxPageSize - 1 ) / maxPageSize ) * maxPageSize; + + size_t metadataFileSize = meta_data_start + metadata_size; + if ( pwrite( cow.fhm, "", 1, metadataFileSize ) != 1 ) { + logadd( LOG_ERROR, "Could not write cow meta_data_table to file. Bye.\n" ); + return false; + } + + cow.metadata_mmap = mmap( NULL, metadataFileSize, PROT_READ | PROT_WRITE, MAP_SHARED, cow.fhm, 0 ); + + + if ( cow.metadata_mmap == MAP_FAILED ) { + logadd( LOG_ERROR, "Error while mapping mmap:\n%s \n Bye.\n", strerror( errno ) ); + return false; + } + + metadata = (cowfile_metadata_header_t *)( cow.metadata_mmap ); + metadata->magicValue = COW_FILE_META_MAGIC_VALUE; + metadata->version = cowFileVersion; + metadata->dataFileSize = ATOMIC_VAR_INIT( 0 ); + metadata->metadataFileSize = ATOMIC_VAR_INIT( 0 ); + metadata->metadataFileSize = metadataFileSize; + metadata->blocksize = DNBD3_BLOCK_SIZE; + metadata->originalImageSize = **imageSizePtr; + metadata->imageSize = metadata->originalImageSize; + metadata->creationTime = time( NULL ); + *imageSizePtr = &metadata->imageSize; + metadata->metaDataStart = meta_data_start; + metadata->bitfieldSize = COW_BITFIELD_SIZE; + metadata->maxImageSize = cow.maxImageSize; + snprintf( metadata->imageName, 200, "%s", image_Name ); + cow.l1 = (l1 *)( cow.metadata_mmap + meta_data_start ); + metadata->nextL2 = 0; + + for ( size_t i = 0; i < cow.l1Size; i++ ) { + cow.l1[i] = -1; + } + cow.firstL2 = (l2 *)( ( (char *)cow.l1 ) + cow.l1Size ); + + // write header to data file + uint64_t header = COW_FILE_DATA_MAGIC_VALUE; + if ( pwrite( cow.fhd, &header, sizeof( uint64_t ), 0 ) != sizeof( uint64_t ) ) { + logadd( LOG_ERROR, "Could not write header to cow data file. Bye.\n" ); + return false; + } + // move the dataFileSize to make room for the header + atomic_store( &metadata->dataFileSize, COW_METADATA_STORAGE_CAPACITY ); + + pthread_mutex_init( &cow.l2CreateLock, NULL ); + + + cowServerAddress = serverAddress; + curl_global_init( CURL_GLOBAL_ALL ); + curl = curl_easy_init(); + if ( !curl ) { + logadd( LOG_ERROR, "Error on curl init. Bye.\n" ); + return false; + } + if ( !createSession( image_Name, imageVersion ) ) { + return false; + } + + createCowStatsFile( path ); + pthread_create( &tidCowUploader, NULL, &cowfile_uploader, NULL ); + if ( statFile || statStdout ) { + pthread_create( &tidStatUpdater, NULL, &cowfile_statUpdater, NULL ); + } + return true; +} + +/** + * @brief loads an existing cow state from the meta & data files + * + * @param path where the meta & data file is located + * @param imageSizePtr + */ +bool cowfile_load( char *path, atomic_uint_fast64_t **imageSizePtr, char *serverAddress, bool sStdout, bool sFile ) +{ + statStdout = sStdout; + statFile = sFile; + cowServerAddress = serverAddress; + curl_global_init( CURL_GLOBAL_ALL ); + curl = curl_easy_init(); + char pathMeta[strlen( path ) + 6]; + char pathData[strlen( path ) + 6]; + + snprintf( pathMeta, strlen( path ) + 6, "%s%s", path, "/meta" ); + snprintf( pathData, strlen( path ) + 6, "%s%s", path, "/data" ); + + + if ( ( cow.fhm = open( pathMeta, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR ) ) == -1 ) { + logadd( LOG_ERROR, "Could not open cow meta file. Bye.\n" ); + return false; + } + if ( ( cow.fhd = open( pathData, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR ) ) == -1 ) { + logadd( LOG_ERROR, "Could not open cow data file. Bye.\n" ); + return false; + } + + cowfile_metadata_header_t header; + { + size_t sizeToRead = sizeof( cowfile_metadata_header_t ); + size_t readBytes = 0; + while ( readBytes < sizeToRead ) { + ssize_t bytes = pread( cow.fhm, ( ( &header ) + readBytes ), sizeToRead, 0 ); + if ( bytes <= 0 ) { + logadd( LOG_ERROR, "Error while reading meta file header. Bye.\n" ); + return false; + } + readBytes += bytes; + } + + + if ( header.magicValue != COW_FILE_META_MAGIC_VALUE ) { + if ( __builtin_bswap64( header.magicValue ) == COW_FILE_META_MAGIC_VALUE ) { + logadd( LOG_ERROR, "cow meta file of wrong endianess. Bye.\n" ); + return false; + } + logadd( LOG_ERROR, "cow meta file of unkown format. Bye.\n" ); + return false; + } + struct stat st; + stat( pathMeta, &st ); + if ( (long)st.st_size < (long)header.metaDataStart + (long)header.nextL2 * (long)sizeof( l2 ) ) { + logadd( LOG_ERROR, "cow meta file to small. Bye.\n" ); + return false; + } + } + { + uint64_t magicValueDataFile; + if ( pread( cow.fhd, &magicValueDataFile, sizeof( uint64_t ), 0 ) != sizeof( uint64_t ) ) { + logadd( LOG_ERROR, "Error while reading cow data file, wrong file?. Bye.\n" ); + return false; + } + + if ( magicValueDataFile != COW_FILE_DATA_MAGIC_VALUE ) { + if ( __builtin_bswap64( magicValueDataFile ) == COW_FILE_DATA_MAGIC_VALUE ) { + logadd( LOG_ERROR, "cow data file of wrong endianess. Bye.\n" ); + return false; + } + logadd( LOG_ERROR, "cow data file of unkown format. Bye.\n" ); + return false; + } + struct stat st; + stat( pathData, &st ); + if ( (long)header.dataFileSize < st.st_size ) { + logadd( LOG_ERROR, "cow data file to small. Bye.\n" ); + return false; + } + } + + cow.metadata_mmap = mmap( NULL, header.metadataFileSize, PROT_READ | PROT_WRITE, MAP_SHARED, cow.fhm, 0 ); + + if ( cow.metadata_mmap == MAP_FAILED ) { + logadd( LOG_ERROR, "Error while mapping mmap:\n%s \n Bye.\n", strerror( errno ) ); + return false; + } + if ( header.version != cowFileVersion ) { + logadd( LOG_ERROR, "Error wrong file version got: %i expected: 1. Bye.\n", metadata->version ); + return false; + } + + + metadata = (cowfile_metadata_header_t *)( cow.metadata_mmap ); + + *imageSizePtr = &metadata->imageSize; + cow.l1 = (l1 *)( cow.metadata_mmap + metadata->metaDataStart ); + cow.maxImageSize = metadata->maxImageSize; + cow.l1Size = ( ( cow.maxImageSize + COW_L2_STORAGE_CAPACITY - 1LL ) / COW_L2_STORAGE_CAPACITY ); + + cow.firstL2 = (l2 *)( ( (char *)cow.l1 ) + cow.l1Size ); + pthread_mutex_init( &cow.l2CreateLock, NULL ); + createCowStatsFile( path ); + pthread_create( &tidCowUploader, NULL, &cowfile_uploader, NULL ); + + if ( statFile || statStdout ) { + pthread_create( &tidStatUpdater, NULL, &cowfile_statUpdater, NULL ); + } + + return true; +} + +/** + * @brief writes the given data in the data file + * + * @param buffer containing the data + * @param size of the buffer + * @param netSize which actually contributes to the fuse write request (can be different from size if partial full blocks are written) + * @param cowRequest + * @param block + * @param inBlockOffset + */ +static void writeData( const char *buffer, ssize_t size, size_t netSize, atomic_int *errorCode, + atomic_size_t *bytesWorkedOn, cow_block_metadata_t *block, off_t inBlockOffset ) +{ + ssize_t totalBytesWritten = 0; + while ( totalBytesWritten < size ) { + ssize_t bytesWritten = pwrite( cow.fhd, ( buffer + totalBytesWritten ), size - totalBytesWritten, + block->offset + inBlockOffset + totalBytesWritten ); + if ( bytesWritten == -1 ) { + logadd( LOG_ERROR, + "size:%zu netSize:%zu errorCode:%i bytesWorkedOn:%zu inBlockOffset:%lld block->offset:%lld \n", size, + netSize, *errorCode, *bytesWorkedOn, inBlockOffset, block->offset ); + *errorCode = errno; + break; + } else if ( bytesWritten == 0 ) { + logadd( LOG_ERROR, + "size:%zu netSize:%zu errorCode:%i bytesWorkedOn:%zu inBlockOffset:%lld block->offset:%lld \n", size, + netSize, *errorCode, *bytesWorkedOn, inBlockOffset, block->offset ); + *errorCode = EIO; + break; + } + totalBytesWritten += bytesWritten; + } + atomic_fetch_add( bytesWorkedOn, netSize ); + setBitsInBitfield( block->bitfield, (int)( inBlockOffset / DNBD3_BLOCK_SIZE ), + (int)( ( inBlockOffset + totalBytesWritten - 1 ) / DNBD3_BLOCK_SIZE ), 1 ); + + block->timeChanged = time( NULL ); +} + +/** + * @brief Increases the metadata->dataFileSize by COW_METADATA_STORAGE_CAPACITY. + * The space is not reserved on disk. + * + * @param block for which the space should be reserved. + */ +static bool allocateMetaBlockData( cow_block_metadata_t *block ) +{ + block->offset = (atomic_long)atomic_fetch_add( &metadata->dataFileSize, COW_METADATA_STORAGE_CAPACITY ); + return true; +} + +/** + * @brief Get the cow_block_metadata_t from l1Offset and l2Offset + * + * @param l1Offset + * @param l2Offset + * @return cow_block_metadata_t* + */ +static cow_block_metadata_t *getBlock( int l1Offset, int l2Offset ) +{ + cow_block_metadata_t *block = ( cow.firstL2[cow.l1[l1Offset]] + l2Offset ); + if ( block->offset == -1 ) { + allocateMetaBlockData( block ); + } + return block; +} + +/** + * @brief creates an new L2 Block and initializes the containing cow_block_metadata_t blocks + * + * @param l1Offset + */ +static bool createL2Block( int l1Offset ) +{ + pthread_mutex_lock( &cow.l2CreateLock ); + if ( cow.l1[l1Offset] == -1 ) { + for ( int i = 0; i < COW_L2_SIZE; i++ ) { + cow.firstL2[metadata->nextL2][i].offset = -1; + cow.firstL2[metadata->nextL2][i].timeChanged = ATOMIC_VAR_INIT( 0 ); + cow.firstL2[metadata->nextL2][i].uploads = ATOMIC_VAR_INIT( 0 ); + for ( int j = 0; j < COW_BITFIELD_SIZE; j++ ) { + cow.firstL2[metadata->nextL2][i].bitfield[j] = ATOMIC_VAR_INIT( 0 ); + } + } + cow.l1[l1Offset] = metadata->nextL2; + metadata->nextL2 += 1; + } + pthread_mutex_unlock( &cow.l2CreateLock ); + return true; +} + +/** + * @brief Is called once an fuse write request ist finished. + * Calls the corrsponding fuse reply depending on the type and + * success of the request. + * + * @param req fuse_req_t + * @param cowRequest + */ + +static void finishWriteRequest( fuse_req_t req, cow_request_t *cowRequest ) +{ + if ( cowRequest->errorCode != 0 ) { + fuse_reply_err( req, cowRequest->errorCode ); + + } else { + metadata->imageSize = MAX( metadata->imageSize, cowRequest->bytesWorkedOn + cowRequest->fuseRequestOffset ); + fuse_reply_write( req, cowRequest->bytesWorkedOn ); + } + free( cowRequest ); +} + +/** + * @brief Called after the padding data was received from the dnbd3 server. + * The data from the write request will be combined witch the data from the server + * so that we get a full DNBD3_BLOCK and is then written on the disk. + * @param sRequest + */ +static void writePaddedBlock( cow_sub_request_t *sRequest ) +{ + //copy write Data + memcpy( ( sRequest->writeBuffer + ( sRequest->inBlockOffset % DNBD3_BLOCK_SIZE ) ), sRequest->writeSrc, + sRequest->size ); + writeData( sRequest->writeBuffer, DNBD3_BLOCK_SIZE, (ssize_t)sRequest->size, &sRequest->cowRequest->errorCode, + &sRequest->cowRequest->bytesWorkedOn, sRequest->block, + ( sRequest->inBlockOffset - ( sRequest->inBlockOffset % DNBD3_BLOCK_SIZE ) ) ); + + + if ( atomic_fetch_sub( &sRequest->cowRequest->workCounter, 1 ) == 1 ) { + finishWriteRequest( sRequest->dRequest.fuse_req, sRequest->cowRequest ); + } + free( sRequest ); +} + +/** + * @brief If an block does not start or finishes on an multiple of DNBD3_BLOCK_SIZE, the blocks needs to be + * padded. If this block is inside the original image size, the padding data will be read fro the server + * otherwise it will be padded with 0 since the it must be the block at the end of the image. + * + */ +static void padBlockFromRemote( fuse_req_t req, off_t offset, cow_request_t *cowRequest, const char *buffer, + size_t size, cow_block_metadata_t *block, off_t inBlockOffset ) +{ + if ( offset > (off_t)metadata->originalImageSize ) { + //pad 0 and done + inBlockOffset -= inBlockOffset % DNBD3_BLOCK_SIZE; + char buf[DNBD3_BLOCK_SIZE] = { 0 }; + memcpy( buf + ( offset % 4096 ), buffer, size ); + + writeData( buf, DNBD3_BLOCK_SIZE, (ssize_t)size, &cowRequest->errorCode, &cowRequest->bytesWorkedOn, block, + inBlockOffset ); + return; + } + cow_sub_request_t *sRequest = calloc( sizeof( cow_sub_request_t ) + DNBD3_BLOCK_SIZE, sizeof( char ) ); + sRequest->callback = writePaddedBlock; + sRequest->inBlockOffset = inBlockOffset; + sRequest->block = block; + sRequest->size = size; + sRequest->writeSrc = buffer; + sRequest->cowRequest = cowRequest; + off_t start = offset - ( offset % DNBD3_BLOCK_SIZE ); + + sRequest->dRequest.length = DNBD3_BLOCK_SIZE; + sRequest->dRequest.offset = start; + sRequest->dRequest.fuse_req = req; + + if ( ( (size_t)( offset + DNBD3_BLOCK_SIZE ) ) > metadata->originalImageSize ) { + sRequest->dRequest.length = + (uint32_t)MIN( DNBD3_BLOCK_SIZE, offset + DNBD3_BLOCK_SIZE - metadata->originalImageSize ); + } + + atomic_fetch_add( &cowRequest->workCounter, 1 ); + if ( !connection_read( &sRequest->dRequest ) ) { + cowRequest->errorCode = EIO; + free( sRequest ); + if ( atomic_fetch_sub( &sRequest->cowRequest->workCounter, 1 ) == 1 ) { + finishWriteRequest( sRequest->dRequest.fuse_req, sRequest->cowRequest ); + } + return; + } +} + +/** + * @brief Will be called after a dnbd3_async_t is finished. + * Calls the corrsponding callback function, either writePaddedBlock or readRemoteData + * depending if the original fuse request was a write or read. + * + */ +void cowfile_handleCallback( dnbd3_async_t *request ) +{ + cow_sub_request_t *sRequest = container_of( request, cow_sub_request_t, dRequest ); + sRequest->callback( sRequest ); +} + + +/** + * @brief called once dnbd3_async_t is finished. Increases bytesWorkedOn by the number of bytes + * this request had. Also checks if it was the last dnbd3_async_t to finish the fuse request, if + * so replys to fuse and cleans up the request. + * + */ +void readRemoteData( cow_sub_request_t *sRequest ) +{ + atomic_fetch_add( &sRequest->cowRequest->bytesWorkedOn, sRequest->dRequest.length ); + + if ( atomic_fetch_sub( &sRequest->cowRequest->workCounter, 1 ) == 1 ) { + if ( sRequest->cowRequest->bytesWorkedOn < sRequest->cowRequest->fuseRequestSize ) { + logadd( LOG_ERROR, "pad read to small\n" ); + } + fuse_reply_buf( + sRequest->dRequest.fuse_req, sRequest->cowRequest->readBuffer, sRequest->cowRequest->bytesWorkedOn ); + free( sRequest->cowRequest->readBuffer ); + free( sRequest->cowRequest ); + } + free( sRequest ); +} + +/** + * @brief changes the imageSize + * + * @param req fuse request + * @param size new size the image should have + * @param ino fuse_ino_t + * @param fi fuse_file_info + */ + +void cowfile_setSize( fuse_req_t req, size_t size, fuse_ino_t ino, struct fuse_file_info *fi ) +{ + // decrease + if ( metadata->imageSize > size ) { + if ( size < metadata->originalImageSize ) { + metadata->originalImageSize = size; + } + + // increase + } else if ( metadata->imageSize < size ) { + off_t offset = metadata->imageSize; + int l1Offset = getL1Offset( offset ); + int l2Offset = getL2Offset( offset ); + int l1EndOffset = getL1Offset( size ); + int l2EndOffset = getL2Offset( size ); + // special case first block + if ( cow.l1[l1Offset] != -1 ) { + cow_block_metadata_t *block = getBlock( l1Offset, l2Offset ); + if ( metadata->imageSize % DNBD3_BLOCK_SIZE != 0 ) { + off_t inBlockOffset = metadata->imageSize % COW_METADATA_STORAGE_CAPACITY; + size_t sizeToWrite = DNBD3_BLOCK_SIZE - ( metadata->imageSize % DNBD3_BLOCK_SIZE ); + + if ( checkBit( block->bitfield, (int)( inBlockOffset / DNBD3_BLOCK_SIZE ) ) ) { + char buf[sizeToWrite]; + memset( buf, 0, sizeToWrite ); + + ssize_t bytesWritten = pwrite( cow.fhd, buf, sizeToWrite, block->offset + inBlockOffset ); + + if ( bytesWritten < (ssize_t)sizeToWrite ) { + fuse_reply_err( req, bytesWritten == -1 ? errno : EIO ); + return; + } + block->timeChanged = time( NULL ); + offset += sizeToWrite; + } + } + // rest of block set bits 0 + l1Offset = getL1Offset( offset ); + l2Offset = getL2Offset( offset ); + block = getBlock( l1Offset, l2Offset ); + off_t inBlockOffset = offset % COW_METADATA_STORAGE_CAPACITY; + setBitsInBitfield( + block->bitfield, (int)( inBlockOffset / DNBD3_BLOCK_SIZE ), ( COW_BITFIELD_SIZE * 8 ) - 1, 0 ); + block->timeChanged = time( NULL ); + l2Offset++; + if ( l2Offset >= COW_L2_SIZE ) { + l2Offset = 0; + l1Offset++; + } + } + // null all bitfields + while ( !( l1Offset > l1EndOffset || ( l1Offset == l1EndOffset && l2EndOffset < l2Offset ) ) ) { + if ( cow.l1[l1Offset] == -1 ) { + l1Offset++; + l2Offset = 0; + continue; + } + + cow_block_metadata_t *block = getBlock( l1Offset, l2Offset ); + setBitsInBitfield( block->bitfield, 0, ( COW_BITFIELD_SIZE * 8 ) - 1, 0 ); + block->timeChanged = time( NULL ); + l2Offset++; + if ( l2Offset >= COW_L2_SIZE ) { + l2Offset = 0; + l1Offset++; + } + } + } + metadata->imageSize = size; + if ( req != NULL ) { + image_ll_getattr( req, ino, fi ); + } +} + +/** + * @brief Implementation of a write request or an truncate. + * + * @param req fuse_req_t + * @param cowRequest + * @param offset Offset where the write starts, + * @param size Size of the write. + */ +void cowfile_write( fuse_req_t req, cow_request_t *cowRequest, off_t offset, size_t size ) +{ + // if beyond end of file, pad with 0 + if ( offset > (off_t)metadata->imageSize ) { + cowfile_setSize( NULL, offset, NULL, NULL ); + } + + + off_t currentOffset = offset; + off_t endOffset = offset + size; + + // write data + + int l1Offset = getL1Offset( currentOffset ); + int l2Offset = getL2Offset( currentOffset ); + while ( currentOffset < endOffset ) { + if ( cow.l1[l1Offset] == -1 ) { + createL2Block( l1Offset ); + } + //loop over L2 array (metadata) + while ( currentOffset < (off_t)endOffset && l2Offset < COW_L2_SIZE ) { + cow_block_metadata_t *metaBlock = getBlock( l1Offset, l2Offset ); + + + size_t metaBlockStartOffset = l1Offset * COW_L2_STORAGE_CAPACITY + l2Offset * COW_METADATA_STORAGE_CAPACITY; + + size_t inBlockOffset = currentOffset - metaBlockStartOffset; + size_t sizeToWriteToBlock = + MIN( (size_t)( endOffset - currentOffset ), COW_METADATA_STORAGE_CAPACITY - inBlockOffset ); + + + ///////////////////////// + // lock for the half block probably needed + if ( currentOffset % DNBD3_BLOCK_SIZE != 0 + && !checkBit( metaBlock->bitfield, (int)( inBlockOffset / DNBD3_BLOCK_SIZE ) ) ) { + // write remote + size_t padSize = MIN( sizeToWriteToBlock, DNBD3_BLOCK_SIZE - ( (size_t)currentOffset % DNBD3_BLOCK_SIZE ) ); + const char *sbuf = cowRequest->writeBuffer + ( ( currentOffset - offset ) ); + padBlockFromRemote( req, currentOffset, cowRequest, sbuf, padSize, metaBlock, (off_t)inBlockOffset ); + currentOffset += padSize; + continue; + } + + size_t endPaddedSize = 0; + if ( ( currentOffset + sizeToWriteToBlock ) % DNBD3_BLOCK_SIZE != 0 ) { + off_t currentEndOffset = currentOffset + sizeToWriteToBlock; + off_t padStartOffset = currentEndOffset - ( currentEndOffset % 4096 ); + off_t inBlockPadStartOffset = padStartOffset - metaBlockStartOffset; + if ( !checkBit( metaBlock->bitfield, (int)( inBlockPadStartOffset / DNBD3_BLOCK_SIZE ) ) ) { + const char *sbuf = cowRequest->writeBuffer + ( ( padStartOffset - offset ) ); + padBlockFromRemote( req, padStartOffset, cowRequest, sbuf, (currentEndOffset)-padStartOffset, metaBlock, + inBlockPadStartOffset ); + + + sizeToWriteToBlock -= (currentEndOffset)-padStartOffset; + endPaddedSize = (currentEndOffset)-padStartOffset; + } + } + writeData( cowRequest->writeBuffer + ( ( currentOffset - offset ) ), (ssize_t)sizeToWriteToBlock, + sizeToWriteToBlock, &cowRequest->errorCode, &cowRequest->bytesWorkedOn, metaBlock, inBlockOffset ); + + currentOffset += sizeToWriteToBlock; + currentOffset += endPaddedSize; + + + l2Offset++; + } + l1Offset++; + l2Offset = 0; + } + if ( atomic_fetch_sub( &cowRequest->workCounter, 1 ) == 1 ) { + finishWriteRequest( req, cowRequest ); + } +} + + +/** + * @brief Request data, that is not available locally, via the network. + * + * @param req fuse_req_t + * @param offset from the start of the file + * @param size of data to request + * @param buffer into which the data is to be written + * @param workCounter workCounter is increased by one and later reduced by one again when the request is completed. + */ +static void readRemote( fuse_req_t req, off_t offset, ssize_t size, char *buffer, cow_request_t *cowRequest ) +{ + // edgecase: Image size got reduced before on a non block border + if ( offset + size > metadata->originalImageSize ) { + size_t padZeroSize = ( offset + size ) - metadata->originalImageSize; + off_t padZeroOffset = metadata->originalImageSize - offset; + assert( offset > 0 ); + memset( ( buffer + padZeroOffset ), 0, padZeroOffset ); + + atomic_fetch_add( &cowRequest->bytesWorkedOn, padZeroSize ); + } + cow_sub_request_t *sRequest = malloc( sizeof( cow_sub_request_t ) ); + sRequest->callback = readRemoteData; + sRequest->dRequest.length = (uint32_t)size; + sRequest->dRequest.offset = offset; + sRequest->dRequest.fuse_req = req; + sRequest->cowRequest = cowRequest; + sRequest->buffer = buffer; + + atomic_fetch_add( &cowRequest->workCounter, 1 ); + if ( !connection_read( &sRequest->dRequest ) ) { + cowRequest->errorCode = EIO; + free( sRequest ); + if ( atomic_fetch_sub( &cowRequest->workCounter, 1 ) == 1 ) { + fuse_reply_buf( req, cowRequest->readBuffer, cowRequest->bytesWorkedOn ); + } + free( cowRequest->readBuffer ); + free( cowRequest ); + return; + } +} + +/** + * @brief Get the Block Data Source object + * + * @param block + * @param bitfieldOffset + * @param offset + * @return enum dataSource + */ +enum dataSource getBlockDataSource( cow_block_metadata_t *block, off_t bitfieldOffset, off_t offset ) +{ + if ( block != NULL && checkBit( block->bitfield, (int)bitfieldOffset ) ) { + return local; + } + if ( offset >= (off_t)metadata->originalImageSize ) { + return zero; + } + return remote; +} + +/** + * @brief Reads data at given offset. If the data are available locally, + * they are read locally, otherwise they are requested remotely. + * + * @param req fuse_req_t + * @param size of date to read + * @param offset offset where the read starts. + * @return uint64_t Number of bytes read. + */ +void cowfile_read( fuse_req_t req, size_t size, off_t offset ) +{ + cow_request_t *cowRequest = malloc( sizeof( cow_request_t ) ); + cowRequest->fuseRequestSize = size; + cowRequest->bytesWorkedOn = ATOMIC_VAR_INIT( 0 ); + cowRequest->workCounter = ATOMIC_VAR_INIT( 1 ); + cowRequest->errorCode = ATOMIC_VAR_INIT( 0 ); + cowRequest->readBuffer = malloc( size ); + cowRequest->fuseRequestOffset = offset; + off_t lastReadOffset = offset; + off_t endOffset = offset + size; + off_t searchOffset = offset; + int l1Offset = getL1Offset( offset ); + int l2Offset = getL2Offset( offset ); + int bitfieldOffset = getBitfieldOffset( offset ); + enum dataSource dataState; + cow_block_metadata_t *block = NULL; + + if ( cow.l1[l1Offset] != -1 ) { + block = getBlock( l1Offset, l2Offset ); + } + + bool doRead = false; + bool firstLoop = true; + bool updateBlock = false; + while ( searchOffset < endOffset ) { + if ( firstLoop ) { + firstLoop = false; + lastReadOffset = searchOffset; + dataState = getBlockDataSource( block, bitfieldOffset, searchOffset ); + } else if ( getBlockDataSource( block, bitfieldOffset, searchOffset ) != dataState ) { + doRead = true; + } else { + bitfieldOffset++; + } + + if ( bitfieldOffset >= COW_BITFIELD_SIZE * 8 ) { + bitfieldOffset = 0; + l2Offset++; + if ( l2Offset >= COW_L2_SIZE ) { + l2Offset = 0; + l1Offset++; + } + updateBlock = true; + if ( dataState == local ) { + doRead = true; + } + } + // compute the original file offset from bitfieldOffset, l2Offset and l1Offset + searchOffset = DNBD3_BLOCK_SIZE * ( bitfieldOffset ) + l2Offset * COW_METADATA_STORAGE_CAPACITY + + l1Offset * COW_L2_STORAGE_CAPACITY; + if ( doRead || searchOffset >= endOffset ) { + ssize_t sizeToRead = MIN( searchOffset, endOffset ); + if ( dataState == remote ) { + if ( sizeToRead > metadata->originalImageSize ) { + //pad rest with 0 + memset( cowRequest->readBuffer + + ( ( lastReadOffset - offset ) + ( metadata->originalImageSize - offset ) ), + 0, sizeToRead - metadata->originalImageSize ); + atomic_fetch_add( &cowRequest->bytesWorkedOn, sizeToRead - metadata->originalImageSize ); + sizeToRead = metadata->originalImageSize; + } + sizeToRead -= lastReadOffset; + readRemote( + req, lastReadOffset, sizeToRead, cowRequest->readBuffer + ( lastReadOffset - offset ), cowRequest ); + } else if ( dataState == zero ) { + sizeToRead -= lastReadOffset; + memset( cowRequest->readBuffer + ( lastReadOffset - offset ), 0, sizeToRead ); + atomic_fetch_add( &cowRequest->bytesWorkedOn, sizeToRead ); + } else { + sizeToRead -= lastReadOffset; + // Compute the offset in the data file where the read starts + off_t localRead = + block->offset + ( ( lastReadOffset % COW_L2_STORAGE_CAPACITY ) % COW_METADATA_STORAGE_CAPACITY ); + ssize_t totalBytesRead = 0; + while ( totalBytesRead < sizeToRead ) { + ssize_t bytesRead = + pread( cow.fhd, cowRequest->readBuffer + ( lastReadOffset - offset ), sizeToRead, localRead ); + if ( bytesRead == -1 ) { + cowRequest->errorCode = errno; + goto fail; + } else if ( bytesRead <= 0 ) { + cowRequest->errorCode = EIO; + goto fail; + } + totalBytesRead += bytesRead; + } + + atomic_fetch_add( &cowRequest->bytesWorkedOn, totalBytesRead ); + } + lastReadOffset = searchOffset; + doRead = false; + firstLoop = true; + } + + if ( updateBlock ) { + if ( cow.l1[l1Offset] != -1 ) { + block = getBlock( l1Offset, l2Offset ); + } else { + block = NULL; + } + updateBlock = false; + } + } +fail:; + if ( atomic_fetch_sub( &cowRequest->workCounter, 1 ) == 1 ) { + if ( cowRequest->errorCode != 0 ) { + if ( cowRequest->bytesWorkedOn < size ) { + logadd( LOG_ERROR, " read to small" ); + } + fuse_reply_err( req, cowRequest->errorCode ); + + } else { + if ( cowRequest->bytesWorkedOn < size ) { + logadd( LOG_ERROR, " read to small" ); + } + fuse_reply_buf( req, cowRequest->readBuffer, cowRequest->bytesWorkedOn ); + } + free( cowRequest->readBuffer ); + free( cowRequest ); + } +} + + +/** + * @brief stops the StatUpdater and CowUploader threads + * and waits for them to finish, then cleans up curl. + * + */ +void cowfile_close() +{ + uploadLoop = false; + if ( statFile || statStdout ) { + pthread_join( tidStatUpdater, NULL ); + } + pthread_join( tidCowUploader, NULL ); + + if ( curl ) { + curl_global_cleanup(); + curl_easy_cleanup( curl ); + } +} diff --git a/src/fuse/cowfile.h b/src/fuse/cowfile.h new file mode 100644 index 0000000..c74ab99 --- /dev/null +++ b/src/fuse/cowfile.h @@ -0,0 +1,136 @@ +#ifndef _COWFILE_H_ +#define _COWFILE_H_ + +#include <stdint.h> +#include <stdlib.h> +#include <stdatomic.h> +#include <dnbd3/shared/log.h> +#include <sys/mman.h> +#include <string.h> +#include <pthread.h> +#include <errno.h> +#include <curl/curl.h> +#include "main.h" +#include "connection.h" + + +#define COW_METADATA_STORAGE_CAPACITY ( COW_BITFIELD_SIZE * 8 * DNBD3_BLOCK_SIZE ) +#define COW_L2_SIZE 1024 +#define COW_L2_STORAGE_CAPACITY ( COW_L2_SIZE * COW_METADATA_STORAGE_CAPACITY ) +#define container_of( ptr, type, member ) ( (type *)( (char *)( ptr ) - (char *)&( ( (type *)NULL )->member ) ) ) + +_Static_assert( ATOMIC_INT_LOCK_FREE == 2, "ATOMIC INT not lock free" ); +_Static_assert( ATOMIC_LONG_LOCK_FREE == 2, "ATOMIC LONG not lock free" ); +_Static_assert( ATOMIC_LLONG_LOCK_FREE == 2, "ATOMIC LLONG not lock free" ); +_Static_assert( sizeof( atomic_uint_least64_t ) == 8, "atomic_uint_least64_t not 8 byte" ); +_Static_assert( sizeof( atomic_int_least64_t ) == 8, "atomic_int_least64_t not 8 byte" ); + +enum dataSource +{ + local, + remote, + zero +}; + +#define COW_METADATA_HEADER_SIZE 320 +typedef struct cowfile_metadata_header +{ + uint64_t magicValue; // 8byte + atomic_uint_least64_t imageSize; // 8byte + int32_t version; // 4byte + int32_t blocksize; // 4byte + uint64_t originalImageSize; // 8byte + uint64_t metaDataStart; // 8byte + int32_t bitfieldSize; // 4byte + int32_t nextL2; // 4byte + atomic_uint_least64_t metadataFileSize; // 8byte + atomic_uint_least64_t dataFileSize; // 8byte + uint64_t maxImageSize; // 8byte + uint64_t creationTime; // 8byte + char uuid[40]; // 40byte + char imageName[200]; // 200byte +} cowfile_metadata_header_t; +_Static_assert( + sizeof( cowfile_metadata_header_t ) == COW_METADATA_HEADER_SIZE, "cowfile_metadata_header is messed up" ); + +#define COW_METADATA_METADATA_SIZE 64 +typedef struct cow_block_metadata +{ + atomic_int_least64_t offset; + atomic_uint_least64_t timeChanged; + atomic_uint_least64_t uploads; + atomic_char bitfield[40]; +} cow_block_metadata_t; +_Static_assert( sizeof( cow_block_metadata_t ) == COW_METADATA_METADATA_SIZE, "cow_block_metadata_t is messed up" ); + + +typedef struct cow_request +{ + size_t fuseRequestSize; + off_t fuseRequestOffset; + char *readBuffer; + const char *writeBuffer; + atomic_size_t bytesWorkedOn; + atomic_int workCounter; + atomic_int errorCode; + fuse_ino_t ino; + struct fuse_file_info *fi; +} cow_request_t; + +typedef struct cow_sub_request cow_sub_request_t; +typedef void ( *cow_callback )( cow_sub_request_t *sRequest ); + +typedef struct cow_sub_request +{ + size_t size; + off_t inBlockOffset; + const char *writeSrc; + char *buffer; + cow_block_metadata_t *block; + cow_callback callback; + cow_request_t *cowRequest; + dnbd3_async_t dRequest; + char writeBuffer[]; +} cow_sub_request_t; + +typedef struct cow_curl_read_upload +{ + atomic_uint_least64_t time; + cow_block_metadata_t *block; + size_t position; + long unsigned int blocknumber; + int fails; + curl_off_t ulLast; +} cow_curl_read_upload_t; + + +typedef struct cow_block_upload_statistics +{ + uint64_t blocknumber; + uint64_t uploads; +} cow_block_upload_statistics_t; + + +typedef int32_t l1; +typedef cow_block_metadata_t l2[COW_L2_SIZE]; + +bool cowfile_init( char *path, const char *image_Name, uint16_t imageVersion, atomic_uint_fast64_t **imageSizePtr, + char *serverAddress, bool sStdout, bool sFile ); + +bool cowfile_load( char *path, atomic_uint_fast64_t **imageSizePtr, char *serverAddress, bool sStdout, bool sFile ); + +void cowfile_read( fuse_req_t req, size_t size, off_t offset ); + +void cowfile_write( fuse_req_t req, cow_request_t *cowRequest, off_t offset, size_t size ); + +void cowfile_handleCallback( dnbd3_async_t *request ); + +void cowfile_setSize( fuse_req_t req, size_t size, fuse_ino_t ino, struct fuse_file_info *fi ); + +void readRemoteData( cow_sub_request_t *sRequest ); + +int cow_printStats( char *buffer, const size_t len ); + +void cowfile_close(); + +#endif /* COWFILE_H_ */
\ No newline at end of file diff --git a/src/fuse/main.c b/src/fuse/main.c index e06f6e8..1ae6d33 100644 --- a/src/fuse/main.c +++ b/src/fuse/main.c @@ -8,58 +8,34 @@ * FUSE lowlevel by Alan Reichert * */ -#include "connection.h" -#include "helper.h" -#include <dnbd3/version.h> -#include <dnbd3/build.h> -#include <dnbd3/shared/protocol.h> -#include <dnbd3/shared/log.h> - -#define FUSE_USE_VERSION 30 -#include <dnbd3/config.h> -#include <fuse_lowlevel.h> -#include <stdio.h> -#include <stdlib.h> -#include <unistd.h> -#include <errno.h> -#include <fcntl.h> -#include <unistd.h> -#include <assert.h> -/* for printing uint */ -#define __STDC_FORMAT_MACROS -#include <inttypes.h> -#include <getopt.h> -#include <time.h> -#include <signal.h> -#include <pthread.h> - -#define debugf(...) do { logadd( LOG_DEBUG1, __VA_ARGS__ ); } while (0) - -#define INO_ROOT (1) -#define INO_STATS (2) -#define INO_IMAGE (3) +#include "main.h" + + static const char *IMAGE_NAME = "img"; static const char *STATS_NAME = "status"; static struct fuse_session *_fuseSession = NULL; +bool useCow = false; +bool cow_merge_after_upload = false; +static atomic_uint_fast64_t imageSize; +static atomic_uint_fast64_t *imageSizePtr =&imageSize; -static uint64_t imageSize; /* Debug/Benchmark variables */ static bool useDebug = false; static log_info logInfo; static struct timespec startupTime; static uid_t owner; - static int reply_buf_limited( fuse_req_t req, const char *buf, size_t bufsize, off_t off, size_t maxsize ); static void fillStatsFile( fuse_req_t req, size_t size, off_t offset ); static void image_destroy( void *private_data ); -static void image_ll_getattr( fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi ); static void image_ll_init( void *userdata, struct fuse_conn_info *conn ); static void image_ll_lookup( fuse_req_t req, fuse_ino_t parent, const char *name ); static void image_ll_open( fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi ); static void image_ll_readdir( fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi ); static void image_ll_read( fuse_req_t req, fuse_ino_t ino, size_t size, off_t offset, struct fuse_file_info *fi ); +static void image_ll_write( fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi ); +static void image_ll_setattr( fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi ); static int image_stat( fuse_ino_t ino, struct stat *stbuf ); static void printUsage( char *argv0, int exitCode ); static void printVersion(); @@ -69,13 +45,20 @@ static int image_stat( fuse_ino_t ino, struct stat *stbuf ) switch ( ino ) { case INO_ROOT: stbuf->st_mode = S_IFDIR | 0550; + if( useCow ) { + stbuf->st_mode = S_IFDIR | 0777; + } stbuf->st_nlink = 2; stbuf->st_mtim = startupTime; break; case INO_IMAGE: - stbuf->st_mode = S_IFREG | 0440; + if ( useCow ) { + stbuf->st_mode = S_IFREG | 0777; + } else { + stbuf->st_mode = S_IFREG | 0440; + } stbuf->st_nlink = 1; - stbuf->st_size = imageSize; + stbuf->st_size = *imageSizePtr; stbuf->st_mtim = startupTime; break; case INO_STATS: @@ -93,7 +76,7 @@ static int image_stat( fuse_ino_t ino, struct stat *stbuf ) return 0; } -static void image_ll_getattr( fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi ) +void image_ll_getattr( fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi ) { struct stat stbuf = { 0 }; ( void ) fi; @@ -170,8 +153,8 @@ static void image_ll_readdir( fuse_req_t req, fuse_ino_t ino, size_t size, off_t static void image_ll_open( fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi ) { if ( ino != INO_IMAGE && ino != INO_STATS ) { - fuse_reply_err( req, EISDIR ); - } else if ( ( fi->flags & 3 ) != O_RDONLY ) { + fuse_reply_err( req, EISDIR ); + } else if ( ( fi->flags & 3 ) != O_RDONLY && !useCow ) { fuse_reply_err( req, EACCES ); } else { // auto caching @@ -202,17 +185,23 @@ static void image_ll_read( fuse_req_t req, fuse_ino_t ino, size_t size, off_t of return; } - if ( (uint64_t)offset >= imageSize ) { + if ( size == 0 || size > UINT32_MAX ) { fuse_reply_err( req, 0 ); return; } - if ( offset + size > imageSize ) { - size = imageSize - offset; - } - if ( size == 0 || size > UINT32_MAX ) { + + if ( (uint64_t)offset >= *imageSizePtr ) { fuse_reply_err( req, 0 ); return; } + if ( offset + size > *imageSizePtr ) { + size = *imageSizePtr - offset; + } + + if ( useCow ) { + cowfile_read(req, size, offset); + return; + } if ( useDebug ) { uint64_t startBlock = offset / ( 4096 ); @@ -223,14 +212,16 @@ static void image_ll_read( fuse_req_t req, fuse_ino_t ino, size_t size, off_t of ++logInfo.blockRequestCount[startBlock]; } } - dnbd3_async_t *request = malloc( sizeof(dnbd3_async_t) + size ); - request->length = (uint32_t)size; - request->offset = offset; - request->fuse_req = req; + - if ( !connection_read( request ) ) { + dnbd3_async_parent_t *parent = malloc( sizeof(dnbd3_async_parent_t) + size ); + parent->request.length = (uint32_t)size; + parent->request.offset = offset; + parent->request.fuse_req = req; + + if ( !connection_read( &parent->request ) ) { fuse_reply_err( req, EIO ); - free( request ); + free( parent ); } } @@ -260,6 +251,40 @@ static void image_destroy( void *private_data UNUSED ) connection_close(); } + +static void image_ll_write( fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi ) +{ + assert( ino == INO_STATS || ino == INO_IMAGE ); + + ( void )fi; + + if ( ino == INO_STATS ) { + fuse_reply_err( req, EACCES ); + return; + } + + cow_request_t* cowRequest = malloc(sizeof(cow_request_t)); + cowRequest->fuseRequestSize = size; + cowRequest->workCounter = ATOMIC_VAR_INIT( 1 ); + cowRequest->writeBuffer = buf; + cowRequest->readBuffer = NULL; + cowRequest->errorCode = ATOMIC_VAR_INIT( 0 ); + cowRequest->fuseRequestOffset = off; + cowRequest->bytesWorkedOn = ATOMIC_VAR_INIT( 0 ); + cowfile_write(req, cowRequest, off, size); +} + +static void image_ll_setattr( fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi ) +{ + if ( ino != INO_IMAGE ) { + fuse_reply_err( req, EACCES ); + return; + } + if (to_set & FUSE_SET_ATTR_SIZE) { + cowfile_setSize( req, attr->st_size, ino, fi); + } +} + /* map the implemented fuse operations */ static struct fuse_lowlevel_ops image_oper = { .lookup = image_ll_lookup, @@ -271,6 +296,20 @@ static struct fuse_lowlevel_ops image_oper = { .destroy = image_destroy, }; +/* map the implemented fuse operations with copy on write */ +static struct fuse_lowlevel_ops image_oper_cow = { + .lookup = image_ll_lookup, + .getattr = image_ll_getattr, + .readdir = image_ll_readdir, + .open = image_ll_open, + .read = image_ll_read, + .init = image_ll_init, + .destroy = image_destroy, + .write = image_ll_write, + .setattr = image_ll_setattr, +}; + + static void printVersion() { char *arg[] = { "foo", "-V" }; @@ -288,8 +327,9 @@ static void printUsage( char *argv0, int exitCode ) struct fuse_args args = FUSE_ARGS_INIT( 2, arg ); fuse_parse_cmdline( &args, NULL, NULL, NULL ); printf( "\n" ); - printf( "Usage: %s [--debug] [--option mountOpts] --host <serverAddress(es)> --image <imageName> [--rid revision] <mountPoint>\n", argv0 ); - printf( "Or: %s [-d] [-o mountOpts] -h <serverAddress(es)> -i <imageName> [-r revision] <mountPoint>\n", argv0 ); + printf( "Usage: %s [--debug] [--option mountOpts] --host <serverAddress(es)> --image <imageName> [--rid revision] <mountPoint>\n", argv0 ); + printf( "Or: %s [-d] [-o mountOpts] -h <serverAddress(es)> -i <imageName> [-r revision] <mountPoint>\n", argv0 ); + printf( "For cow: %s [-d] [-o mountOpts] -h <serverAddress(es)> -i <imageName> [-r revision] -c <path> -C <cowServerAddress> -m [--cowStatStdout] [--cowStatFile] <mountPoint>\n", argv0 ); printf( " -d --debug Don't fork, write stats file, and print debug output (fuse -> stderr, dnbd3 -> stdout)\n" ); printf( " -f Don't fork (dnbd3 -> stdout)\n" ); printf( " -h --host List of space separated hosts to use\n" ); @@ -299,10 +339,15 @@ static void printUsage( char *argv0, int exitCode ) printf( " -r --rid Revision to use (omit or pass 0 for latest)\n" ); printf( " -S --sticky Use only servers from command line (no learning from servers)\n" ); printf( " -s Single threaded mode\n" ); + printf( " -c Enables cow, creates the cow files at given location\n" ); + printf( " -L Loads the cow files from the given location\n" ); + printf( " -C Host address of the cow server\n" ); + printf( " --cowStatStdout prints the cow status in stdout\n" ); + printf( " --cowStatFile creates and updates the cow status file\n" ); exit( exitCode ); } -static const char *optString = "dfHh:i:l:o:r:SsVv"; +static const char *optString = "dfHh:i:l:o:r:SsVvc:L:C:mxy"; static const struct option longOpts[] = { { "debug", no_argument, NULL, 'd' }, { "help", no_argument, NULL, 'H' }, @@ -313,14 +358,22 @@ static const struct option longOpts[] = { { "rid", required_argument, NULL, 'r' }, { "sticky", no_argument, NULL, 'S' }, { "version", no_argument, NULL, 'v' }, + { "cow", required_argument, NULL, 'c' }, + { "loadcow", required_argument, NULL, 'L' }, + { "cowServer", required_argument, NULL, 'C' }, + { "merge", no_argument, NULL, 'm' }, + { "cowStatStdout", no_argument, NULL, 'x' }, + { "cowStatFile", no_argument, NULL, 'y' }, { 0, 0, 0, 0 } }; int main( int argc, char *argv[] ) { char *server_address = NULL; + char *cow_server_address = NULL; char *image_Name = NULL; char *log_file = NULL; + cow_merge_after_upload = false; uint16_t rid = 0; char **newArgv; int newArgc; @@ -330,6 +383,10 @@ int main( int argc, char *argv[] ) struct fuse_chan *ch; char *mountpoint; int foreground = 0; + char *cow_file_path = NULL; + bool loadCow = false; + bool sStdout = false; + bool sFile = false; log_init(); @@ -395,6 +452,27 @@ int main( int argc, char *argv[] ) case 'f': foreground = 1; break; + case 'c': + cow_file_path = optarg; + useCow = true; + break; + case 'C': + cow_server_address = optarg; + break; + case 'm': + cow_merge_after_upload = true; + break; + case 'L': + cow_file_path = optarg; + useCow = true; + loadCow = true; + break; + case 'x': + sStdout = true; + break; + case 'y': + sFile = true; + break; default: printUsage( argv[0], EXIT_FAILURE ); } @@ -413,7 +491,24 @@ int main( int argc, char *argv[] ) logadd( LOG_WARNING, "Could not open log file at '%s'", log_file ); } } + if( useCow && cow_server_address == NULL ) { + printf( "for -c you also need a cow server address. Please also use -C --host \n" ); + printUsage( argv[0], EXIT_FAILURE ); + } + if( cow_merge_after_upload && !useCow ) { + printf( "-m only works if cow is enabled. \n" ); + printUsage( argv[0], EXIT_FAILURE ); + } + if ( loadCow ) { + if( cow_server_address == NULL ) { + printf( "for -L you also need a cow server address. Please also use -C --host \n" ); + printUsage( argv[0], EXIT_FAILURE ); + } + if ( !cowfile_load( cow_file_path, &imageSizePtr, cow_server_address, sStdout, sFile ) ) { + return EXIT_FAILURE; + } + } // Prepare our handler struct sigaction newHandler; memset( &newHandler, 0, sizeof( newHandler ) ); @@ -433,17 +528,20 @@ int main( int argc, char *argv[] ) /* initialize benchmark variables */ logInfo.receivedBytes = 0; - logInfo.imageSize = imageSize; - logInfo.imageBlockCount = ( imageSize + 4095 ) / 4096; + logInfo.imageSize = *imageSizePtr; + logInfo.imageBlockCount = ( *imageSizePtr + 4095 ) / 4096; if ( useDebug ) { logInfo.blockRequestCount = calloc( logInfo.imageBlockCount, sizeof(uint8_t) ); } else { logInfo.blockRequestCount = NULL; } - - // Since dnbd3 is always read only and the remote image will not change + newArgv[newArgc++] = "-o"; - newArgv[newArgc++] = "ro,default_permissions"; + if(useCow){ + newArgv[newArgc++] = "default_permissions"; + }else{ + newArgv[newArgc++] = "ro,default_permissions"; + } // Mount point goes last newArgv[newArgc++] = argv[optind]; @@ -455,6 +553,12 @@ int main( int argc, char *argv[] ) clock_gettime( CLOCK_REALTIME, &startupTime ); owner = getuid(); + if ( useCow & !loadCow) { + if( !cowfile_init( cow_file_path, connection_getImageName(), connection_getImageRID(), &imageSizePtr, cow_server_address, sStdout, sFile ) ) { + return EXIT_FAILURE; + } + } + // Fuse lowlevel loop struct fuse_args args = FUSE_ARGS_INIT( newArgc, newArgv ); int fuse_err = 1; @@ -463,7 +567,11 @@ int main( int argc, char *argv[] ) } else if ( ( ch = fuse_mount( mountpoint, &args ) ) == NULL ) { logadd( LOG_ERROR, "Mounting file system failed" ); } else { - _fuseSession = fuse_lowlevel_new( &args, &image_oper, sizeof( image_oper ), NULL ); + if(useCow){ + _fuseSession = fuse_lowlevel_new( &args, &image_oper_cow, sizeof( image_oper_cow ), NULL ); + } else{ + _fuseSession = fuse_lowlevel_new( &args, &image_oper, sizeof( image_oper ), NULL ); + } if ( _fuseSession == NULL ) { logadd( LOG_ERROR, "Could not initialize fuse session" ); } else { @@ -484,6 +592,9 @@ int main( int argc, char *argv[] ) _fuseSession = NULL; } fuse_unmount( mountpoint, ch ); + if( useCow ) { + cowfile_close(); + } } fuse_opt_free_args( &args ); free( newArgv ); diff --git a/src/fuse/main.h b/src/fuse/main.h new file mode 100644 index 0000000..721f251 --- /dev/null +++ b/src/fuse/main.h @@ -0,0 +1,39 @@ +#ifndef _MAIN_H_ +#define _MAIN_H_ +#include "cowfile.h" +#include "connection.h" +#include "helper.h" +#include <dnbd3/version.h> +#include <dnbd3/build.h> +#include <dnbd3/shared/protocol.h> +#include <dnbd3/shared/log.h> + + +#define FUSE_USE_VERSION 30 +#include <dnbd3/config.h> +#include <fuse_lowlevel.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <assert.h> +/* for printing uint */ +#define __STDC_FORMAT_MACROS +#include <inttypes.h> +#include <getopt.h> +#include <time.h> +#include <signal.h> +#include <pthread.h> +#define debugf(...) do { logadd( LOG_DEBUG1, __VA_ARGS__ ); } while (0) + +#define INO_ROOT (1) +#define INO_STATS (2) +#define INO_IMAGE (3) + +extern bool useCow; +extern bool cow_merge_after_upload; +void image_ll_getattr( fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi ); + +#endif /* main_H_ */
\ No newline at end of file |