diff options
author | Michael Scherle | 2022-02-17 18:58:25 +0100 |
---|---|---|
committer | Michael Scherle | 2022-02-17 18:58:25 +0100 |
commit | f2c5bb8ba9176cab845935f5ba6c325df3a2def7 (patch) | |
tree | 552221d44d246b94ad50321ea09b26c21cd05e76 | |
parent | [KERNEL] Add missing include to fix compile on 4.14.x (diff) | |
download | dnbd3-f2c5bb8ba9176cab845935f5ba6c325df3a2def7.tar.gz dnbd3-f2c5bb8ba9176cab845935f5ba6c325df3a2def7.tar.xz dnbd3-f2c5bb8ba9176cab845935f5ba6c325df3a2def7.zip |
basic cow implementation & rudimentary tests
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/CMakeLists.txt | 4 | ||||
-rw-r--r-- | src/cowtest/CMakeLists.txt | 27 | ||||
-rw-r--r-- | src/cowtest/main.c | 358 | ||||
-rw-r--r-- | src/fuse/CMakeLists.txt | 9 | ||||
-rw-r--r-- | src/fuse/connection.c | 24 | ||||
-rw-r--r-- | src/fuse/connection.h | 7 | ||||
-rw-r--r-- | src/fuse/cowfile.c | 577 | ||||
-rw-r--r-- | src/fuse/cowfile.h | 72 | ||||
-rw-r--r-- | src/fuse/main.c | 191 | ||||
-rw-r--r-- | src/fuse/main.h | 36 |
11 files changed, 1248 insertions, 58 deletions
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/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..559d332 --- /dev/null +++ b/src/cowtest/main.c @@ -0,0 +1,358 @@ +#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> + + +static bool run = true; + +const size_t l2Size = 1024; +const size_t bitfieldByteSize = 40; +const size_t blocksize = 4096; +const size_t l2Capacity = l2Size * blocksize * bitfieldByteSize; + +const size_t testFileSize = l2Size * bitfieldByteSize * blocksize * 5; + +const char standartValue = 'a'; +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 + */ + +void generateTestFile( char *path, size_t size ) +{ + int fh; + strcpy( filePath, path ); + if ( ( fh = open( path, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR ) ) == -1 ) { + perror( "Could not create test file: " ); + return; + } + if ( ftruncate( fh, size ) == -1 ) { + perror( "Error while expanding test file: " ); + return; + } + + + close( fh ); + printf( "Generated Test File of size: %zu bytes. \n", size ); + //todo write markers: +} + + +void printUsage() +{ + printf( "Press the follwing for: \n" ); + printf( " c <path> Creates test file at the path. \n" ); + printf( " t <path> Runs the standart test procedure. \n" ); +} + +void printCharInHexadecimal( const char *str, int len ) +{ + for ( int i = 0; i < len; ++i ) { + uint8_t val = str[i]; + char tbl[] = "0123456789ABCDEF"; + printf( "0x" ); + printf( "%c", tbl[val / 16] ); + printf( "%c", tbl[val % 16] ); + printf( " " ); + } + printf( "\n" ); +} + +bool compare( char buff[], char expected[], size_t size, char errorMessage[] ) +{ + if ( memcmp( buff, expected, size ) != 0 ) { + perror( errorMessage ); + if ( printOnError ) { + printf( "Expected: \n" ); + printCharInHexadecimal( expected, size ); + printf( "Got: \n " ); + printCharInHexadecimal( buff, 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 < 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 ) +{ + if ( pwrite( fh, buf, size, off ) < size ) { + perror( error ); + return false; + } + return true; +} + +bool testFirstBit() +{ + char buff[blocksize]; + char expected[blocksize]; + memset( expected, 0, blocksize ); + if ( !readSizeTested( fh, buff, 4096, 0, "FirstBit test Failed: read to small" ) ) + return false; + + if ( !compare( buff, expected, 4096, "FirstBit test Failed: initial read" ) ) + return false; + expected[0] = 1; + if ( !writeSizeTested( fh, expected, 4096, 0, "FirstBit test Failed: write failed" ) ) + return false; + if ( !readSizeTested( fh, buff, 4096, 0, "FirstBit test Failed: read to small" ) ) + return false; + if ( !compare( buff, expected, 4096, "FirstBit test Failed: write not as expected" ) ) + return false; + printf( "testFirstBit successful!\n" ); + return true; +} + +bool writeOverTwoBlocks() +{ + char buff[blocksize * 2]; + char expected[blocksize * 2]; + memset( expected, 0, blocksize * 2 ); + if ( !readSizeTested( fh, buff, blocksize * 2, blocksize * 3, "writeOverTwoBlocks test Failed: read to small" ) ) + return false; + if ( !compare( buff, expected, blocksize * 2, "OverTwoBlocks test Failed: initial read" ) ) + return false; + memset( expected, 1, blocksize * 2 ); + if ( !writeSizeTested( fh, expected, blocksize * 2, blocksize * 3, "writeOverTwoBlocks test Failed: write failed" ) ) + return false; + if ( !readSizeTested( fh, buff, blocksize * 2, blocksize * 3, "writeOverTwoBlocks test Failed: read to small" ) ) + return false; + if ( !compare( buff, expected, blocksize * 2, "OverTwoBlocks test Failed: write not as expected" ) ) + return false; + printf( "writeOverTwoBlocks successful!\n" ); + return true; +} + +bool writeOverL2() +{ + char buff[blocksize * 2]; + char expected[blocksize * 2]; + memset( expected, 0, blocksize * 2 ); + size_t offset = l2Capacity * 2 - blocksize; + if ( !readSizeTested( fh, buff, blocksize * 2, offset, "writeOverL2 test Failed: read to small" ) ) + return false; + if ( !compare( buff, expected, blocksize * 2, "writeOverL2 test Failed: initial read" ) ) + return false; + memset( expected, 1, blocksize * 2 ); + if ( !writeSizeTested( fh, expected, blocksize * 2, offset, "writeOverL2 test Failed: write failed" ) ) + return false; + if ( !readSizeTested( fh, buff, blocksize * 2, offset, "writeOverL2 test Failed: read to small" ) ) + return false; + if ( !compare( buff, expected, blocksize * 2, "writeOverL2 test Failed: write not as expected" ) ) + return false; + printf( "writeOverL2 successful!\n" ); + return true; +} + + +// perhaps do some initial markers on the file +bool writeNotOnBlockBorder() +{ + char buff[blocksize * 2]; + char expected[blocksize * 2]; + memset( expected, 0, blocksize * 2 ); + size_t offset = blocksize * 11 - blocksize / 2; + if ( !readSizeTested( fh, buff, blocksize * 2, offset, "writeNotOnBlockBorder test Failed: read to small" ) ) + return false; + if ( !compare( buff, expected, blocksize * 2, "writeNotOnBlockBorder test Failed: initial read" ) ) + return false; + memset( expected, 1, blocksize * 2 ); + if ( !writeSizeTested( fh, expected, blocksize * 2, offset, "writeNotOnBlockBorder test Failed: write failed" ) ) + return false; + if ( !readSizeTested( fh, buff, blocksize * 2, offset, "writeNotOnBlockBorder test Failed: read to small" ) ) + return false; + if ( !compare( buff, expected, blocksize * 2, "writeNotOnBlockBorder test Failed: write not as expected" ) ) + return false; + printf( "writeNotOnBlockBorder successful!\n" ); + return true; +} + +bool fileSizeChanges() +{ + // increase filesize + + printf( "Truncate file to: %zu\n", testFileSize + 2 * l2Capacity ); + if ( truncate( filePath, testFileSize + 2 * l2Capacity ) != 0 ) { + perror( "fileSizeChanges test Failed: first truncate failed." ); + return false; + } + // verify + struct stat st; + stat( filePath, &st ); + size_t size = st.st_size; + + if ( size != testFileSize + 2 * l2Capacity ) { + printf( "fileSizeChanges test Failed\n expectedSize: %zu\n got: %zu\n", testFileSize + 2 * l2Capacity, size ); + return false; + } + // check if increased is 0 + char buff[blocksize * 10]; + char expected[blocksize * 10]; + memset( expected, 0, blocksize * 10 ); + if ( !readSizeTested( + fh, buff, blocksize * 10, testFileSize + l2Capacity, "fileSizeChanges test Failed: read to small" ) ) + return false; + if ( !compare( buff, expected, blocksize * 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, blocksize * 10 ); + if ( !writeSizeTested( fh, expected, blocksize * 10, testFileSize, "fileSizeChanges test Failed: write failed" ) ) + return false; + if ( !readSizeTested( fh, buff, blocksize * 10, testFileSize, "fileSizeChanges test Failed: read to small" ) ) + return false; + if ( !compare( buff, expected, blocksize * 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 ( truncate( filePath, testFileSize ) != 0 ) { + perror( "fileSizeChanges test Failed: second truncate failed." ); + return false; + } + // verify + printf( "truncate done, verifing...\n" ); + stat( filePath, &st ); + size = st.st_size; + if ( size != testFileSize ) { + printf( + "fileSizeChanges test Failed, decrease not worked.\n expectedSize: %zu\n got: %zu\n", testFileSize, size ); + return false; + } + printf( "size verified\n" ); + // increase again, check its 0 again + printf( "Truncate file to: %zu\n", testFileSize + 2 * l2Capacity ); + if ( truncate( filePath, testFileSize + 2 * l2Capacity ) != 0 ) { + perror( "fileSizeChanges test Failed: second increase failed." ); + return false; + } + printf( "truncate done, verifing...\n" ); + stat( filePath, &st ); + size = st.st_size; + if ( size != ( testFileSize + 2 * l2Capacity ) ) { + printf( "fileSizeChanges test Failed, increse not worked.\n expectedSize: %zu\n got: %zu\n", testFileSize, size ); + return false; + } + printf( "size verified\n" ); + memset( expected, 0, blocksize * 10 ); + + + if ( !readSizeTested( fh, buff, blocksize * 10, testFileSize, "fileSizeChanges test Failed: read to small" ) ) + return false; + if ( !compare( buff, expected, blocksize * 2, "fileSizeChanges test Failed: increased data (second time) not 0" ) ) + return false; + printf( "fileSizeChanges successful!\n" ); + return true; +} + +void 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 ( !testFirstBit() ) + return; + if ( !writeOverTwoBlocks() ) + return; + if ( !writeOverL2() ) + return; + if ( !fileSizeChanges() ) + return; + printf( "All test's successful.\n" ); +} + + +void 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; + } +} + +void execCommand( char command, char *parameters ) +{ + switch ( command ) { + case 'c': + if ( parameters[0] == '\0' ) { + printUsage(); + break; + } + generateTestFile( parameters, 3 * l2Capacity ); + break; + case 't': + if ( parameters[0] == '\0' ) { + printUsage(); + break; + } + printf( "starting standart test\n" ); + runTest( parameters ); + break; + case 'v': + if ( parameters[0] == '\0' ) { + printUsage(); + break; + } + printf( "verifing file \n" ); + runTest( parameters ); + default: + printf( "Command not Found \n" ); + printUsage(); + break; + } +} + + +int main( int argc, char *argv[] ) +{ + if ( argc == 3 ) { + execCommand( argv[1][0], argv[2] ); + } else { + printUsage(); + } + return 0; +} + + +/* + methode to generate test file. +*/ +/* Tests to impelment: + +1. Read & Writes over block borders (l1, l2, metadata). +2. Parallel writes on different unchanged blocks.(test for race condition on cow file increse). +3. Test truncate file (smaller and lager). +4. Random read writes. +5. Read & Writes over data which is partially in cow file +6. Read & Write single byte +*/
\ No newline at end of file diff --git a/src/fuse/CMakeLists.txt b/src/fuse/CMakeLists.txt index be062f0..605ef87 100644 --- a/src/fuse/CMakeLists.txt +++ b/src/fuse/CMakeLists.txt @@ -13,11 +13,14 @@ find_package(Libatomic 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}) diff --git a/src/fuse/connection.c b/src/fuse/connection.c index 25ea219..ff77794 100644 --- a/src/fuse/connection.c +++ b/src/fuse/connection.c @@ -406,8 +406,16 @@ static void* connection_receiveThreadMain( void *sockPtr ) } unlock_rw( &altLock ); } - fuse_reply_buf( request->fuse_req, request->buffer, request->length ); - free( request ); + if( request->cow_write != NULL ) { + cowfile_writePaddedBlock(request ); + } else if( request->cow != NULL ) { + cowFile_readRemoteData( request ); + } + else { + fuse_reply_buf( request->fuse_req, request->buffer, request->length ); + free( request->buffer ); + free( request ); + } } } else if ( reply.cmd == CMD_GET_SERVERS ) { // List of known alt servers @@ -706,8 +714,16 @@ static void probeAltServers() goto fail; } // Success, reply to fuse - fuse_reply_buf( request->fuse_req, request->buffer, request->length ); - free( request ); + if( request->cow_write != NULL ) { + cowfile_writePaddedBlock(request ); + } else if( request->cow != NULL ) { + cowFile_readRemoteData( request ); + } + else { + fuse_reply_buf( request->fuse_req, request->buffer, request->length ); + free( request->buffer ); + free( request ); + } logadd( LOG_DEBUG1, "%s probe: Successful direct probe", hstr ); } else { // Wasn't a request that's in our request queue diff --git a/src/fuse/connection.h b/src/fuse/connection.h index b869ac6..ee605bb 100644 --- a/src/fuse/connection.h +++ b/src/fuse/connection.h @@ -15,13 +15,18 @@ extern atomic_bool keepRunning; struct _dnbd3_async; +typedef struct cow_request cow_request; +typedef struct cow_write_request cow_write_request; + typedef struct _dnbd3_async { struct _dnbd3_async *next; // Next in this linked list (provate field, not set by caller) ticks time; // When request was put on wire, 0 if not measuring uint64_t offset; uint32_t length; fuse_req_t fuse_req; - char buffer[]; // Must be last member! + cow_request *cow; + cow_write_request *cow_write; + char* buffer; } dnbd3_async_t; bool connection_init( const char *hosts, const char *image, const uint16_t rid, const bool learnNewServers ); diff --git a/src/fuse/cowfile.c b/src/fuse/cowfile.c new file mode 100644 index 0000000..fbf6c15 --- /dev/null +++ b/src/fuse/cowfile.c @@ -0,0 +1,577 @@ +#include "cowfile.h" + +extern void image_ll_getattr( fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi ); + +int cowFileVersion = 1; +size_t blockSize = 4096; +cowfile_metadata_Header *metadata = NULL; + +static struct cow +{ + pthread_mutex_t l2CreateLock; + int fhm; + int fhd; + char *metadata_mmap; + cow_block_metadata **l1; + l2 nextL2; + atomic_size_t metadataFileSize; + atomic_size_t dataFileSize; + size_t maxImageSize; + + int bitfieldSize; + size_t l1Size; //size of l1 array + int l2Size; //size of an l2 array + + size_t metadataStorageCapacity; + size_t l2StorageCapacity; // memory a l2 array can address +} cow; + +int getL1Offset( size_t offset ) +{ + return (int)( offset / cow.l2StorageCapacity ); +} + +int getL2Offset( size_t offset ) +{ + return (int)( ( offset % cow.l2StorageCapacity ) / cow.metadataStorageCapacity ); +} +int getBitfieldOffset( size_t offset ) +{ + return (int)( offset % cow.metadataStorageCapacity ) / 4096; +} + +/** + * @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 + */ +void setBits( atomic_char *byte, int from, int to ) +{ + char mask = (char)( 255 >> ( 8 - ( to - from + 1 ) ) ); + + atomic_char val = atomic_load( byte ); + while ( !atomic_compare_exchange_weak( byte, &val, ( val | ( char ) ( mask << from ) ) ) ); +} + +/** + * @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 + */ +void setBitsInBitfield( atomic_char *bitfield, int from, int to ) +{ + 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 ) ); + from = ( i + 1 ) * 8; + } +} + +/** + * @brief Checks if the n bit of an bitfield is 0 or 1. + * + * @param bitfield of a cow_block_metadata + * @param n the bit which should be checked + */ +bool checkBit( atomic_char *bitfield, int n ) +{ + return ( atomic_load( ( bitfield + ( n / 8 ) ) ) >> ( n % 8 ) ) & 1; +} + +bool cowfile_init( char *path, const char *image_Name, size_t **imageSizePtr ) +{ + char pathMeta[strlen( path ) + 6]; + char pathData[strlen( path ) + 6]; + strcpy( pathMeta, path ); + strcpy( pathData, path ); + strcat( pathMeta, ".meta" ); + 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" ); + return false; + } + + strcat( pathData, ".data" ); + 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; + } + cow.dataFileSize = 0; + // create Meta Data Mapping + int pageSize = getpagesize(); + int maxPageSize = 8192; + + // TODO IMAGE NAME IS FIXED + size_t metaDataSizeHeader = sizeof( cowfile_metadata_Header ) + strlen( image_Name ); + + cow.bitfieldSize = 40; + cow.maxImageSize = 1000L * 1000L * 1000L * 1000L; // tb*gb*mb*kb + cow.l2Size = 1024; + + cow.metadataStorageCapacity = cow.bitfieldSize * 8L * 4096L; + cow.l2StorageCapacity = ( cow.l2Size * cow.metadataStorageCapacity ); + + cow.l1Size = ( ( cow.maxImageSize + cow.l2StorageCapacity - 1L ) / cow.l2StorageCapacity ); + + size_t metadata_size = cow.l1Size * sizeof( l1 ) + cow.l1Size * cow.l2Size * sizeof( l2 ) + + cow.l1Size * cow.l2Size * ( sizeof( cow_block_metadata ) ); + + + //compute next fitting multiple of getpagesize() + size_t meta_data_start = ( ( metaDataSizeHeader + maxPageSize - 1 ) / maxPageSize ) * maxPageSize; + + cow.metadataFileSize = meta_data_start + metadata_size; + if ( pwrite( cow.fhm, "", 1, cow.metadataFileSize ) != 1 ) { + logadd( LOG_ERROR, "Could not write cow meta_data_table to file. Bye.\n" ); + return false; + } + + cow.metadata_mmap = mmap( NULL, cow.metadataFileSize, PROT_READ | PROT_WRITE, MAP_SHARED, cow.fhm, 0 ); + + + if ( cow.metadata_mmap == MAP_FAILED ) { + logadd( LOG_ERROR, "Error while mapping mmap. Bye.\n" ); + return false; + } + + + size_t *metaDataHeaderSizePtr = (size_t *)cow.metadata_mmap; + *metaDataHeaderSizePtr = metaDataSizeHeader; + metadata = (cowfile_metadata_Header *)( cow.metadata_mmap + sizeof( size_t ) ); + metadata->version = cowFileVersion; + metadata->blocksize = pageSize; + metadata->originalImageSize = **imageSizePtr; + metadata->ImageSize = metadata->originalImageSize; + *imageSizePtr = &metadata->ImageSize; + + metadata->meta_data_start = meta_data_start; + + + metadata->bitfieldSize = cow.bitfieldSize; + metadata->maxImageSize = cow.maxImageSize; + strcpy( metadata->imageName, image_Name ); + cow.l1 = (cow_block_metadata **)( cow.metadata_mmap + meta_data_start ); + + + for ( size_t i = 0; i < cow.l1Size; i++ ) { + cow.l1[i] = NULL; + } + cow.nextL2 = (l2)( cow.l1 + cow.l1Size ); + pthread_mutex_init( &cow.l2CreateLock, NULL ); + return 1; +} + +bool cowfile_load( char *path ) +{ + if ( ( cow.fhm = open( strcat( path, ".meta" ), 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( strcat( path, ".data" ), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR ) ) == -1 ) { + logadd( LOG_ERROR, "Could not open cow data file. Bye.\n" ); + return false; + } + size_t metaDataSizeHeader; + ssize_t bytesRead = read( cow.fhm, &metaDataSizeHeader, sizeof( size_t ) ); + if ( bytesRead < (ssize_t)sizeof( size_t ) ) { + if ( bytesRead < 0 ) { + logadd( LOG_ERROR, "Error while reading metaDataSizeHeader: " ); + } else { + logadd( LOG_ERROR, "metaDataSizeHeader smaller than expected. Bye.\n" ); + } + return false; + } + cowfile_metadata_Header *metadata = malloc( metaDataSizeHeader ); + bytesRead = read( cow.fhm, metadata, metaDataSizeHeader ); + if ( bytesRead < (ssize_t)sizeof( size_t ) ) { + if ( bytesRead < 0 ) { + logadd( LOG_ERROR, "Error while reading metadata. Bye.\n" ); + } else { + logadd( LOG_ERROR, "metadata smaller than expected. Bye.\n" ); + } + return false; + } + logadd( LOG_DEBUG1, "===Image Name===: %s\n", metadata->imageName ); + + return true; +} + +void writeData(const char* buffer, size_t size, size_t netSize, cow_request* cowRequest, cow_block_metadata * block, off_t inBlockOffset) { + ssize_t bytesWritten = pwrite( cow.fhd, buffer, size, block->offset + inBlockOffset ); + + if ( bytesWritten == -1 ) { + cowRequest->errorCode = errno; + } else if ( (size_t) bytesWritten < size ) { + cowRequest->errorCode = EIO; + } + atomic_fetch_add( &cowRequest->bytesWritten, netSize ); + setBitsInBitfield( block->bitfield, (int)( inBlockOffset / blockSize ), + (int)( (inBlockOffset + size)/ blockSize ) ); + block->time_changed = (atomic_uint_fast32_t)time( NULL ); +} + + +bool createL2Block( int l1Offset ) +{ + pthread_mutex_lock( &cow.l2CreateLock ); + if ( cow.l1[l1Offset] == NULL ) { + for ( int i = 0; i < cow.l2Size; i++ ) { + cow.nextL2[i].offset = -1; + cow.nextL2[i].time_changed = 0; + cow.nextL2[i].time_uploaded = 0; + memset( &cow.nextL2[i].bitfield, ATOMIC_VAR_INIT( 0 ), cow.bitfieldSize ); + } + cow.l1[l1Offset] = cow.nextL2; + cow.nextL2 += cow.l2Size; + } + pthread_mutex_unlock( &cow.l2CreateLock ); + return true; +} + +bool allocateMetaBlockData( cow_block_metadata *block ) +{ + block->offset = (atomic_long)atomic_fetch_add( &cow.dataFileSize, cow.metadataStorageCapacity ); + return true; +} + + + +// TODO if > remote pad 0 +/** + * @brief + * + */ +void padBlockFromRemote( fuse_req_t req, off_t offset, cow_request *cowRequest, cow_write_request *cowWriteRequest ) +{ + if ( offset > (off_t)metadata->originalImageSize ) { + //pad 0 and done + char buffer[4096] = { 0 }; + memcpy( buffer, cowWriteRequest->buffer, cowWriteRequest->size ); + + writeData( buffer, 4096, cowWriteRequest->size, cowRequest, cowWriteRequest->block, cowWriteRequest->inBlockOffset ); + free( cowWriteRequest ); + return; + } + + off_t start = offset - ( offset % 4096 ); + + dnbd3_async_t *request = malloc( sizeof( dnbd3_async_t ) ); + request->buffer = calloc( 4096, sizeof( char ) ); + request->length = 4096; + request->offset = start; + request->fuse_req = req; + request->cow = cowRequest; + request->cow_write = cowWriteRequest; + if ( ( (size_t)( offset + 4096L ) ) > metadata->originalImageSize ) { + request->length = (uint32_t) min( 4096, offset + 4096 - metadata->originalImageSize ); + } + + atomic_fetch_add(&cowRequest->workCounter,1); + if ( !connection_read( request ) ) { + atomic_fetch_sub(&cowRequest->workCounter,1); + // todo check if not now + cowRequest->errorCode = EIO; + free( request ); + return; + } +} + +void cowFile_readRemoteData( dnbd3_async_t *request ) +{ + if ( atomic_fetch_sub( &request->cow->workCounter, 1 ) == 1 ) { + fuse_reply_buf( request->fuse_req, request->cow->readBuffer, request->cow->fuseRequestSize ); + free( request->cow->readBuffer ); + free( request->cow ); + } + free( request ); +} + +void finishWriteRequest( fuse_req_t req, cow_request *cowRequest ) +{ + if ( cowRequest->errorCode != 0 ) { + fuse_reply_err( req, cowRequest->errorCode ); + + } else { + metadata->ImageSize = max( metadata->ImageSize, cowRequest->bytesWritten + cowRequest->fuseRequestOffset ); + if ( cowRequest->replyAttr ) { + //TODO HANDLE ERROR + image_ll_getattr( req, cowRequest->ino, cowRequest->fi ); + + } else { + fuse_reply_write( req, cowRequest->bytesWritten ); + } + } + if ( cowRequest->replyAttr ) { + free((char*)cowRequest->writeBuffer); + } + free( cowRequest ); +} + + + + +void cowfile_writePaddedBlock( dnbd3_async_t *request ) +{ + //copy write Data + memcpy( request->buffer + ( request->cow_write->inBlockOffset % 4096 ), request->cow_write->buffer, + request->cow_write->size ); + writeData( request->buffer, 4096, request->cow_write->size, request->cow, request->cow_write->block, request->cow_write->inBlockOffset ); + + free( request->cow_write ); + if ( atomic_fetch_sub( &request->cow->workCounter, 1 ) == 1 ) { + finishWriteRequest( request->fuse_req, request->cow ); + } + free( request->buffer ); + free( request ); +} + +/// TODO move block padding in write +void cowfile_write( fuse_req_t req, cow_request *cowRequest, off_t offset, size_t size ) +{ + if ( cowRequest->replyAttr ) { + cowRequest->writeBuffer = calloc( sizeof( char ), min( size, cow.metadataStorageCapacity ) ); + } + // if beyond end of file, pad with 0 + if ( offset > (off_t)metadata->ImageSize ) { + size_t pSize = offset - metadata->ImageSize; + // half end block will be padded with original write + pSize = pSize - ( ( pSize + offset ) % 4096 ); + atomic_fetch_add(&cowRequest->workCounter,1); + //TODO FIX that its actually 0 + cowfile_write( req, cowRequest, metadata->ImageSize, pSize); + } + + // TODO PREVENT RACE CONDITION on not full block writes + + off_t currentOffset = offset; + off_t endOffset = offset + size; + // get start & end block if needed( not on border and not already there) + if ( offset % 4096 != 0 ) { + int l1Offset = getL1Offset( offset ); + int l2Offset = getL2Offset( offset ); + if ( cow.l1[l1Offset] == NULL ) { + createL2Block( l1Offset ); + } + cow_block_metadata * metaBlock = &( cow.l1[l1Offset] )[l2Offset]; + size_t metaBlockStartOffset = l1Offset * cow.l2StorageCapacity + l2Offset * cow.metadataStorageCapacity; + size_t inBlockOffset = offset - metaBlockStartOffset; + + + if ( !checkBit( metaBlock->bitfield, (int)( inBlockOffset / 4096 ) ) ) { + size_t padSize = min( size, 4096L - ( (size_t) offset % 4096L ) ); + cow_write_request *cowWriteRequest = malloc( sizeof( cow_write_request ) ); + cowWriteRequest->inBlockOffset = (off_t) inBlockOffset; + cowWriteRequest->block = metaBlock; + cowWriteRequest->size = padSize; + cowWriteRequest->buffer = cowRequest->writeBuffer; + padBlockFromRemote( req, offset, cowRequest, cowWriteRequest ); + currentOffset += padSize; + } + } + // also make sure endblock != start block + if ( offset + size % 4096 != 0 && ( ( offset + (off_t) size ) / 4096L ) != ( offset / 4096L ) ) { + int l1Offset = getL1Offset( offset + size ); + int l2Offset = getL2Offset( offset + size ); + if ( cow.l1[l1Offset] == NULL ) { + createL2Block( l1Offset ); + } + cow_block_metadata * metaBlock = &( cow.l1[l1Offset] )[l2Offset]; + if ( metaBlock->offset == -1 ) { + allocateMetaBlockData( metaBlock ); + } + size_t metaBlockStartOffset = l1Offset * cow.l2StorageCapacity + l2Offset * cow.metadataStorageCapacity; + size_t padOffset = endOffset - ( endOffset % 4096 ); + size_t inBlockOffset = padOffset - metaBlockStartOffset; + + + if ( !checkBit( metaBlock->bitfield, (int)(inBlockOffset / 4096L) ) ) { + cow_write_request *cowWriteRequest = malloc( sizeof( cow_write_request ) ); + cowWriteRequest->inBlockOffset = (off_t )inBlockOffset; + cowWriteRequest->block = metaBlock; + cowWriteRequest->size = endOffset - padOffset; + cowWriteRequest->buffer = cowRequest->writeBuffer + ( padOffset - offset ); + padBlockFromRemote( req, padOffset, cowRequest, cowWriteRequest ); + + endOffset = padOffset; //TODO written size + } + } + + // lock for have block probably needed + + // write data + + int l1Offset = getL1Offset( currentOffset ); + int l2Offset = getL2Offset( currentOffset ); + while ( currentOffset < endOffset ) { + if ( cow.l1[l1Offset] == NULL ) { + createL2Block( l1Offset ); + } + //loop over L2 array (metadata) + while ( currentOffset < (off_t)endOffset && l2Offset < cow.l2Size ) { + cow_block_metadata *metaBlock = &( cow.l1[l1Offset] )[l2Offset]; + if ( metaBlock->offset == -1 ) { + allocateMetaBlockData( metaBlock ); + } + size_t metaBlockStartOffset = l1Offset * cow.l2StorageCapacity + l2Offset * cow.metadataStorageCapacity; + + size_t inBlockOffset = currentOffset - metaBlockStartOffset; + size_t sizeToWriteToBlock = min( (size_t) ( endOffset - currentOffset ), cow.metadataStorageCapacity - inBlockOffset ); + + writeData( cowRequest->writeBuffer + ( ( currentOffset - offset ) * !cowRequest->replyAttr ), sizeToWriteToBlock, + sizeToWriteToBlock, cowRequest, metaBlock, inBlockOffset); + + + currentOffset += sizeToWriteToBlock; + l2Offset++; + } + l1Offset++; + l2Offset = 0; + } + // return to fuse either here or in remote reads/writes + // increase file size if its now larger + 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. + */ +void readRemote( fuse_req_t req, off_t offset, size_t size, char *buffer, cow_request *cowRequest ) +{ + dnbd3_async_t *request = malloc( sizeof( dnbd3_async_t ) ); + request->buffer = buffer; + request->length = (uint32_t)size; + request->offset = offset; + request->fuse_req = req; + request->cow = cowRequest; + request->cow_write = NULL; + atomic_fetch_add(&cowRequest->workCounter,1); + if ( !connection_read( request ) ) { + atomic_fetch_sub(&cowRequest->workCounter,1); + //TODO ChECK IF NOT 0 Now + cowRequest->errorCode = EIO; + free( request ); + return; + } +} + +/* +Maybe optimize that remote reads are done first +*/ +/** + * @brief + * + * @param req Fuse request + * @param size of date to read + * @param offset + * @return uint64_t + */ +void cowfile_read( fuse_req_t req, size_t size, off_t offset ) +{ + cow_request *cowRequest = malloc( sizeof( cow_request ) ); + cowRequest->fuseRequestSize = size; + 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; + off_t localRead = -1; + int l1Offset = getL1Offset( offset ); + int l2Offset = getL2Offset( offset ); + int bitfieldOffset = getBitfieldOffset( offset ); + //loop over L1 array (l2s) + while ( searchOffset < offset + (off_t) size ) { + //verify l1Offset exists + if ( cow.l1[l1Offset] != NULL ) { + //loop over L2 array (metadata) + while ( searchOffset < offset + (off_t) size && l2Offset < cow.l2Size ) { + //verify l2Offset exists + //loop over Bitarray + cow_block_metadata block = ( cow.l1[l1Offset] )[l2Offset]; + while ( searchOffset < offset + (off_t) size && bitfieldOffset < cow.bitfieldSize ) { + // read differece between search and cur remote + // read everting possible in bitfield + if ( checkBit( block.bitfield, bitfieldOffset ) ) { + if ( localRead == -1 ) { + if ( lastReadOffset != offset ) { + readRemote( req, lastReadOffset, searchOffset - lastReadOffset, + cowRequest->readBuffer + ( lastReadOffset - offset ), cowRequest ); + + lastReadOffset = searchOffset; + } + localRead = block.offset + 4096 * bitfieldOffset + searchOffset % 4096; + } + } else if ( localRead != -1 ) { + //move search offset as far as possible in block + searchOffset = min( searchOffset + 4095, endOffset ); + size = searchOffset - lastReadOffset; + + ssize_t bytesRead = pread( cow.fhd, cowRequest->readBuffer + ( lastReadOffset - offset ), size, localRead ); + if ( bytesRead == -1 ) { + cowRequest->errorCode = errno; + } else if ( bytesRead < 4096 ) { + cowRequest->errorCode = EIO; + } + lastReadOffset = searchOffset; + localRead = -1; + } + bitfieldOffset++; + searchOffset = 4096 * ( bitfieldOffset + 1 ) + l2Offset * cow.metadataStorageCapacity + + l1Offset * cow.l2StorageCapacity; + } + // TODO deduplicate code + // READ DATA if last block was readable + if ( localRead != -1 ) { + //move search offset as far as possible in block + searchOffset = min( searchOffset + 4095, endOffset ); + size = searchOffset - lastReadOffset; + + ssize_t bytesRead = pread( cow.fhd, cowRequest->readBuffer + ( lastReadOffset - offset ), size, localRead ); + if ( bytesRead == -1 ) { + cowRequest->errorCode = errno; + } else if ( bytesRead < 4096 ) { + cowRequest->errorCode = EIO; + } + lastReadOffset = searchOffset; + localRead = -1; + } + l2Offset++; + searchOffset = l2Offset * cow.metadataStorageCapacity + l1Offset * cow.l2StorageCapacity; + bitfieldOffset = 0; + } + } + l1Offset++; + l2Offset = 0; + bitfieldOffset = 0; + searchOffset = l1Offset * cow.l2StorageCapacity; + } + + if ( lastReadOffset < endOffset ) { + readRemote( req, lastReadOffset, endOffset - lastReadOffset, + cowRequest->readBuffer + ( lastReadOffset - offset ), cowRequest ); + } + if ( atomic_fetch_sub( &cowRequest->workCounter, 1 ) == 1 ) { + if ( cowRequest->errorCode != 0 ) { + fuse_reply_err( req, cowRequest->errorCode ); + + } else { + fuse_reply_buf( req, cowRequest->readBuffer, size ); + } + free( cowRequest->readBuffer ); + free( cowRequest ); + } +}
\ No newline at end of file diff --git a/src/fuse/cowfile.h b/src/fuse/cowfile.h new file mode 100644 index 0000000..097d5e0 --- /dev/null +++ b/src/fuse/cowfile.h @@ -0,0 +1,72 @@ +#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 "main.h" + +#define min( X, Y ) ( ( ( X ) < ( Y ) ) ? ( X ) : ( Y ) ) +#define max( X, Y ) ( ( ( X ) > ( Y ) ) ? ( X ) : ( Y ) ) + +typedef struct cowfile_metadata_Header +{ + int version; + int blocksize; + size_t originalImageSize; + size_t ImageSize; + size_t meta_data_start; + int bitfieldSize; + size_t maxImageSize; + char imageName[200]; +} cowfile_metadata_Header; + +typedef struct cow_block_metadata +{ + atomic_long offset; + atomic_uint_fast32_t time_changed; + atomic_uint_fast32_t time_uploaded; + atomic_char bitfield[40]; +} cow_block_metadata; + + +typedef struct cow_request +{ + size_t fuseRequestSize; + off_t fuseRequestOffset; + char* readBuffer; + const char* writeBuffer; + atomic_size_t bytesWritten; + atomic_int workCounter; + atomic_int errorCode; + bool replyAttr; + fuse_ino_t ino; + struct fuse_file_info *fi; +} cow_request; + +typedef struct cow_write_request +{ + const char* buffer; + size_t size; + off_t inBlockOffset; + cow_block_metadata * block; + +} cow_write_request; + + +typedef cow_block_metadata** l1; +typedef cow_block_metadata* l2; + +bool cowfile_init( char *path, const char *image_Name, size_t ** imageSizePtr ); +bool cowfile_load( char *path ); +void cowfile_read(fuse_req_t req, size_t size, off_t offset); +void cowfile_write( fuse_req_t req, cow_request* cowRequest, off_t offset, size_t size); + +size_t cowfile_append( char *buffer, uint64_t offset, uint64_t size ); + +#endif /* COWFILE_H_ */
\ No newline at end of file diff --git a/src/fuse/main.c b/src/fuse/main.c index e06f6e8..f2566df 100644 --- a/src/fuse/main.c +++ b/src/fuse/main.c @@ -8,36 +8,9 @@ * 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"; @@ -45,21 +18,24 @@ static const char *STATS_NAME = "status"; static struct fuse_session *_fuseSession = NULL; static uint64_t imageSize; +static uint64_t *imageSizePtr =&imageSize; + /* Debug/Benchmark variables */ static bool useDebug = false; static log_info logInfo; static struct timespec startupTime; static uid_t owner; - +static bool useCow = false; 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; @@ -171,7 +154,7 @@ static void image_ll_open( fuse_req_t req, fuse_ino_t ino, struct fuse_file_info { if ( ino != INO_IMAGE && ino != INO_STATS ) { fuse_reply_err( req, EISDIR ); - } else if ( ( fi->flags & 3 ) != O_RDONLY ) { + } 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,11 +212,17 @@ 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 ); + + + dnbd3_async_t *request = malloc( sizeof(dnbd3_async_t) ); + request->buffer = malloc(size); request->length = (uint32_t)size; request->offset = offset; request->fuse_req = req; + request->cow = NULL; + request->cow_write = NULL; + if ( !connection_read( request ) ) { fuse_reply_err( req, EIO ); free( request ); @@ -260,6 +255,58 @@ 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* cowRequest = malloc(sizeof(cow_request)); + cowRequest->fuseRequestSize = size; + cowRequest->workCounter = ATOMIC_VAR_INIT( 1 ); + cowRequest->writeBuffer = buf; + cowRequest->readBuffer = NULL; + cowRequest->errorCode = ATOMIC_VAR_INIT( 0 ); + cowRequest->replyAttr = false; + cowRequest->fuseRequestOffset = off; + cowRequest->bytesWritten = 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) { + if(attr->st_size > (long)*imageSizePtr) { + cow_request* cowRequest = malloc(sizeof(cow_request)); + cowRequest->fuseRequestSize = attr->st_size - *imageSizePtr; + cowRequest->workCounter = ATOMIC_VAR_INIT( 1 ); + cowRequest->writeBuffer = NULL; + cowRequest->readBuffer = NULL; + cowRequest->errorCode = ATOMIC_VAR_INIT( 0 ); + cowRequest->replyAttr = true; + cowRequest->fi = fi; + cowRequest->ino = ino; + cowRequest->fuseRequestOffset = *imageSizePtr; + cowRequest->bytesWritten = ATOMIC_VAR_INIT( 0 ); + cowfile_write( req, cowRequest, *imageSizePtr, attr->st_size - *imageSizePtr); + } + else{ + *imageSizePtr = attr->st_size; + image_ll_getattr(req, ino, fi); + } + } +} + /* map the implemented fuse operations */ static struct fuse_lowlevel_ops image_oper = { .lookup = image_ll_lookup, @@ -271,6 +318,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" }; @@ -299,10 +360,11 @@ 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 <path> Enables cow, creates the cow files at given path\n" ); exit( exitCode ); } -static const char *optString = "dfHh:i:l:o:r:SsVv"; +static const char *optString = "dfHh:i:l:o:r:SsVvc:L:"; static const struct option longOpts[] = { { "debug", no_argument, NULL, 'd' }, { "help", no_argument, NULL, 'H' }, @@ -313,6 +375,7 @@ static const struct option longOpts[] = { { "rid", required_argument, NULL, 'r' }, { "sticky", no_argument, NULL, 'S' }, { "version", no_argument, NULL, 'v' }, + { "cow", required_argument, NULL, 'v' }, { 0, 0, 0, 0 } }; @@ -330,6 +393,8 @@ int main( int argc, char *argv[] ) struct fuse_chan *ch; char *mountpoint; int foreground = 0; + char *cow_file_path = NULL; + bool loadCow = false; log_init(); @@ -395,6 +460,15 @@ int main( int argc, char *argv[] ) case 'f': foreground = 1; break; + case 'c': + cow_file_path = optarg; + useCow = true; + break; + case 'L': + cow_file_path = optarg; + useCow = true; + loadCow = true; + break; default: printUsage( argv[0], EXIT_FAILURE ); } @@ -413,7 +487,11 @@ int main( int argc, char *argv[] ) logadd( LOG_WARNING, "Could not open log file at '%s'", log_file ); } } - + if ( loadCow ) { + if ( !cowfile_load( cow_file_path ) ) { + return EXIT_FAILURE; + } + } // Prepare our handler struct sigaction newHandler; memset( &newHandler, 0, sizeof( newHandler ) ); @@ -433,17 +511,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 +536,12 @@ int main( int argc, char *argv[] ) clock_gettime( CLOCK_REALTIME, &startupTime ); owner = getuid(); + if ( useCow & !loadCow) { + if( !cowfile_init( cow_file_path, IMAGE_NAME, &imageSizePtr) ) { + return EXIT_FAILURE; + } + } + // Fuse lowlevel loop struct fuse_args args = FUSE_ARGS_INIT( newArgc, newArgv ); int fuse_err = 1; @@ -463,7 +550,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 { diff --git a/src/fuse/main.h b/src/fuse/main.h new file mode 100644 index 0000000..117b31f --- /dev/null +++ b/src/fuse/main.h @@ -0,0 +1,36 @@ +#ifndef _MAIN_H_ +#define _MAIN_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> +#include "cowfile.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) + +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 |