summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Scherle2022-02-17 18:58:25 +0100
committerMichael Scherle2022-02-17 18:58:25 +0100
commitf2c5bb8ba9176cab845935f5ba6c325df3a2def7 (patch)
tree552221d44d246b94ad50321ea09b26c21cd05e76
parent[KERNEL] Add missing include to fix compile on 4.14.x (diff)
downloaddnbd3-f2c5bb8ba9176cab845935f5ba6c325df3a2def7.tar.gz
dnbd3-f2c5bb8ba9176cab845935f5ba6c325df3a2def7.tar.xz
dnbd3-f2c5bb8ba9176cab845935f5ba6c325df3a2def7.zip
basic cow implementation & rudimentary tests
-rw-r--r--CMakeLists.txt1
-rw-r--r--src/CMakeLists.txt4
-rw-r--r--src/cowtest/CMakeLists.txt27
-rw-r--r--src/cowtest/main.c358
-rw-r--r--src/fuse/CMakeLists.txt9
-rw-r--r--src/fuse/connection.c24
-rw-r--r--src/fuse/connection.h7
-rw-r--r--src/fuse/cowfile.c577
-rw-r--r--src/fuse/cowfile.h72
-rw-r--r--src/fuse/main.c191
-rw-r--r--src/fuse/main.h36
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