diff --git a/src/PMDK.sln b/src/PMDK.sln index 7a5569642e1dc74ed34932b89f9df93919cf27c2..64b40f5741ebe60fefbabe6e27f2ce5a803a02b6 100644 --- a/src/PMDK.sln +++ b/src/PMDK.sln @@ -275,6 +275,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "obj_many_size_allocs", "tes EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "obj_pmemlog_simple", "examples\libpmemobj\pmemlog\obj_pmemlog_simple.vcxproj", "{5DB2E259-0D19-4A89-B8EC-B2912F39924D}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "unsafe_shutdown", "examples\libpmem2\unsafe_shutdown\unsafe_shutdown.vcxproj", "{5E005D50-1C73-4E52-B295-864BB9AF7AC6}" +EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "obj_tx_user_data", "test\obj_tx_user_data\obj_tx_user_data.vcxproj", "{5E7305DB-93E6-448B-AE44-90EAF916A776}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "util_sds", "test\util_sds\util_sds.vcxproj", "{5EC35099-9777-45E8-9520-EB2EE75BDF88}" @@ -1207,6 +1209,10 @@ Global {5DB2E259-0D19-4A89-B8EC-B2912F39924D}.Debug|x64.Build.0 = Debug|x64 {5DB2E259-0D19-4A89-B8EC-B2912F39924D}.Release|x64.ActiveCfg = Release|x64 {5DB2E259-0D19-4A89-B8EC-B2912F39924D}.Release|x64.Build.0 = Release|x64 + {5E005D50-1C73-4E52-B295-864BB9AF7AC6}.Debug|x64.ActiveCfg = Debug|x64 + {5E005D50-1C73-4E52-B295-864BB9AF7AC6}.Debug|x64.Build.0 = Debug|x64 + {5E005D50-1C73-4E52-B295-864BB9AF7AC6}.Release|x64.ActiveCfg = Release|x64 + {5E005D50-1C73-4E52-B295-864BB9AF7AC6}.Release|x64.Build.0 = Release|x64 {5E7305DB-93E6-448B-AE44-90EAF916A776}.Debug|x64.ActiveCfg = Debug|x64 {5E7305DB-93E6-448B-AE44-90EAF916A776}.Debug|x64.Build.0 = Debug|x64 {5E7305DB-93E6-448B-AE44-90EAF916A776}.Release|x64.ActiveCfg = Release|x64 @@ -2017,6 +2023,7 @@ Global {5B2B9C0D-1B6D-4357-8307-6DE1EE0A41A3} = {BD6CC700-B36B-435B-BAF9-FC5AFCD766C9} {5D362DB7-D2BD-4907-AAD8-4B8627E72282} = {63C9B3F8-437D-4AD9-B32D-D04AE38C35B6} {5DB2E259-0D19-4A89-B8EC-B2912F39924D} = {F42C09CD-ABA5-4DA9-8383-5EA40FA4D763} + {5E005D50-1C73-4E52-B295-864BB9AF7AC6} = {BEA6AC7C-831D-44EF-AD61-DA65A448CC9B} {5E7305DB-93E6-448B-AE44-90EAF916A776} = {63C9B3F8-437D-4AD9-B32D-D04AE38C35B6} {5EC35099-9777-45E8-9520-EB2EE75BDF88} = {4C291EEB-3874-4724-9CC2-1335D13FF0EE} {5F2B687A-1B42-439C-AEEC-135DD22FB851} = {2F543422-4B8A-4898-BE6B-590F52B4E9D1} diff --git a/src/examples/libpmem2/Makefile b/src/examples/libpmem2/Makefile index 015d69ed84d95c70b96b537ee14e168daf9c1458..6d35cadf415ba619e36fc3ab1b967ec122a25f26 100644 --- a/src/examples/libpmem2/Makefile +++ b/src/examples/libpmem2/Makefile @@ -5,7 +5,7 @@ # examples/libpmem2/Makefile -- build the libpmem2 examples # -DIRS = advanced basic log redo map_multiple_files +DIRS = advanced basic log redo map_multiple_files unsafe_shutdown include ../Makefile.inc diff --git a/src/examples/libpmem2/unsafe_shutdown/.gitignore b/src/examples/libpmem2/unsafe_shutdown/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..5e5c1cc7975184dabc4aad91546a72a17e094f6b --- /dev/null +++ b/src/examples/libpmem2/unsafe_shutdown/.gitignore @@ -0,0 +1 @@ +unsafe_shutdown diff --git a/src/examples/libpmem2/unsafe_shutdown/Makefile b/src/examples/libpmem2/unsafe_shutdown/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..51c2b7a742ff466db6faff414453aa6a94a28fd8 --- /dev/null +++ b/src/examples/libpmem2/unsafe_shutdown/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright 2020, Intel Corporation + +# +# examples/libpmem2/basic/Makefile -- build the unsafe_shutdown example +# +PROGS = unsafe_shutdown + +LIBS = -lpmem2 + +include ../../Makefile.inc + +unsafe_shutdown: unsafe_shutdown.o diff --git a/src/examples/libpmem2/unsafe_shutdown/unsafe_shutdown.c b/src/examples/libpmem2/unsafe_shutdown/unsafe_shutdown.c new file mode 100644 index 0000000000000000000000000000000000000000..014ed0d1240f9620df011b1d173ec60804abb502 --- /dev/null +++ b/src/examples/libpmem2/unsafe_shutdown/unsafe_shutdown.c @@ -0,0 +1,674 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* Copyright 2020, Intel Corporation */ + +/* + * unsafe_shutdown.c -- unsafe shutdown example for the libpmem2 + * + * This examples demonstrates how a normal application should consume the + * deep flush and unsafe shutdown count interfaces to provide a reliable + * and recoverable access to persistent memory resident data structures. + */ + +#include <assert.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <inttypes.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <stdbool.h> + +#include <libpmem2.h> + +#ifndef _WIN32 +#include <unistd.h> +#else +#include <io.h> +#endif + +#define DEVICE_ID_LEN ((size_t)512ULL) + +struct device_info { + char id[DEVICE_ID_LEN]; + uint64_t usc; /* unsafe shutdown count */ +}; + +/* + * device_info_read -- populates device_info with data + */ +static int +device_info_read(struct device_info *di, struct pmem2_source *src) +{ + /* obtain device unsafe shutdown counter value (USC) */ + int ret = pmem2_source_device_usc(src, &di->usc); + if (ret) { + pmem2_perror("pmem2_source_device_usc"); + return ret; + } + + /* obtain a device's ID */ + size_t len = 0; + ret = pmem2_source_device_id(src, NULL, &len); + if (ret) { + pmem2_perror( + "pmem2_source_device_id failed querying device ID length"); + return ret; + } + + if (len > DEVICE_ID_LEN) { + fprintf(stderr, "the device ID is too long " + "(%zu > %zu)\n", + len, DEVICE_ID_LEN); + return 1; + } + + ret = pmem2_source_device_id(src, (char *)&di->id, &len); + if (ret) { + pmem2_perror("pmem2_source_device_id failed reading device ID"); + return ret; + } + + return ret; +} + +/* + * device_info_write -- safely writes new device info into the old location + */ +static int +device_info_write(struct device_info *di_old, const struct device_info *di_new, + struct pmem2_map *map) +{ + int ret; + + /* First, clear any leftover invalid state from the structure */ + memset(di_old, 0, sizeof(*di_old)); + ret = pmem2_deep_flush(map, di_old, sizeof(*di_old)); + if (ret) { + pmem2_perror("pmem2_deep_flush on device_info memset failed"); + return ret; + } + + /* + * Next, write, persist and deep sync the USC value. It has to be stored + * on the persistent medium before it will be validated by writing + * device ID. + */ + di_old->usc = di_new->usc; + ret = pmem2_deep_flush(map, &di_old->usc, sizeof(di_old->usc)); + if (ret) { + pmem2_perror("pmem2_deep_flush USC failed"); + return ret; + } + + /* valid device ID validates already stored USC value */ + memcpy(di_old->id, di_new->id, DEVICE_ID_LEN); + ret = pmem2_deep_flush(map, di_old->id, DEVICE_ID_LEN); + if (ret) { + pmem2_perror("pmem2_deep_flush device ID failed"); + return ret; + } + + return ret; +} + +/* + * device_info_is_initalized -- checks if the content of device info is + * initialized. + * + * This function returns false if device info was never initialized, + * initialization was interrupted or the file was moved to a different device. + * Otherwise, the function returns true. + */ +static bool +device_info_is_initalized(const struct device_info *di_old, + const struct device_info *di_new) +{ + return strncmp(di_new->id, di_old->id, DEVICE_ID_LEN) == 0; +} + +/* + * device_info_is_consistent -- checks if the device info indicates possible + * silent data corruption. + * + * This function returns false if the unsafe shutdown count of the device + * was incremented since the last open. + * Otherwise, the function returns true. + */ +static bool +device_info_is_consistent(const struct device_info *di_old, + const struct device_info *di_new) +{ + return di_old->usc == di_new->usc; +} + +#define POOL_SIGNATURE ("SHUTDOWN") /* 8 bytes */ +#define POOL_SIGNATURE_LEN (sizeof(POOL_SIGNATURE)) + +#define POOL_FLAG_IN_USE ((uint64_t)(1 << 0)) +#define POOL_USC_SUPPORTED ((uint64_t)(1 << 1)) + +#define POOL_VALID_FLAGS (POOL_FLAG_IN_USE | POOL_USC_SUPPORTED) + +enum pool_state { + /* + * The pool state cannot be determined because of errors during + * retrieval of device information. + */ + POOL_STATE_INDETERMINATE, + + /* + * The pool is internally consistent and was closed cleanly. + * Application can assume that no custom recovery is needed. + */ + POOL_STATE_OK, + + /* + * The pool is internally consistent, but it was not closed cleanly. + * Application must perform consistency checking and custom recovery + * on user data. + */ + POOL_STATE_OK_BUT_INTERRUPTED, + + /* + * The pool can contain invalid data as a result of hardware failure. + * Reading the pool is unsafe. + */ + POOL_STATE_CORRUPTED, +}; + +struct pool_header { + uint8_t signature[POOL_SIGNATURE_LEN]; + uint64_t flags; + uint64_t size; + struct device_info info; +}; + +struct pool_data { + struct pool_header header; + char usable_data[]; +}; + +struct pool { + struct pool_data *data; + struct pmem2_source *src; + struct pmem2_map *map; +}; + +/* + * pool_new -- creates a new runtime pool instance + */ +static struct pool * +pool_new(int fd) +{ + struct pool *pool = malloc(sizeof(struct pool)); + if (pool == NULL) + goto err_malloc; + + int ret = pmem2_source_from_fd(&pool->src, fd); + if (ret) { + pmem2_perror("pmem2_source_from_fd"); + goto err_source; + } + + struct pmem2_config *cfg; + /* prepare configuration */ + ret = pmem2_config_new(&cfg); + if (ret) { + pmem2_perror("pmem2_config_new"); + goto err_config; + } + + ret = pmem2_config_set_required_store_granularity(cfg, + PMEM2_GRANULARITY_PAGE); + if (ret) { + pmem2_perror("pmem2_config_set_required_store_granularity"); + goto err_config_settings; + } + + /* prepare the mapping */ + ret = pmem2_map_new(&pool->map, cfg, pool->src); + if (ret) { + pmem2_perror("pmem2_map"); + goto err_map; + } + + pool->data = pmem2_map_get_address(pool->map); + + pmem2_config_delete(&cfg); + + return pool; + +err_map: +err_config_settings: + pmem2_config_delete(&cfg); +err_config: + pmem2_source_delete(&pool->src); +err_source: + free(pool); +err_malloc: + return NULL; +} + +/* + * pool_delete -- deletes a runtime pool instance + */ +static void +pool_delete(struct pool *pool) +{ + pmem2_map_delete(&pool->map); + pmem2_source_delete(&pool->src); + free(pool); +} + +/* + * pool_set_flag -- safely sets the flag in the pool's header + */ +static int +pool_set_flag(struct pool *pool, uint64_t flag) +{ + uint64_t *flagsp = &pool->data->header.flags; + *flagsp |= flag; + + int ret = pmem2_deep_flush(pool->map, flagsp, sizeof(*flagsp)); + + return ret; +} + +/* + * pool_clear_flag -- safely clears the flag in the pool's header + */ +static int +pool_clear_flag(struct pool *pool, uint64_t flag) +{ + uint64_t *flagsp = &pool->data->header.flags; + *flagsp &= ~flag; + + int ret = pmem2_deep_flush(pool->map, flagsp, sizeof(*flagsp)); + + return ret; +} + +/* + * pool_header_is_initialized -- checks if all the pool header data is correct + */ +static bool +pool_header_is_initialized(struct pool *pool) +{ + if (memcmp(POOL_SIGNATURE, pool->data->header.signature, + POOL_SIGNATURE_LEN) != 0) + return false; + + if (pool->data->header.flags & ~POOL_VALID_FLAGS) + return false; + + if (pool->data->header.size != pmem2_map_get_size(pool->map)) + return false; + + return true; +} + +/* + * pool_header_initialize -- safely initializes the pool header data + */ +static int +pool_header_initialize(struct pool *pool) +{ + struct pool_header *hdrp = &pool->data->header; + memset(hdrp, 0, sizeof(*hdrp)); + int ret = pmem2_deep_flush(pool->map, hdrp, sizeof(*hdrp)); + if (ret) { + pmem2_perror("pmem2_deep_flush on device_info memset failed"); + return ret; + } + + pool->data->header.size = pmem2_map_get_size(pool->map); + pool->data->header.flags = 0; + ret = pmem2_deep_flush(pool->map, hdrp, sizeof(*hdrp)); + if (ret) { + pmem2_perror("pmem2_deep_flush on device_info memset failed"); + return ret; + } + + memcpy(hdrp->signature, POOL_SIGNATURE, POOL_SIGNATURE_LEN); + ret = pmem2_deep_flush(pool->map, hdrp, sizeof(*hdrp)); + if (ret) { + pmem2_perror("pmem2_deep_flush on device_info memset failed"); + return ret; + } + + return 0; +} + +/* + * pool_state_check_and_maybe_init -- verifies various invariants about the pool + * and returns its state. + * The pool is initialized if needed. + */ +static enum pool_state +pool_state_check_and_maybe_init(struct pool *pool) +{ + if (pool_header_is_initialized(pool) != 0) { + if (pool_header_initialize(pool) != 0) + return POOL_STATE_INDETERMINATE; + } + + struct device_info di_new; + bool in_use = pool->data->header.flags & POOL_FLAG_IN_USE; + enum pool_state state = in_use ? + POOL_STATE_OK_BUT_INTERRUPTED : POOL_STATE_OK; + + int ret; + if ((ret = device_info_read(&di_new, pool->src)) != 0) { + if (ret == PMEM2_E_NOSUPP) { + if (pool->data->header.flags & POOL_USC_SUPPORTED) + return POOL_STATE_INDETERMINATE; + else + return state; + } else { + return POOL_STATE_INDETERMINATE; + } + } + + struct device_info *di_old = &pool->data->header.info; + + bool initialized = device_info_is_initalized(di_old, &di_new); + + if (initialized) { + /* + * Pool is corrupted only if it wasn't closed cleanly and + * the device info indicates that device info is inconsistent. + */ + if (device_info_is_consistent(di_old, &di_new)) + return state; + else if (in_use) + return POOL_STATE_CORRUPTED; + + /* + * If device info indicates inconsistency, but the pool was + * not in use, we just need to reinitialize the device info. + */ + initialized = false; + } + + if (!initialized) { + if (device_info_write(di_old, &di_new, pool->map) != 0) + return POOL_STATE_INDETERMINATE; + if (pool_set_flag(pool, POOL_USC_SUPPORTED) != 0) + return POOL_STATE_INDETERMINATE; + } + + return state; +} + +/* + * pool_arm -- sets the in use flag, arming the pool state detection mechanism + */ +static int +pool_arm(struct pool *pool) +{ + return pool_set_flag(pool, POOL_FLAG_IN_USE); +} + +/* + * pool_disarm -- tries to deep flush the entire pool and clears the in use flag + */ +static int +pool_disarm(struct pool *pool) +{ + int ret = pmem2_deep_flush(pool->map, pool->data, + pmem2_map_get_size(pool->map)); + if (ret != 0) + return ret; + + return pool_clear_flag(pool, POOL_FLAG_IN_USE); +} + +/* + * pool_access_data -- verifies the pool state and, if possible, accesses + * the pool for reading and writing + */ +static enum pool_state +pool_access_data(struct pool *pool, void **data, size_t *size) +{ + enum pool_state state = pool_state_check_and_maybe_init(pool); + + if (state == POOL_STATE_OK || state == POOL_STATE_OK_BUT_INTERRUPTED) { + *data = &pool->data->usable_data; + *size = pmem2_map_get_size(pool->map) - + sizeof(struct pool_header); + + if (pool_arm(pool) != 0) + return POOL_STATE_INDETERMINATE; + } + + return state; +} + +/* + * pool_access_drop -- drops the access for the pool and marks it as not in use + */ +static void +pool_access_drop(struct pool *pool) +{ + if (pool_disarm(pool) != 0) { + fprintf(stderr, + "Failed to drop access to pool which might cause inconsistent state during next open.\n"); + } +} + +/* + * pool_get_memcpy -- retrieves the pool's memcpy function + */ +static pmem2_memcpy_fn +pool_get_memcpy(struct pool *pool) +{ + assert(pool->data->header.flags & POOL_FLAG_IN_USE); + return pmem2_get_memcpy_fn(pool->map); +} + +/* + * pool_get_memset -- retrieves the pool's memset function + */ +static pmem2_memset_fn +pool_get_memset(struct pool *pool) +{ + assert(pool->data->header.flags & POOL_FLAG_IN_USE); + return pmem2_get_memset_fn(pool->map); +} + +/* + * pool_get_persist -- retrieves the pool's persist function + */ +static pmem2_persist_fn +pool_get_persist(struct pool *pool) +{ + assert(pool->data->header.flags & POOL_FLAG_IN_USE); + return pmem2_get_persist_fn(pool->map); +} + +#define USAGE_STR "usage: %s <command> <file> [<arg>]\n" \ + "Where available commands are:\n" \ + "\tread - print the file contents\n" \ + "\twrite - store <arg> into the file\n" + +/* + * If the state of the pool is ok, the invariant on this data structure is that + * the persistent variable is set to 1 only if the string has valid content. + * If the persistent variable is set to 0, the string should be empty. + * + * If the pool state is ok but interrupted, the string variable can contain + * garbage if persistent variable is set to 0. To restore the previously + * described invariant, the recovery method needs to zero-out the string if + * the persistent variable is 0. + * + * If the pool state is corrupted, the previous invariants don't hold, and + * we cannot assume that the string is valid if persistent variable is set to 1. + * The only correct course of action is to either reinitialize the data or + * restore from backup. + */ +struct user_data { + int persistent; /* flag indicates whether buf contains a valid string */ + char buf[]; /* user string */ +}; + +/* + * user_data_recovery -- this function restores the invariant that if + * persistent variable is 0, then the string is empty. + */ +static void +user_data_recovery(struct pool *pool, + struct user_data *data, size_t usable_size) +{ + size_t max_str_size = usable_size - sizeof(data->persistent); + + if (!data->persistent) + pool_get_memset(pool)(data->buf, 0, max_str_size, 0); +} + +/* + * user_data_read -- this function prints out the string. + * + * Inside of this method, we can be sure that our invariants hold. + */ +static int +user_data_read(struct pool *pool, + struct user_data *data, size_t usable_size, void *arg) +{ + if (data->persistent) + printf("%s\n", data->buf); + else + printf("empty string\n"); + + return 0; +} + +/* + * user_data_write -- persistently writes a string to a variable. + * + * Inside of this method, we can be sure that our invariants hold. + */ +static int +user_data_write(struct pool *pool, + struct user_data *data, size_t usable_size, void *arg) +{ + if (arg == NULL) { + fprintf(stderr, "expected string input argument\n"); + return 1; + } + char *str = arg; + + size_t max_str_size = usable_size - sizeof(data->persistent); + + /* + * To make sure our invariants hold, we first write the string and then + * set the persistent variable to 1. + */ + size_t str_size = strlen(str) + 1; + if (str_size <= max_str_size) { + pool_get_memcpy(pool)(data->buf, str, str_size, 0); + data->persistent = 1; + pool_get_persist(pool)(&data->persistent, + sizeof(data->persistent)); + } + + return 0; +} + +enum user_data_operation_type { + USER_OP_READ, + USER_OP_WRITE, + + MAX_USER_OP, +}; + +typedef int user_data_operation_fn(struct pool *pool, + struct user_data *data, size_t usable_size, void *arg); + +static struct user_data_operation { + const char *name; + user_data_operation_fn *fn; +} user_data_operations[MAX_USER_OP] = { + [USER_OP_READ] = { .name = "read", .fn = user_data_read }, + [USER_OP_WRITE] = { .name = "write", .fn = user_data_write }, +}; + +/* + * user_data_operation_parse -- parses the user data operation + */ +static struct user_data_operation * +user_data_operation_parse(char *op) +{ + for (int i = 0; i < MAX_USER_OP; ++i) { + if (strcmp(user_data_operations[i].name, op) == 0) + return &user_data_operations[i]; + } + return NULL; +} + +int +main(int argc, char *argv[]) +{ + /* parse and validate arguments */ + if (argc < 3) { + fprintf(stderr, USAGE_STR, argv[0]); + return 1; + } + + char *file = argv[2]; + + struct user_data_operation *op = user_data_operation_parse(argv[1]); + if (op == NULL) { + fprintf(stderr, USAGE_STR, argv[0]); + return 1; + } + + /* open file and prepare source */ + int fd = open(file, O_RDWR); + if (fd < 0) { + perror(file); + return 1; + } + + struct pool *pool = pool_new(fd); + if (pool == NULL) { + fprintf(stderr, "unable open a pool from %s", file); + return 1; + } + + struct user_data *data; + size_t size; + enum pool_state state = pool_access_data(pool, (void **)&data, &size); + + int ret = 1; + switch (state) { + case POOL_STATE_INDETERMINATE: + fprintf(stderr, + "Unable to determine the state of the pool %s. Accessing the pool might be unsafe.\n", + file); + goto exit; + case POOL_STATE_CORRUPTED: + fprintf(stderr, + "The pool %s might be corrupted, silent data corruption is possible. Accessing the pool is unsafe.\n", + file); + goto exit; + case POOL_STATE_OK_BUT_INTERRUPTED: + fprintf(stderr, + "The pool %s was not closed cleanly. User data recovery is required.\n", + file); + user_data_recovery(pool, data, size); + break; + case POOL_STATE_OK: + break; + } + + ret = op->fn(pool, data, size, argv[3]); + + pool_access_drop(pool); + +exit: + pool_delete(pool); + + close(fd); + + return ret; +} diff --git a/src/examples/libpmem2/unsafe_shutdown/unsafe_shutdown.vcxproj b/src/examples/libpmem2/unsafe_shutdown/unsafe_shutdown.vcxproj new file mode 100644 index 0000000000000000000000000000000000000000..1fb16c2e61063a58fa1d01fa0b338a6dba76037b --- /dev/null +++ b/src/examples/libpmem2/unsafe_shutdown/unsafe_shutdown.vcxproj @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + </ItemGroup> + <ItemGroup> + <ClCompile Include="unsafe_shutdown.c" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\..\libpmem2\libpmem2.vcxproj"> + <Project>{f596c36c-5c96-4f08-b420-8908af500954}</Project> + </ProjectReference> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectGuid>{5E005D50-1C73-4E52-B295-864BB9AF7AC6}</ProjectGuid> + <RootNamespace>pmem2</RootNamespace> + <WindowsTargetPlatformVersion>10.0.17134.0</WindowsTargetPlatformVersion> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>true</UseDebugLibraries> + <PlatformToolset>v140</PlatformToolset> + </PropertyGroup> + <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <PlatformToolset>v140</PlatformToolset> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <Import Project="..\..\Examples_$(Configuration).props" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <Import Project="..\..\Examples_$(Configuration).props" /> + </ImportGroup> + <ItemDefinitionGroup> + <Manifest> + <AdditionalManifestFiles>..\..\..\LongPath.manifest</AdditionalManifestFiles> + </Manifest> + <Link> + <AdditionalDependencies>libpmem2.lib;%(AdditionalDependencies)</AdditionalDependencies> + </Link> + </ItemDefinitionGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> +</Project> \ No newline at end of file diff --git a/src/examples/libpmem2/unsafe_shutdown/unsafe_shutdown.vcxproj.filters b/src/examples/libpmem2/unsafe_shutdown/unsafe_shutdown.vcxproj.filters new file mode 100644 index 0000000000000000000000000000000000000000..029c36b5e8dd72d7f4441cc8d394053e612f87f9 --- /dev/null +++ b/src/examples/libpmem2/unsafe_shutdown/unsafe_shutdown.vcxproj.filters @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <Filter Include="Source Files"> + <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier> + <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions> + </Filter> + </ItemGroup> + <ItemGroup> + <ClCompile Include="unsafe_shutdown.c"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> +</Project> \ No newline at end of file diff --git a/src/test/ex_libpmem2/TESTS.py b/src/test/ex_libpmem2/TESTS.py index 2f2473b55126f24ea79d9fe26ee6faea45f76978..bd805f091a6df48e09a4a02b79420538adfd5387 100755 --- a/src/test/ex_libpmem2/TESTS.py +++ b/src/test/ex_libpmem2/TESTS.py @@ -71,3 +71,12 @@ class TEST4(EX_LIBPMEM2): args.append(file_path) ctx.exec(example_path, *args, stdout_file='out4.log') + + +class TEST5(EX_LIBPMEM2): + + def run(self, ctx): + example_path = futils.get_example_path(ctx, 'pmem2', 'unsafe_shutdown') + file_path = ctx.create_holey_file(self.file_size, 'testfile0') + ctx.exec(example_path, "write", file_path, "foobar") + ctx.exec(example_path, "read", file_path, stdout_file='out5.log') diff --git a/src/test/ex_libpmem2/out5.log.match b/src/test/ex_libpmem2/out5.log.match new file mode 100644 index 0000000000000000000000000000000000000000..bd954f831e4a965a7701c589794c419843be9998 --- /dev/null +++ b/src/test/ex_libpmem2/out5.log.match @@ -0,0 +1,3 @@ +$(OPT)pmem2_source_device_usc: Unsafe shutdown count is not supported for this source +$(OPT)pmem2_source_device_usc: Getting unsafe shutdown count is not supported on this system +foobar