diff --git a/.github/workflows/compilation_on_android_ubuntu.yml b/.github/workflows/compilation_on_android_ubuntu.yml index 8a79e7ad..97021f17 100644 --- a/.github/workflows/compilation_on_android_ubuntu.yml +++ b/.github/workflows/compilation_on_android_ubuntu.yml @@ -413,6 +413,15 @@ jobs: ./build.sh ./run.sh + - name: Build Sample [file] + if: ${{ matrix.light == 'green' }} + run: | + cd samples/file + mkdir build && cd build + cmake .. + cmake --build . --config Release --parallel 4 + ./src/iwasm -f wasm-app/file.wasm -d . + - name: Build Sample [multi-thread] if: ${{ matrix.light == 'green' }} run: | diff --git a/.github/workflows/compilation_on_macos.yml b/.github/workflows/compilation_on_macos.yml index 9afbb844..ceacacd8 100644 --- a/.github/workflows/compilation_on_macos.yml +++ b/.github/workflows/compilation_on_macos.yml @@ -356,6 +356,15 @@ jobs: ./build.sh ./run.sh + - name: Build Sample [file] + if: ${{ matrix.light == 'green' }} + run: | + cd samples/file + mkdir build && cd build + cmake .. + cmake --build . --config Release --parallel 4 + ./src/iwasm -f wasm-app/file.wasm -d . + - name: Build Sample [multi-thread] if: ${{ matrix.light == 'green' }} run: | diff --git a/.github/workflows/compilation_on_sgx.yml b/.github/workflows/compilation_on_sgx.yml index f21b618c..98ebb457 100644 --- a/.github/workflows/compilation_on_sgx.yml +++ b/.github/workflows/compilation_on_sgx.yml @@ -140,6 +140,7 @@ jobs: # "-DWAMR_BUILD_SIMD=1", "-DWAMR_BUILD_TAIL_CALL=1", "-DWAMR_DISABLE_HW_BOUND_CHECK=1", + "-DWAMR_BUILD_SGX_IPFS=1", ] os: [ubuntu-20.04] platform: [linux-sgx] @@ -363,6 +364,15 @@ jobs: ./build.sh ./run.sh + - name: Build Sample [file] + if: ${{ matrix.light == 'green' }} + run: | + cd samples/file + mkdir build && cd build + cmake .. + cmake --build . --config Release --parallel 4 + ./src/iwasm -f wasm-app/file.wasm -d . + - name: Build Sample [multi-thread] if: ${{ matrix.light == 'green' }} run: | diff --git a/README.md b/README.md index 02f7ea3c..84bee711 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,7 @@ The WAMR [samples](./samples) integrate the iwasm VM core, application manager a - [**basic**](./samples/basic): Demonstrating how to use runtime exposed API's to call WASM functions, how to register native functions and call them, and how to call WASM function from native function. - **[simple](./samples/simple/README.md)**: The runtime is integrated with most of the WAMR APP libraries, and a few WASM applications are provided for testing the WAMR APP API set. It uses **built-in libc** and executes apps in **interpreter** mode by default. +- **[file](./samples/file/README.md)**: Demonstrating the supported file interaction API of WASI. This sample can also demonstrate the SGX IPFS (Intel Protected File System), enabling an enclave to seal and unseal data at rest. - **[littlevgl](./samples/littlevgl/README.md)**: Demonstrating the graphic user interface application usage on WAMR. The whole [LVGL](https://github.com/lvgl/lvgl) 2D user graphic library and the UI application are built into WASM application. It uses **WASI libc** and executes apps in **AOT mode** by default. - **[gui](./samples/gui/README.md)**: Move the [LVGL](https://github.com/lvgl/lvgl) library into the runtime and define a WASM application interface by wrapping the littlevgl API. It uses **WASI libc** and executes apps in **interpreter** mode by default. - **[multi-thread](./samples/multi-thread/)**: Demonstrating how to run wasm application which creates multiple threads to execute wasm functions concurrently, and uses mutex/cond by calling pthread related API's. diff --git a/build-scripts/config_common.cmake b/build-scripts/config_common.cmake index 8d99b93a..21ddc501 100644 --- a/build-scripts/config_common.cmake +++ b/build-scripts/config_common.cmake @@ -287,3 +287,7 @@ if (WAMR_BUILD_STACK_GUARD_SIZE GREATER 0) add_definitions (-DWASM_STACK_GUARD_SIZE=${WAMR_BUILD_STACK_GUARD_SIZE}) message (" Custom stack guard size: " ${WAMR_BUILD_STACK_GUARD_SIZE}) endif () +if (WAMR_BUILD_SGX_IPFS EQUAL 1) + add_definitions (-DWASM_ENABLE_SGX_IPFS=1) + message (" SGX IPFS enabled") +endif () diff --git a/core/config.h b/core/config.h index 728bdc7e..024913b7 100644 --- a/core/config.h +++ b/core/config.h @@ -407,4 +407,8 @@ #define WASM_ENABLE_REF_TYPES 0 #endif +#ifndef WASM_ENABLE_SGX_IPFS +#define WASM_ENABLE_SGX_IPFS 0 +#endif + #endif /* end of _CONFIG_H_ */ diff --git a/core/shared/platform/linux-sgx/sgx_file.c b/core/shared/platform/linux-sgx/sgx_file.c index f71249fc..772a8087 100644 --- a/core/shared/platform/linux-sgx/sgx_file.c +++ b/core/shared/platform/linux-sgx/sgx_file.c @@ -7,6 +7,10 @@ #include "sgx_error.h" #include "sgx_file.h" +#if WASM_ENABLE_SGX_IPFS != 0 +#include "sgx_ipfs.h" +#endif + #ifndef SGX_DISABLE_WASI #define TRACE_FUNC() os_printf("undefined %s\n", __FUNCTION__) @@ -184,6 +188,22 @@ openat(int dirfd, const char *pathname, int flags, ...) if (fd == -1) errno = get_errno(); + +#if WASM_ENABLE_SGX_IPFS != 0 + // When WAMR uses Intel SGX IPFS to enabled, it opens a second + // file descriptor to interact with the secure file. + // The first file descriptor opened earlier is used to interact + // with the metadata of the file (e.g., time, flags, etc.). + int ret; + void *file_ptr = ipfs_fopen(fd, pathname, flags); + if (file_ptr == NULL) { + if (ocall_close(&ret, fd) != SGX_SUCCESS) { + TRACE_OCALL_FAIL(); + } + return -1; + } +#endif + return fd; } @@ -192,6 +212,13 @@ close(int fd) { int ret; +#if WASM_ENABLE_SGX_IPFS != 0 + // Close the IPFS file pointer in addition of the file descriptor + ret = ipfs_close(fd); + if (ret == -1) + errno = get_errno(); +#endif + if (ocall_close(&ret, fd) != SGX_SUCCESS) { TRACE_OCALL_FAIL(); return -1; @@ -345,6 +372,12 @@ readv_internal(int fd, const struct iovec *iov, int iovcnt, bool has_offset, if (total_size >= UINT32_MAX) return -1; +#if WASM_ENABLE_SGX_IPFS != 0 + if (fd > 2) { + return ipfs_read(fd, iov, iovcnt, has_offset, offset); + } +#endif + iov1 = BH_MALLOC((uint32)total_size); if (iov1 == NULL) @@ -410,6 +443,12 @@ writev_internal(int fd, const struct iovec *iov, int iovcnt, bool has_offset, if (total_size >= UINT32_MAX) return -1; +#if WASM_ENABLE_SGX_IPFS != 0 + if (fd > 2) { + return ipfs_write(fd, iov, iovcnt, has_offset, offset); + } +#endif + iov1 = BH_MALLOC((uint32)total_size); if (iov1 == NULL) @@ -468,12 +507,18 @@ off_t lseek(int fd, off_t offset, int whence) { off_t ret; + +#if WASM_ENABLE_SGX_IPFS != 0 + ret = ipfs_lseek(fd, offset, whence); +#else if (ocall_lseek(&ret, fd, (long)offset, whence) != SGX_SUCCESS) { TRACE_OCALL_FAIL(); return -1; } if (ret == -1) errno = get_errno(); +#endif + return ret; } @@ -482,12 +527,17 @@ ftruncate(int fd, off_t length) { int ret; +#if WASM_ENABLE_SGX_IPFS != 0 + ret = ipfs_ftruncate(fd, length); +#else if (ocall_ftruncate(&ret, fd, length) != SGX_SUCCESS) { TRACE_OCALL_FAIL(); return -1; } if (ret == -1) errno = get_errno(); +#endif + return ret; } @@ -554,12 +604,17 @@ fsync(int fd) { int ret; +#if WASM_ENABLE_SGX_IPFS != 0 + ret = ipfs_fflush(fd); +#else if (ocall_fsync(&ret, fd) != SGX_SUCCESS) { TRACE_OCALL_FAIL(); return -1; } if (ret == -1) errno = get_errno(); +#endif + return ret; } @@ -568,12 +623,17 @@ fdatasync(int fd) { int ret; +#if WASM_ENABLE_SGX_IPFS != 0 + ret = ipfs_fflush(fd); +#else if (ocall_fdatasync(&ret, fd) != SGX_SUCCESS) { TRACE_OCALL_FAIL(); return -1; } if (ret == -1) errno = get_errno(); +#endif + return ret; } @@ -801,10 +861,14 @@ posix_fallocate(int fd, off_t offset, off_t len) { int ret; +#if WASM_ENABLE_SGX_IPFS != 0 + ret = ipfs_posix_fallocate(fd, offset, len); +#else if (ocall_posix_fallocate(&ret, fd, offset, len) != SGX_SUCCESS) { TRACE_OCALL_FAIL(); return -1; } +#endif return ret; } diff --git a/core/shared/platform/linux-sgx/sgx_ipfs.c b/core/shared/platform/linux-sgx/sgx_ipfs.c new file mode 100644 index 00000000..76750946 --- /dev/null +++ b/core/shared/platform/linux-sgx/sgx_ipfs.c @@ -0,0 +1,460 @@ +/* + * Copyright (C) 2022 Intel Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#if WASM_ENABLE_SGX_IPFS != 0 + +#include "ssp_config.h" +#include "bh_platform.h" +#include "sgx_ipfs.h" + +#include + +#include "sgx_tprotected_fs.h" + +#define SGX_ERROR_FILE_LOWEST_ERROR_ID SGX_ERROR_FILE_BAD_STATUS +#define SGX_ERROR_FILE_HIGHEST_ERROR_ID SGX_ERROR_FILE_CLOSE_FAILED + +// The mapping between file descriptors and IPFS file pointers. +static HashMap *ipfs_file_list; + +// Converts an SGX error code to a POSIX error code. +static __wasi_errno_t +convert_sgx_errno(int error) +{ + if (error >= SGX_ERROR_FILE_LOWEST_ERROR_ID + && error <= SGX_ERROR_FILE_HIGHEST_ERROR_ID) { + switch (error) { + /* The file is in bad status */ + case SGX_ERROR_FILE_BAD_STATUS: + return ENOTRECOVERABLE; + /* The Key ID field is all zeros, can't re-generate the encryption + * key */ + case SGX_ERROR_FILE_NO_KEY_ID: + return EKEYREJECTED; + /* The current file name is different then the original file name + * (not allowed, substitution attack) */ + case SGX_ERROR_FILE_NAME_MISMATCH: + return EIO; + /* The file is not an SGX file */ + case SGX_ERROR_FILE_NOT_SGX_FILE: + return EEXIST; + /* A recovery file can't be opened, so flush operation can't + * continue (only used when no EXXX is returned) */ + case SGX_ERROR_FILE_CANT_OPEN_RECOVERY_FILE: + return EIO; + /* A recovery file can't be written, so flush operation can't + * continue (only used when no EXXX is returned) */ + case SGX_ERROR_FILE_CANT_WRITE_RECOVERY_FILE: + return EIO; + /* When openeing the file, recovery is needed, but the recovery + * process failed */ + case SGX_ERROR_FILE_RECOVERY_NEEDED: + return EIO; + /* fflush operation (to disk) failed (only used when no EXXX is + * returned) */ + case SGX_ERROR_FILE_FLUSH_FAILED: + return EIO; + /* fclose operation (to disk) failed (only used when no EXXX is + * returned) */ + case SGX_ERROR_FILE_CLOSE_FAILED: + return EIO; + } + } + + return error; +} + +static void * +fd2file(int fd) +{ + return bh_hash_map_find(ipfs_file_list, (void *)(intptr_t)fd); +} + +static void +ipfs_file_destroy(void *sgx_file) +{ + sgx_fclose(sgx_file); +} + +int +ipfs_init() +{ + ipfs_file_list = + bh_hash_map_create(32, true, (HashFunc)fd_hash, (KeyEqualFunc)fd_equal, + NULL, (ValueDestroyFunc)ipfs_file_destroy); + + return ipfs_file_list != NULL ? BHT_OK : BHT_ERROR; +} + +void +ipfs_destroy() +{ + bh_hash_map_destroy(ipfs_file_list); +} + +int +ipfs_posix_fallocate(int fd, off_t offset, size_t len) +{ + void *sgx_file = fd2file(fd); + if (!sgx_file) { + return EBADF; + } + + // The wrapper for fseek takes care of extending the file if sought beyond + // the end + if (ipfs_lseek(fd, offset + len, SEEK_CUR) == -1) { + return errno; + } + + // Make sure the file is allocated by flushing it + if (sgx_fflush(sgx_file) != 0) { + return errno; + } + + return 0; +} + +size_t +ipfs_read(int fd, const struct iovec *iov, int iovcnt, bool has_offset, + off_t offset) +{ + int i; + off_t original_offset = 0; + void *sgx_file = fd2file(fd); + size_t read_result, number_of_read_bytes = 0; + + if (!sgx_file) { + errno = EBADF; + return -1; + } + + if (has_offset) { + // Save the current offset, to restore it after the read operation + original_offset = (off_t)sgx_ftell(sgx_file); + + if (original_offset == -1) { + errno = convert_sgx_errno(sgx_ferror(sgx_file)); + return -1; + } + + // Move to the desired location + if (sgx_fseek(sgx_file, offset, SEEK_SET) == -1) { + errno = convert_sgx_errno(sgx_ferror(sgx_file)); + return -1; + } + } + + // For each element in the vector + for (i = 0; i < iovcnt; i++) { + if (iov[i].iov_len == 0) + continue; + + read_result = sgx_fread(iov[i].iov_base, 1, iov[i].iov_len, sgx_file); + number_of_read_bytes += read_result; + + if (read_result != iov[i].iov_len) { + if (!sgx_feof(sgx_file)) { + errno = convert_sgx_errno(sgx_ferror(sgx_file)); + return -1; + } + } + } + + if (has_offset) { + // Restore the position of the cursor + if (sgx_fseek(sgx_file, original_offset, SEEK_SET) == -1) { + errno = convert_sgx_errno(sgx_ferror(sgx_file)); + return -1; + } + } + + return number_of_read_bytes; +} + +size_t +ipfs_write(int fd, const struct iovec *iov, int iovcnt, bool has_offset, + off_t offset) +{ + int i; + off_t original_offset = 0; + void *sgx_file = fd2file(fd); + size_t write_result, number_of_written_bytes = 0; + + if (!sgx_file) { + errno = EBADF; + return -1; + } + + if (has_offset) { + // Save the current offset, to restore it after the read operation + original_offset = (off_t)sgx_ftell(sgx_file); + + if (original_offset == -1) { + errno = convert_sgx_errno(sgx_ferror(sgx_file)); + return -1; + } + + // Move to the desired location + if (sgx_fseek(sgx_file, offset, SEEK_SET) == -1) { + errno = convert_sgx_errno(sgx_ferror(sgx_file)); + return -1; + } + } + + // For each element in the vector + for (i = 0; i < iovcnt; i++) { + if (iov[i].iov_len == 0) + continue; + + write_result = sgx_fwrite(iov[i].iov_base, 1, iov[i].iov_len, sgx_file); + number_of_written_bytes += write_result; + + if (write_result != iov[i].iov_len) { + errno = convert_sgx_errno(sgx_ferror(sgx_file)); + return -1; + } + } + + if (has_offset) { + // Restore the position of the cursor + if (sgx_fseek(sgx_file, original_offset, SEEK_SET) == -1) { + errno = convert_sgx_errno(sgx_ferror(sgx_file)); + return -1; + } + } + + return number_of_written_bytes; +} + +int +ipfs_close(int fd) +{ + void *sgx_file; + + if (!bh_hash_map_remove(ipfs_file_list, (void *)(intptr_t)fd, NULL, + &sgx_file)) { + errno = EBADF; + return -1; + } + + if (sgx_fclose(sgx_file)) { + errno = convert_sgx_errno(sgx_ferror(sgx_file)); + return -1; + } + + return 0; +} + +void * +ipfs_fopen(int fd, const char *filename, int flags) +{ + // Mapping back the mode + const char *mode; + + bool must_create = (flags & O_CREAT) != 0; + bool must_truncate = (flags & O_TRUNC) != 0; + bool must_append = (flags & O_APPEND) != 0; + bool read_only = (flags & O_ACCMODE) == O_RDONLY; + bool write_only = (flags & O_ACCMODE) == O_WRONLY; + bool read_write = (flags & O_ACCMODE) == O_RDWR; + + // The mapping of the mode are described in the table in the official + // specifications: + // https://pubs.opengroup.org/onlinepubs/9699919799/functions/fopen.html + if (read_only) + mode = "r"; + else if (write_only && must_create && must_truncate) + mode = "w"; + else if (write_only && must_create && must_append) + mode = "a"; + else if (read_write && must_create && must_truncate) + mode = "w+"; + else if (read_write && must_create && must_append) + mode = "a+"; + else if (read_write && must_create) + mode = "w+"; + else if (read_write) + mode = "r+"; + else + mode = NULL; + + // Cannot map the requested access to the SGX IPFS + if (mode == NULL) { + errno = __WASI_ENOTCAPABLE; + return NULL; + } + + // Opening the file + void *sgx_file = sgx_fopen_auto_key(filename, mode); + + if (sgx_file == NULL) { + errno = convert_sgx_errno(sgx_ferror(sgx_file)); + return NULL; + } + + if (!bh_hash_map_insert(ipfs_file_list, (void *)(intptr_t)fd, sgx_file)) { + errno = __WASI_ECANCELED; + sgx_fclose(sgx_file); + os_printf("An error occurred while inserting the IPFS file pointer in " + "the map."); + return NULL; + } + + return sgx_file; +} + +int +ipfs_fflush(int fd) +{ + void *sgx_file = fd2file(fd); + + if (!sgx_file) { + errno = EBADF; + return EOF; + } + + int ret = sgx_fflush(sgx_file); + + if (ret == 1) { + errno = convert_sgx_errno(sgx_ferror(sgx_file)); + return EOF; + } + + return ret; +} + +off_t +ipfs_lseek(int fd, off_t offset, int nwhence) +{ + off_t new_offset; + void *sgx_file = fd2file(fd); + if (!sgx_file) { + errno = EBADF; + return -1; + } + + // Optimization: if the offset is 0 and the whence is SEEK_CUR, + // this is equivalent of a call to ftell. + if (offset == 0 && nwhence == SEEK_CUR) { + int64_t ftell_result = (off_t)sgx_ftell(sgx_file); + + if (ftell_result == -1) { + errno = convert_sgx_errno(sgx_ferror(sgx_file)); + return -1; + } + + return ftell_result; + } + + int fseek_result = sgx_fseek(sgx_file, offset, nwhence); + + if (fseek_result == 0) { + new_offset = (__wasi_filesize_t)sgx_ftell(sgx_file); + + if (new_offset == -1) { + errno = convert_sgx_errno(sgx_ferror(sgx_file)); + return -1; + } + + return new_offset; + } + else { + // In the case fseek returned an error + int sgx_error = sgx_ferror(sgx_file); + if (sgx_error != EINVAL) { + errno = convert_sgx_errno(sgx_error); + return -1; + } + + // We must consider a difference in behavior of sgx_fseek and the POSIX + // fseek. If the cursor is moved beyond the end of the file, sgx_fseek + // returns an error, whereas POSIX fseek accepts the cursor move and + // fill with zeroes the difference for the next write. This + // implementation handle zeroes completion and moving the cursor forward + // the end of the file, but does it now (during the fseek), which is + // different compared to POSIX implementation, that writes zeroes on the + // next write. This avoids the runtime to keep track of the cursor + // manually. + + // Assume the error is raised because the cursor is moved beyond the end + // of the file. Try to move the cursor at the end of the file. + if (sgx_fseek(sgx_file, 0, SEEK_END) == -1) { + errno = convert_sgx_errno(sgx_ferror(sgx_file)); + return -1; + } + + // Write the missing zeroes + char zero = 0; + int64_t number_of_zeroes = offset - sgx_ftell(sgx_file); + if (sgx_fwrite(&zero, 1, number_of_zeroes, sgx_file) == 0) { + errno = convert_sgx_errno(sgx_ferror(sgx_file)); + return -1; + } + + // Move again at the end of the file + if (sgx_fseek(sgx_file, 0, SEEK_END) == -1) { + errno = convert_sgx_errno(sgx_ferror(sgx_file)); + return -1; + } + + return offset; + } +} + +// The official API does not provide a way to truncate files. +// Only files extension is supported. +int +ipfs_ftruncate(int fd, off_t len) +{ + void *sgx_file = fd2file(fd); + if (!sgx_file) { + errno = EBADF; + return -1; + } + + off_t original_offset = sgx_ftell(sgx_file); + + // Optimization path: if the length is smaller than the offset, + // IPFS does not support truncate to a smaller size. + if (len < original_offset) { + os_printf( + "SGX IPFS does not support truncate files to smaller sizes.\n"); + return __WASI_ECANCELED; + } + + // Move to the end of the file to determine whether this is + // a file extension or reduction. + if (sgx_fseek(sgx_file, 0, SEEK_END) == -1) { + errno = convert_sgx_errno(sgx_ferror(sgx_file)); + return -1; + } + + off_t file_size = sgx_ftell(sgx_file); + + // Reducing the file space is not supported by IPFS. + if (len < file_size) { + os_printf( + "SGX IPFS does not support truncate files to smaller sizes.\n"); + return __WASI_ECANCELED; + } + + // Increasing the size is equal to writing from the end of the file + // with null bytes. + char null_byte = 0; + if (sgx_fwrite(&null_byte, 1, len - file_size, sgx_file) == 0) { + errno = convert_sgx_errno(sgx_ferror(sgx_file)); + return -1; + } + + // Restore the position of the cursor + if (sgx_fseek(sgx_file, original_offset, SEEK_SET) == -1) { + errno = convert_sgx_errno(sgx_ferror(sgx_file)); + return -1; + } + + return 0; +} + +#endif /* end of WASM_ENABLE_SGX_IPFS */ \ No newline at end of file diff --git a/core/shared/platform/linux-sgx/sgx_ipfs.h b/core/shared/platform/linux-sgx/sgx_ipfs.h new file mode 100644 index 00000000..ade40bd5 --- /dev/null +++ b/core/shared/platform/linux-sgx/sgx_ipfs.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2022 Intel Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#ifndef _LIBC_WASI_SGX_PFS_H +#define _LIBC_WASI_SGX_PFS_H + +#include "bh_hashmap.h" +#include "wasmtime_ssp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int +ipfs_init(); +void +ipfs_destroy(); +int +ipfs_posix_fallocate(int fd, off_t offset, size_t len); +size_t +ipfs_read(int fd, const struct iovec *iov, int iovcnt, bool has_offset, + off_t offset); +size_t +ipfs_write(int fd, const struct iovec *iov, int iovcnt, bool has_offset, + off_t offset); +int +ipfs_close(int fd); +void * +ipfs_fopen(int fd, const char *filename, int flags); +int +ipfs_fflush(int fd); +off_t +ipfs_lseek(int fd, off_t offset, int nwhence); +int +ipfs_ftruncate(int fd, off_t len); + +/** + * Whether two file descriptors are equal. + */ +inline static bool +fd_equal(int left, int right) +{ + return left == right ? true : false; +} + +/** + * Returns the file descriptor as a hash value. + */ +inline static uint32 +fd_hash(int fd) +{ + return (uint32)fd; +} + +#ifdef __cplusplus +} +#endif + +#endif /* end of _LIBC_WASI_SGX_PFS_H */ \ No newline at end of file diff --git a/core/shared/platform/linux-sgx/sgx_platform.c b/core/shared/platform/linux-sgx/sgx_platform.c index b14ce67d..c0b423d5 100644 --- a/core/shared/platform/linux-sgx/sgx_platform.c +++ b/core/shared/platform/linux-sgx/sgx_platform.c @@ -7,17 +7,31 @@ #include "platform_api_extension.h" #include "sgx_rsrv_mem_mngr.h" +#if WASM_ENABLE_SGX_IPFS != 0 +#include "sgx_ipfs.h" +#endif + static os_print_function_t print_function = NULL; int bh_platform_init() { - return 0; + int ret = BHT_OK; + +#if WASM_ENABLE_SGX_IPFS != 0 + ret = ipfs_init(); +#endif + + return ret; } void bh_platform_destroy() -{} +{ +#if WASM_ENABLE_SGX_IPFS != 0 + ipfs_destroy(); +#endif +} void * os_malloc(unsigned size) diff --git a/doc/linux_sgx.md b/doc/linux_sgx.md index 29f570ba..e7a32753 100644 --- a/doc/linux_sgx.md +++ b/doc/linux_sgx.md @@ -195,6 +195,57 @@ typedef enum EcallCmd { }; ``` +SGX Intel Protected File System +------------------------------- +Intel SGX introduced a feature called [Intel Protection File System Library (IPFS)](https://www.intel.com/content/www/us/en/developer/articles/technical/overview-of-intel-protected-file-system-library-using-software-guard-extensions.html) to create, operate and delete files inside the enclave. +WAMR supports the mapping of IPFS on WASI functions related to file interactions, providing seamless persistence with confidentiality and integrity to the hosted WebAssembly applications in the enclave. + +The usage of SGX IPFS is an optional feature. +To opt-in, the support of IPFS requires the following changes: + - set the flag `WAMR_BUILD_SGX_IPFS=1` when running `cmake`, + - the enclave must be linked with the trusted IPFS library (`-lsgx_tprotected_fs`), + - the application outside of the enclave must be linked with the untrusted IPFS library (`-lsgx_uprotected_fs`), + - the EDL file must include the following import statement: + +```edl +from "sgx_tprotected_fs.edl" import *; +``` + +When using the [enclave-sample](../product-mini/platforms/linux-sgx/enclave-sample/) project, setting the flag `WAMR_BUILD_SGX_IPFS=1` when running `cmake` enables these changes automatically. + + +### Verification of SGX IPFS +One can observe the usage of IPFS by running the [file sample](../samples/file/) WebAssembly application. +Enabling the SGX IPFS on this sample project leads to the generation of an encrypted text file. + + +### Mapping of WASI/POSIX to IPFS +This table summarizes how WASI is mapped to POSIX and IPFS. +Since IPFS is a subset of the WASI/POSIX, emulation is performed to fill the missing implementation. + +| WASI | POSIX | IPFS | +|------------------------|-------------------|-------------------------------------------------------------------------------------------------------------------------| +| `fd_read` | `readv` | `sgx_fread` | +| `fd_write` | `writev` | `sgx_fwrite` | +| `fd_close` | `close` | `sgx_fclose` | +| `path_open` | `openat` | `sgx_fopen` | +| `fd_datasync` | `fsync` | `sgx_fflush` | +| `fd_tell` | `lseek` | `sgx_ftell` | +| `fd_filestat_set_size` | `ftruncate` | Shrinking files is not supported, nor emulated. Extending files is emulated using `sgx_fseek`/`sgx_ftell`/`sgx_fwrite`. | +| `fd_seek` | `lseek` | The POSIX and IPFS behaviors differ. Emulated using `sgx_fseek`/`sgx_ftell`/`sgx_fwrite`. | +| `fd_pwrite` | `pwrite` | Not supported. Emulated using `sgx_fseek`/`sgx_ftell`/`sgx_fwrite`. | +| `fd_pread` | `pread` | Not supported. Emulated using `sgx_fseek`/`sgx_ftell`/`sgx_fread`. | +| `fd_allocate` | `posix_fallocate` | Not supported. Emulated using `sgx_fseek`/`sgx_ftell`/`sgx_fwrite`/`sgx_fflush`. | + + +### Performance overheads +Many benchmarks have assessed the overheads caused by IPFS through WASI functions using Twine, an early and academic adaptation of WAMR in Intel SGX with WASI support. +The results can be found in [this paper](https://arxiv.org/abs/2103.15860). + +### Limitations +The threat model and the limitations of SGX IPFS can be found in [the official documentation](https://www.intel.com/content/dam/develop/external/us/en/documents/overviewofintelprotectedfilesystemlibrary.pdf). + + Others ------ diff --git a/product-mini/platforms/linux-sgx/CMakeLists.txt b/product-mini/platforms/linux-sgx/CMakeLists.txt index dfc98975..a0ca1abe 100644 --- a/product-mini/platforms/linux-sgx/CMakeLists.txt +++ b/product-mini/platforms/linux-sgx/CMakeLists.txt @@ -84,6 +84,11 @@ if (NOT DEFINED WAMR_BUILD_SIMD) set (WAMR_BUILD_SIMD 0) endif () +if (NOT DEFINED WAMR_BUILD_SGX_IPFS) + # Disable SGX IPFS by default + set (WAMR_BUILD_SGX_IPFS 0) +endif () + if (COLLECT_CODE_COVERAGE EQUAL 1) set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-arcs -ftest-coverage") endif () @@ -117,3 +122,23 @@ else() OUTPUT_VARIABLE cmdOutput ) endif() + +if (WAMR_BUILD_SGX_IPFS EQUAL 1) + execute_process( + COMMAND bash -c "sed -i -E 's/^#define SGX_IPFS 0/#define SGX_IPFS 1/g' ${CMAKE_CURRENT_SOURCE_DIR}/enclave-sample/Enclave/Enclave.edl" + OUTPUT_VARIABLE cmdOutput + ) + execute_process( + COMMAND bash -c "sed -i -E 's/^SGX_IPFS = 0/SGX_IPFS = 1/g' ${CMAKE_CURRENT_SOURCE_DIR}/enclave-sample/Makefile" + OUTPUT_VARIABLE cmdOutput + ) +else() + execute_process( + COMMAND bash -c "sed -i -E 's/^#define SGX_IPFS 1/#define SGX_IPFS 0/g' ${CMAKE_CURRENT_SOURCE_DIR}/enclave-sample/Enclave/Enclave.edl" + OUTPUT_VARIABLE cmdOutput + ) + execute_process( + COMMAND bash -c "sed -i -E 's/^SGX_IPFS = 1/SGX_IPFS = 0/g' ${CMAKE_CURRENT_SOURCE_DIR}/enclave-sample/Makefile" + OUTPUT_VARIABLE cmdOutput + ) +endif() diff --git a/product-mini/platforms/linux-sgx/enclave-sample/Enclave/Enclave.edl b/product-mini/platforms/linux-sgx/enclave-sample/Enclave/Enclave.edl index d3281f84..fa7ed42c 100644 --- a/product-mini/platforms/linux-sgx/enclave-sample/Enclave/Enclave.edl +++ b/product-mini/platforms/linux-sgx/enclave-sample/Enclave/Enclave.edl @@ -4,6 +4,7 @@ */ #define LIB_RATS 0 +#define SGX_IPFS 0 enclave { from "sgx_tstdc.edl" import *; @@ -12,6 +13,9 @@ enclave { #if LIB_RATS != 0 from "rats.edl" import *; #endif +#if SGX_IPFS != 0 + from "sgx_tprotected_fs.edl" import *; +#endif trusted { /* define ECALLs here. */ diff --git a/product-mini/platforms/linux-sgx/enclave-sample/Makefile b/product-mini/platforms/linux-sgx/enclave-sample/Makefile index e1fea1e3..bb7cfd19 100644 --- a/product-mini/platforms/linux-sgx/enclave-sample/Makefile +++ b/product-mini/platforms/linux-sgx/enclave-sample/Makefile @@ -9,6 +9,9 @@ SGX_ARCH ?= x64 SGX_DEBUG ?= 0 SPEC_TEST ?= 0 +# This variable is automatically set by CMakeLists.txt +SGX_IPFS = 0 + VMLIB_BUILD_DIR ?= $(CURDIR)/../build LIB_RATS_SRC ?= $(VMLIB_BUILD_DIR)/_deps/librats-build LIB_RATS := $(shell if [ -d $(LIB_RATS_SRC) ]; then echo 1; else echo 0; fi) @@ -106,6 +109,12 @@ else Trts_Library_Name := sgx_trts Service_Library_Name := sgx_tservice endif + +ifeq ($(SGX_IPFS), 1) + Intel_Ipfs_Trusted_Flag = -lsgx_tprotected_fs + App_Link_Flags += -lsgx_uprotected_fs +endif + Crypto_Library_Name := sgx_tcrypto WAMR_ROOT := $(CURDIR)/../../../../ @@ -139,7 +148,7 @@ endif Enclave_Cpp_Flags := $(Enclave_C_Flags) -std=c++11 -nostdinc++ Enclave_Link_Flags := $(SGX_COMMON_CFLAGS) -Wl,--no-undefined -nostdlib -nodefaultlibs -nostartfiles -L$(SGX_LIBRARY_PATH) ${Rats_Lib_Link_Dirs} \ - -Wl,--whole-archive -l$(Trts_Library_Name) ${Rats_Lib_Link_libs} -Wl,--no-whole-archive \ + -Wl,--whole-archive -l$(Trts_Library_Name) ${Rats_Lib_Link_libs} $(Intel_Ipfs_Trusted_Flag) -Wl,--no-whole-archive \ -Wl,--start-group -lsgx_tstdc -lsgx_tcxx -lsgx_pthread -lsgx_tkey_exchange -l$(Crypto_Library_Name) -l$(Service_Library_Name) -lsgx_dcap_tvl -Wl,--end-group \ -Wl,-Bstatic -Wl,-Bsymbolic -Wl,--no-undefined \ -Wl,-pie,-eenclave_entry -Wl,--export-dynamic \ @@ -217,7 +226,6 @@ $(App_Name): App/Enclave_u.o $(App_Cpp_Objects) libvmlib_untrusted.a ######## Enclave Objects ######## - Enclave/Enclave_t.c: $(SGX_EDGER8R) Enclave/Enclave.edl librats @cd Enclave && $(SGX_EDGER8R) --trusted ../Enclave/Enclave.edl $(Enclave_Edl_Search_Path) @echo "GEN => $@" diff --git a/samples/file/CMakeLists.txt b/samples/file/CMakeLists.txt new file mode 100644 index 00000000..c3a69ccc --- /dev/null +++ b/samples/file/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (C) 2022 Intel Corporation. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +cmake_minimum_required(VERSION 3.0) +project(file) + +################ wasm application ############### +add_subdirectory(src) +add_subdirectory(wasm-app) diff --git a/samples/file/README.md b/samples/file/README.md new file mode 100644 index 00000000..8b34719e --- /dev/null +++ b/samples/file/README.md @@ -0,0 +1,112 @@ +# "file" sample introduction + +This sample demonstrates the supported file interaction API of WASI. +This sample can also demonstrate the SGX IPFS (Intel Protected File System), enabling an enclave to seal and unseal data at rest. + +## Preparation + +Please install WASI SDK, download the [wasi-sdk release](https://github.com/CraneStation/wasi-sdk/releases) and extract the archive to default path `/opt/wasi-sdk`. +For testing with SGX IPFS, follow the instructions in [the documentation of SGX for WAMR](../../doc/linux_sgx.md#sgx-intel-protected-file-system). + +## Build the sample + +```bash +mkdir build +cd build +cmake .. +make +``` + +The WebAssembly application is the file located at `wasm-app/file.wasm`. + +## Run workload + +Either use [iwasm-sample](../../product-mini/platforms/linux/) for Linux, or [enclave-sample](../../product-mini/platforms/linux-sgx/enclave-sample/) for Intel SGX to run the sample, with the argument to allow the file system interaction with the current folder (`--dir=.`). + +The output with Linux and POSIX is like: + +```bash +Opening a file.. +[Test] File opening passed. +Writing to the file.. +[Test] File writing passed. +Moving the cursor to the start of the file.. +Reading from the file, up to 1000 characters.. +Text read: Hello, world! +[Test] File reading passed. +Determine whether we reach the end of the file.. +Is the end of file? 1 +[Test] End of file detection passed. +Getting the plaintext size.. +The plaintext size is 13. +[Test] Retrieving file offset passed. +Force actual write of all the cached data to the disk.. +[Test] Retrieving file offset passed. +Writing 5 characters at offset 7.. +File current offset: 13 +[Test] Writing at specified offset passed. +Reading 5 characters at offset 7.. +Text read: James +File current offset: 13 +[Test] Reading at specified offset passed. +Allocate more space to the file.. +File current offset: 13 +Moving to the end.. +File current offset: 23 +[Test] Allocation or more space passed. +Extend the file size of 10 bytes using ftruncate.. +File current offset: 23 +Moving to the end.. +File current offset: 33 +[Test] Extension of the file size passed. +Closing from the file.. +[Test] Closing file passed. +Getting the size of the file on disk.. +The file size is 33. +All the tests passed! +``` + +The output with SGX and IPFS is like: + +```bash +Opening a file.. +[Test] File opening passed. +Writing to the file.. +[Test] File writing passed. +Moving the cursor to the start of the file.. +Reading from the file, up to 1000 characters.. +Text read: Hello, world! +[Test] File reading passed. +Determine whether we reach the end of the file.. +Is the end of file? 1 +[Test] End of file detection passed. +Getting the plaintext size.. +The plaintext size is 13. +[Test] Retrieving file offset passed. +Force actual write of all the cached data to the disk.. +[Test] Retrieving file offset passed. +Writing 5 characters at offset 7.. +File current offset: 13 +[Test] Writing at specified offset passed. +Reading 5 characters at offset 7.. +Text read: James +File current offset: 13 +[Test] Reading at specified offset passed. +Allocate more space to the file.. +File current offset: 23 +Moving to the end.. +File current offset: 23 +[Test] Allocation or more space passed. +Extend the file size of 10 bytes using ftruncate.. +File current offset: 23 +Moving to the end.. +File current offset: 33 +[Test] Extension of the file size passed. +Closing from the file.. +[Test] Closing file passed. +Getting the size of the file on disk.. +The file size is 4096. +All the tests passed! +``` + +For SGX IPFS, refer to [SGX Intel Protected File System](../../doc/linux_sgx.md#sgx-intel-protected-file-system) for more details. diff --git a/samples/file/src/CMakeLists.txt b/samples/file/src/CMakeLists.txt new file mode 100644 index 00000000..43936bb0 --- /dev/null +++ b/samples/file/src/CMakeLists.txt @@ -0,0 +1,87 @@ +# Copyright (C) 2022 Intel Corporation. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +cmake_minimum_required (VERSION 3.0) + +if (NOT WAMR_BUILD_PLATFORM STREQUAL "windows") + project (iwasm) +else() + project (iwasm C ASM) + enable_language (ASM_MASM) +endif() + +################ runtime settings ################ +string (TOLOWER ${CMAKE_HOST_SYSTEM_NAME} WAMR_BUILD_PLATFORM) +if (APPLE) + add_definitions(-DBH_PLATFORM_DARWIN) +endif () + +# Reset default linker flags +set (CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") +set (CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "") + +# WAMR features switch + +# Set WAMR_BUILD_TARGET, currently values supported: +# "X86_64", "AMD_64", "X86_32", "AARCH64[sub]", "ARM[sub]", "THUMB[sub]", +# "MIPS", "XTENSA", "RISCV64[sub]", "RISCV32[sub]" +if (NOT DEFINED WAMR_BUILD_TARGET) + if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm64|aarch64)") + set (WAMR_BUILD_TARGET "AARCH64") + elseif (CMAKE_SYSTEM_PROCESSOR STREQUAL "riscv64") + set (WAMR_BUILD_TARGET "RISCV64") + elseif (CMAKE_SIZEOF_VOID_P EQUAL 8) + # Build as X86_64 by default in 64-bit platform + set (WAMR_BUILD_TARGET "X86_64") + elseif (CMAKE_SIZEOF_VOID_P EQUAL 4) + # Build as X86_32 by default in 32-bit platform + set (WAMR_BUILD_TARGET "X86_32") + else () + message(SEND_ERROR "Unsupported build target platform!") + endif () +endif () + +if (NOT CMAKE_BUILD_TYPE) + set (CMAKE_BUILD_TYPE Release) +endif () + +set (WAMR_BUILD_INTERP 1) +set (WAMR_BUILD_AOT 1) +set (WAMR_BUILD_JIT 0) +set (WAMR_BUILD_LIBC_BUILTIN 1) + +if (NOT MSVC) + set (WAMR_BUILD_LIBC_WASI 1) +endif () + +if (NOT MSVC) + # linker flags + set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pie -fPIE") + if (NOT (CMAKE_C_COMPILER MATCHES ".*clang.*" OR CMAKE_C_COMPILER_ID MATCHES ".*Clang")) + set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections") + endif () + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wformat -Wformat-security") + if (WAMR_BUILD_TARGET MATCHES "X86_.*" OR WAMR_BUILD_TARGET STREQUAL "AMD_64") + if (NOT (CMAKE_C_COMPILER MATCHES ".*clang.*" OR CMAKE_C_COMPILER_ID MATCHES ".*Clang")) + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mindirect-branch-register") + endif () + endif () +endif () + +# build out vmlib +set (WAMR_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../../..) +include (${WAMR_ROOT_DIR}/build-scripts/runtime_lib.cmake) + +add_library(vmlib ${WAMR_RUNTIME_LIB_SOURCE}) + +################ application related ################ +include_directories(${CMAKE_CURRENT_LIST_DIR}) +include (${SHARED_DIR}/utils/uncommon/shared_uncommon.cmake) + +add_executable (iwasm main.c ${UNCOMMON_SHARED_SOURCE}) + +if (APPLE) + target_link_libraries (iwasm vmlib -lm -ldl -lpthread) +else () + target_link_libraries (iwasm vmlib -lm -ldl -lpthread -lrt) +endif () diff --git a/samples/file/src/main.c b/samples/file/src/main.c new file mode 100644 index 00000000..de5eb291 --- /dev/null +++ b/samples/file/src/main.c @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2022 Intel Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include "wasm_export.h" +#include "bh_read_file.h" + +void +print_usage(void) +{ + fprintf(stdout, "Required arguments:\r\n"); + fprintf(stdout, " -f [path of wasm file] \n"); + fprintf(stdout, " -d [path of host directory] \n"); +} + +int +main(int argc, char *argv_main[]) +{ + static char global_heap_buf[512 * 1024]; + char *buffer, error_buf[128]; + const char *wasm_path = NULL, *wasi_dir = NULL; + int opt; + + wasm_module_t module = NULL; + wasm_module_inst_t module_inst = NULL; + wasm_exec_env_t exec_env = NULL; + uint32 buf_size, stack_size = 8092, heap_size = 8092; + uint32_t wasm_buffer = 0; + + RuntimeInitArgs init_args; + memset(&init_args, 0, sizeof(RuntimeInitArgs)); + + while ((opt = getopt(argc, argv_main, "hf:d:")) != -1) { + switch (opt) { + case 'f': + wasm_path = optarg; + break; + case 'd': + wasi_dir = optarg; + break; + case 'h': + print_usage(); + return 0; + case '?': + print_usage(); + return 0; + } + } + if (wasm_path == NULL || wasi_dir == NULL) { + print_usage(); + return 0; + } + + init_args.mem_alloc_type = Alloc_With_Pool; + init_args.mem_alloc_option.pool.heap_buf = global_heap_buf; + init_args.mem_alloc_option.pool.heap_size = sizeof(global_heap_buf); + + if (!wasm_runtime_full_init(&init_args)) { + printf("Init runtime environment failed.\n"); + return -1; + } + + buffer = bh_read_file_to_buffer(wasm_path, &buf_size); + + if (!buffer) { + printf("Open wasm app file [%s] failed.\n", wasm_path); + goto fail; + } + + module = wasm_runtime_load(buffer, buf_size, error_buf, sizeof(error_buf)); + if (!module) { + printf("Load wasm module failed. error: %s\n", error_buf); + goto fail; + } + + wasm_runtime_set_wasi_args_ex(module, &wasi_dir, 1, NULL, 0, NULL, 0, NULL, + 0, 0, 1, 2); + + module_inst = wasm_runtime_instantiate(module, stack_size, heap_size, + error_buf, sizeof(error_buf)); + + if (!module_inst) { + printf("Instantiate wasm module failed. error: %s\n", error_buf); + goto fail; + } + + exec_env = wasm_runtime_create_exec_env(module_inst, stack_size); + if (!exec_env) { + printf("Create wasm execution environment failed.\n"); + goto fail; + } + + if (wasm_application_execute_main(module_inst, 0, NULL)) { + printf("Main wasm function successfully finished.\n"); + } + else { + printf("call wasm function main failed. error: %s\n", + wasm_runtime_get_exception(module_inst)); + goto fail; + } + +fail: + if (exec_env) + wasm_runtime_destroy_exec_env(exec_env); + if (module_inst) { + if (wasm_buffer) + wasm_runtime_module_free(module_inst, wasm_buffer); + wasm_runtime_deinstantiate(module_inst); + } + if (module) + wasm_runtime_unload(module); + if (buffer) + BH_FREE(buffer); + wasm_runtime_destroy(); + return 0; +} diff --git a/samples/file/wasm-app/CMakeLists.txt b/samples/file/wasm-app/CMakeLists.txt new file mode 100644 index 00000000..4af87a3f --- /dev/null +++ b/samples/file/wasm-app/CMakeLists.txt @@ -0,0 +1,26 @@ +# Copyright (C) 2022 Intel Corporation. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +cmake_minimum_required(VERSION 3.0) +project(wasm-app) + +set (WAMR_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../..) + +if (APPLE) + set (HAVE_FLAG_SEARCH_PATHS_FIRST 0) + set (CMAKE_C_LINK_FLAGS "") + set (CMAKE_CXX_LINK_FLAGS "") +endif () + +set (CMAKE_SYSTEM_PROCESSOR wasm32) + +if (NOT DEFINED WASI_SDK_DIR) + set (WASI_SDK_DIR "/opt/wasi-sdk") +endif () + +set (CMAKE_C_COMPILER_TARGET "wasm32-wasi") +set (CMAKE_C_COMPILER "${WASI_SDK_DIR}/bin/clang") +set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -Wno-unused-command-line-argument") + +add_executable(file.wasm main.c) +target_link_libraries(file.wasm) diff --git a/samples/file/wasm-app/main.c b/samples/file/wasm-app/main.c new file mode 100644 index 00000000..7726b814 --- /dev/null +++ b/samples/file/wasm-app/main.c @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2019 Intel Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define PATH_TEST_FILE "test.txt" +#define FILE_TEXT "Hello, world!" +#define WORLD_OFFSET 7 +#define NAME_REPLACMENT "James" +#define NAME_REPLACMENT_LEN (sizeof(NAME_REPLACMENT) - 1) +#define ADDITIONAL_SPACE 10 + +int +main(int argc, char **argv) +{ + FILE *file; + const char *text = FILE_TEXT; + char buffer[1000]; + int ret; + + // Test: File opening (fopen) + printf("Opening a file..\n"); + file = fopen(PATH_TEST_FILE, "w+"); + if (file == NULL) { + printf("Error! errno: %d\n", errno); + } + assert(file != NULL); + printf("[Test] File opening passed.\n"); + + // Test: Writing to a file (fprintf) + printf("Writing to the file..\n"); + ret = fprintf(file, "%s", text); + assert(ret == strlen(text)); + printf("[Test] File writing passed.\n"); + + // Test: Reading from a file (fseek) + printf("Moving the cursor to the start of the file..\n"); + ret = fseek(file, 0, SEEK_SET); + assert(ret == 0); + + printf("Reading from the file, up to 1000 characters..\n"); + fread(buffer, 1, sizeof(buffer), file); + printf("Text read: %s\n", buffer); + assert(strncmp(text, buffer, strlen(text)) == 0); + printf("[Test] File reading passed.\n"); + + // Test: end of file detection (feof) + printf("Determine whether we reach the end of the file..\n"); + int is_end_of_file = feof(file); + printf("Is the end of file? %d\n", is_end_of_file); + assert(is_end_of_file == 1); + printf("[Test] End of file detection passed.\n"); + + // Test: retrieving file offset (ftell) + printf("Getting the plaintext size..\n"); + long plaintext_size = ftell(file); + printf("The plaintext size is %ld.\n", plaintext_size); + assert(plaintext_size == 13); + printf("[Test] Retrieving file offset passed.\n"); + + // Test: persist changes on disk (fflush) + printf("Force actual write of all the cached data to the disk..\n"); + ret = fflush(file); + assert(ret == 0); + printf("[Test] Retrieving file offset passed.\n"); + + // Test: writing at specified offset (pwrite) + printf("Writing 5 characters at offset %d..\n", WORLD_OFFSET); + ret = pwrite(fileno(file), NAME_REPLACMENT, NAME_REPLACMENT_LEN, + WORLD_OFFSET); + printf("File current offset: %ld\n", ftell(file)); + assert(ret == NAME_REPLACMENT_LEN); + assert(ftell(file) == strlen(FILE_TEXT)); + printf("[Test] Writing at specified offset passed.\n"); + + // Test: reading at specified offset (pread) + printf("Reading %ld characters at offset %d..\n", NAME_REPLACMENT_LEN, + WORLD_OFFSET); + buffer[NAME_REPLACMENT_LEN] = '\0'; + pread(fileno(file), buffer, NAME_REPLACMENT_LEN, WORLD_OFFSET); + printf("Text read: %s\n", buffer); + printf("File current offset: %ld\n", ftell(file)); + assert(strcmp(NAME_REPLACMENT, buffer) == 0); + assert(ftell(file) == strlen(FILE_TEXT)); + printf("[Test] Reading at specified offset passed.\n"); + + // Test: allocate more space to the file (posix_fallocate) + printf("Allocate more space to the file..\n"); + posix_fallocate(fileno(file), ftell(file), ADDITIONAL_SPACE); + printf("File current offset: %ld\n", ftell(file)); + printf("Moving to the end..\n"); + fseek(file, 0, SEEK_END); + printf("File current offset: %ld\n", ftell(file)); + assert(ftell(file) == strlen(text) + ADDITIONAL_SPACE); + printf("[Test] Allocation or more space passed.\n"); + + // Test: allocate more space to the file (ftruncate) + printf("Extend the file size of 10 bytes using ftruncate..\n"); + ftruncate(fileno(file), ftell(file) + 10); + assert(ftell(file) == strlen(text) + ADDITIONAL_SPACE); + printf("File current offset: %ld\n", ftell(file)); + printf("Moving to the end..\n"); + fseek(file, 0, SEEK_END); + printf("File current offset: %ld\n", ftell(file)); + assert(ftell(file) == strlen(text) + 2 * ADDITIONAL_SPACE); + printf("[Test] Extension of the file size passed.\n"); + + // Test: closing the file (fclose) + printf("Closing from the file..\n"); + ret = fclose(file); + assert(ret == 0); + printf("[Test] Closing file passed.\n"); + + // Display some debug information + printf("Getting the size of the file on disk..\n"); + struct stat st; + stat(PATH_TEST_FILE, &st); + printf("The file size is %lld.\n", st.st_size); + + printf("All the tests passed!\n"); + + return 0; +}