Skip to content
Commits on Source (9)
......@@ -203,6 +203,18 @@ include_directories(
)
include(GNUInstallDirs)
include(CheckSymbolExists)
check_cxx_source_compiles("
#include <fcntl.h>
#include <sys/stat.h>
int main() {
struct statx buf;
statx(AT_FDCWD, \"/foo\", AT_EMPTY_PATH, STATX_BASIC_STATS, &buf);
return 0;
}
" GLIBC_HAS_STATX)
# Global components
add_subdirectory(src/global)
......
......@@ -41,7 +41,9 @@ int gkfs_stat(const std::string& path, struct stat* buf, bool follow_links = tru
// Implementation of statx, it uses the normal stat and maps the information to the statx structure
// Follow links is true by default
#ifdef STATX_TYPE
int gkfs_statx(int dirfd, const std::string& path, int flags, unsigned int mask,struct statx* buf, bool follow_links = true );
#endif
int gkfs_statfs(struct statfs* buf);
......
......@@ -30,7 +30,9 @@ int hook_close(int fd);
int hook_stat(const char* path, struct stat* buf);
#ifdef STATX_TYPE
int hook_statx(int dirfd, const char* path, int flags, unsigned int mask,struct statx* buf);
#endif
int hook_lstat(const char* path, struct stat* buf);
......@@ -103,6 +105,8 @@ int hook_statfs(const char* path, struct statfs* buf);
int hook_fstatfs(unsigned int fd, struct statfs* buf);
int hook_fsync(unsigned int fd);
} // namespace hook
} // namespace gkfs
......
......@@ -232,7 +232,7 @@ int gkfs_stat(const string& path, struct stat* buf, bool follow_links) {
return 0;
}
#ifdef STATX_TYPE
int gkfs_statx(int dirfs, const std::string& path, int flags, unsigned int mask, struct statx* buf, bool follow_links) {
auto md = gkfs::util::get_metadata(path, follow_links);
if (!md) {
......@@ -268,6 +268,7 @@ int gkfs_statx(int dirfs, const std::string& path, int flags, unsigned int mask,
return 0;
}
#endif
int gkfs_statfs(struct statfs* buf) {
auto blk_stat = gkfs::rpc::forward_get_chunk_stat();
......
......@@ -98,7 +98,7 @@ int hook_stat(const char* path, struct stat* buf) {
return syscall_no_intercept(SYS_stat, rel_path.c_str(), buf);
}
#ifdef STATX_TYPE
int hook_statx(int dirfd, const char* path, int flags, unsigned int mask, struct ::statx* buf) {
LOG(DEBUG, "{}() called with dirfd: '{}', path: \"{}\", flags: '{}', mask: '{}', buf: '{}'",
......@@ -127,9 +127,7 @@ int hook_statx(int dirfd, const char* path, int flags, unsigned int mask, struct
return syscall_no_intercept(SYS_statx, dirfd, path, flags, mask, buf);
}
#endif
int hook_lstat(const char* path, struct stat* buf) {
......@@ -816,5 +814,19 @@ int hook_fstatfs(unsigned int fd, struct statfs* buf) {
return syscall_no_intercept(SYS_fstatfs, fd, buf);
}
/* The function should broadcast a flush message (pmem_persist i.e.) if the application needs the capabilities*/
int hook_fsync(unsigned int fd) {
LOG(DEBUG, "{}() called with fd: {}",
__func__, fd);
if (CTX->file_map()->exist(fd)) {
errno = 0;
return 0;
}
return syscall_no_intercept(SYS_fsync, fd);
}
} // namespace hook
} // namespace gkfs
......@@ -466,7 +466,7 @@ int hook(long syscall_number,
reinterpret_cast<struct stat*>(arg1));
break;
#ifdef SYS_statx
#ifdef STATX_TYPE
case SYS_statx:
*result = gkfs::hook::hook_statx(static_cast<int>(arg0),
reinterpret_cast<char*>(arg1),
......@@ -726,6 +726,10 @@ int hook(long syscall_number,
reinterpret_cast<struct statfs*>(arg1));
break;
case SYS_fsync:
*result = gkfs::hook::hook_fsync(static_cast<unsigned int>(arg0));
break;
default:
// ignore any other syscalls, i.e.: pass them on to the kernel
// (syscalls forwarded to the kernel that return are logged in
......
......@@ -16,12 +16,14 @@ gkfs_add_python_test(
SOURCE directories/test_directories.py
)
gkfs_add_python_test(
NAME test_status
PYTHON_VERSION 3.6
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/tests/integration
SOURCE status/test_status.py
)
if (GLIBC_HAS_STATX)
gkfs_add_python_test(
NAME test_status
PYTHON_VERSION 3.6
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/tests/integration
SOURCE status/test_status.py
)
endif()
gkfs_add_python_test(
NAME test_operations
......@@ -44,6 +46,15 @@ gkfs_add_python_test(
SOURCE shell/
)
gkfs_add_python_test(
NAME test_data
PYTHON_VERSION 3.6
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/tests/integration
SOURCE data/
)
if(GKFS_INSTALL_TESTS)
install(DIRECTORY harness
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/gkfs/tests/integration
......@@ -87,6 +98,14 @@ if(GKFS_INSTALL_TESTS)
)
install(DIRECTORY data
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/gkfs/tests/integration
FILES_MATCHING
REGEX ".*\\.py"
PATTERN "__pycache__" EXCLUDE
PATTERN ".pytest_cache" EXCLUDE
)
install(DIRECTORY shell
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/gkfs/tests/integration
FILES_MATCHING
......
# README
This directory contains functional tests for any data-related
functionalities in GekkoFS.
################################################################################
# Copyright 2018-2020, Barcelona Supercomputing Center (BSC), Spain #
# Copyright 2015-2020, Johannes Gutenberg Universitaet Mainz, Germany #
# #
# This software was partially supported by the #
# EC H2020 funded project NEXTGenIO (Project ID: 671951, www.nextgenio.eu). #
# #
# This software was partially supported by the #
# ADA-FS project under the SPPEXA project funded by the DFG. #
# #
# SPDX-License-Identifier: MIT #
################################################################################
import harness
from pathlib import Path
import errno
import stat
import os
import ctypes
import sh
import sys
import pytest
import string
import random
from harness.logger import logger
nonexisting = "nonexisting"
chunksize_start = 128192
chunksize_end = 2097153
step = 4096*9
def generate_random_data(size):
return ''.join([random.choice(string.ascii_letters + string.digits) for _ in range(size)])
#@pytest.mark.xfail(reason="invalid errno returned on success")
def test_data_integrity(gkfs_daemon, gkfs_client):
"""Test several data write-read commands and check that the data is correct"""
topdir = gkfs_daemon.mountdir / "top"
file_a = topdir / "file_a"
# create topdir
ret = gkfs_client.mkdir(
topdir,
stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
assert ret.retval == 0
assert ret.errno == 115 #FIXME: Should be 0!
# test statx on existing dir
ret = gkfs_client.statx(0, topdir, 0, 0)
assert ret.retval == 0
assert ret.errno == 115 #FIXME: Should be 0!
assert stat.S_ISDIR(ret.statbuf.stx_mode)
ret = gkfs_client.open(file_a,
os.O_CREAT,
stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
assert ret.retval != -1
# test statx on existing file
ret = gkfs_client.statx(0, file_a, 0, 0)
assert ret.retval == 0
assert (stat.S_ISDIR(ret.statbuf.stx_mode)==0)
assert (ret.statbuf.stx_size == 0)
# Step 1 - small sizes
# Generate writes
# Read data
# Compare buffer
for i in range (1, 512, 64):
buf = bytes(generate_random_data(i), sys.stdout.encoding)
ret = gkfs_client.write(file_a, buf, i)
assert ret.retval == i
ret = gkfs_client.statx(0, file_a, 0, 0)
assert ret.retval == 0
assert (ret.statbuf.stx_size == i)
ret = gkfs_client.read(file_a, i)
assert ret.retval== i
assert ret.buf == buf
# Step 2 - Compare bigger sizes exceeding typical chunksize
for i in range (chunksize_start, chunksize_end, step):
ret = gkfs_client.write_validate(file_a, i)
assert ret.retval == 1
return
......@@ -26,6 +26,7 @@ add_executable(gkfs.io
gkfs.io/pwritev.cpp
gkfs.io/statx.cpp
gkfs.io/lseek.cpp
gkfs.io/write_validate.cpp
)
include(FetchContent)
......
......@@ -59,10 +59,15 @@ writev_init(CLI::App& app);
void
pwritev_init(CLI::App& app);
#ifdef STATX_TYPE
void
statx_init(CLI::App& app);
#endif
void
lseek_init(CLI::App& app);
void
write_validate_init(CLI::App& app);
#endif // IO_COMMANDS_HPP
......@@ -34,8 +34,11 @@ init_commands(CLI::App& app) {
pwrite_init(app);
writev_init(app);
pwritev_init(app);
#ifdef STATX_TYPE
statx_init(app);
#endif
lseek_init(app);
write_validate_init(app);
}
......
......@@ -105,6 +105,7 @@ struct adl_serializer<struct ::timespec> {
}
};
#ifdef STATX_TYPE
// ADL specialization for struct ::statx_timestamp type
template <>
struct adl_serializer<struct ::statx_timestamp> {
......@@ -116,8 +117,7 @@ struct adl_serializer<struct ::statx_timestamp> {
};
}
};
#endif
// ADL specialization for struct ::dirent type
template <>
......@@ -162,6 +162,7 @@ struct adl_serializer<struct ::stat> {
}
};
#ifdef STATX_TYPE
// ADL specialization for struct ::statx type
template <>
struct adl_serializer<struct ::statx> {
......@@ -192,6 +193,7 @@ struct adl_serializer<struct ::statx> {
};
}
};
#endif
} // namespace nlohmann
......
......@@ -32,6 +32,7 @@ using json = nlohmann::json;
unsigned int mask, struct statx *statxbuf);
*/
#ifdef STATX_TYPE
struct statx_options {
bool verbose;
int dirfd;
......@@ -135,4 +136,4 @@ statx_init(CLI::App& app) {
statx_exec(*opts);
});
}
#endif
/*
Copyright 2018-2020, Barcelona Supercomputing Center (BSC), Spain
Copyright 2015-2020, Johannes Gutenberg Universitaet Mainz, Germany
This software was partially supported by the
EC H2020 funded project NEXTGenIO (Project ID: 671951, www.nextgenio.eu).
This software was partially supported by the
ADA-FS project under the SPPEXA project funded by the DFG.
SPDX-License-Identifier: MIT
*/
/* C++ includes */
#include <CLI/CLI.hpp>
#include <nlohmann/json.hpp>
#include <memory>
#include <fmt/format.h>
#include <commands.hpp>
#include <reflection.hpp>
#include <serialize.hpp>
#include <binary_buffer.hpp>
/* C includes */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
using json = nlohmann::json;
struct write_validate_options {
bool verbose;
std::string pathname;
::size_t count;
REFL_DECL_STRUCT(write_validate_options,
REFL_DECL_MEMBER(bool, verbose),
REFL_DECL_MEMBER(std::string, pathname),
REFL_DECL_MEMBER(::size_t, count)
);
};
struct write_validate_output {
int retval;
int errnum;
REFL_DECL_STRUCT(write_validate_output,
REFL_DECL_MEMBER(int, retval),
REFL_DECL_MEMBER(int, errnum)
);
};
void
to_json(json& record,
const write_validate_output& out) {
record = serialize(out);
}
void
write_validate_exec(const write_validate_options& opts) {
int fd = ::open(opts.pathname.c_str(), O_WRONLY);
if(fd == -1) {
if(opts.verbose) {
fmt::print("write_validate(pathname=\"{}\", count={}) = {}, errno: {} [{}]\n",
opts.pathname, opts.count, fd, errno, ::strerror(errno));
return;
}
json out = write_validate_output{fd, errno};
fmt::print("{}\n", out.dump(2));
return;
}
std::string data = "";
for (::size_t i = 0 ; i < opts.count; i++)
{
data += char((i%10)+'0');
}
io::buffer buf(data);
auto rv = ::write(fd, buf.data(), opts.count);
if(opts.verbose) {
fmt::print("write_validate(pathname=\"{}\", count={}) = {}, errno: {} [{}]\n",
opts.pathname, opts.count, rv, errno, ::strerror(errno));
return;
}
if (rv < 0 or ::size_t(rv) != opts.count) {
json out = write_validate_output{(int)rv, errno};
fmt::print("{}\n", out.dump(2));
return;
}
io::buffer bufread(opts.count);
size_t total = 0;
do{
rv = ::read(fd, bufread.data(), opts.count-total);
total += rv;
} while (rv > 0 and total < opts.count);
if (rv < 0 and total != opts.count) {
json out = write_validate_output{(int)rv, errno};
fmt::print("{}\n", out.dump(2));
return;
}
if ( memcmp(buf.data(),bufread.data(),opts.count) ) {
rv = 1;
errno = 0;
json out = write_validate_output{(int)rv, errno};
fmt::print("{}\n", out.dump(2));
return;
}
else {
rv = 2;
errno = EINVAL;
json out = write_validate_output{(int)-1, errno};
fmt::print("{}\n", out.dump(2));
}
}
void
write_validate_init(CLI::App& app) {
// Create the option and subcommand objects
auto opts = std::make_shared<write_validate_options>();
auto* cmd = app.add_subcommand(
"write_validate",
"Execute the write()-read() system call and compare the content of the buffer");
// Add options to cmd, binding them to opts
cmd->add_flag(
"-v,--verbose",
opts->verbose,
"Produce human writeable output"
);
cmd->add_option(
"pathname",
opts->pathname,
"Directory name"
)
->required()
->type_name("");
cmd->add_option(
"count",
opts->count,
"Number of bytes to test"
)
->required()
->type_name("");
cmd->callback([opts]() {
write_validate_exec(*opts);
});
}
......@@ -293,6 +293,17 @@ class LseekOutputSchema(Schema):
def make_object(self, data, **kwargs):
return namedtuple('LseekReturn', ['retval', 'errno'])(**data)
class WriteValidateOutputSchema(Schema):
"""Schema to deserialize the results of a write() execution"""
retval = fields.Integer(required=True)
errno = Errno(data_key='errnum', required=True)
@post_load
def make_object(self, data, **kwargs):
return namedtuple('WriteValidateReturn', ['retval', 'errno'])(**data)
class IOParser:
OutputSchemas = {
......@@ -312,6 +323,7 @@ class IOParser:
'stat' : StatOutputSchema(),
'statx' : StatxOutputSchema(),
'lseek' : LseekOutputSchema(),
'write_validate' : WriteValidateOutputSchema(),
}
def parse(self, command, output):
......
......@@ -53,12 +53,12 @@ def test_lseek(gkfs_daemon, gkfs_client):
assert ret.retval == 0
assert ret.errno == 115 #FIXME: Should be 0!
# test statx on existing dir
ret = gkfs_client.statx(0, topdir, 0, 0)
# test stat on existing dir
ret = gkfs_client.stat(topdir)
assert ret.retval == 0
assert ret.errno == 115 #FIXME: Should be 0!
assert stat.S_ISDIR(ret.statbuf.stx_mode)
assert stat.S_ISDIR(ret.statbuf.st_mode)
ret = gkfs_client.open(file_a,
os.O_CREAT,
......@@ -83,11 +83,11 @@ def test_lseek(gkfs_daemon, gkfs_client):
assert ret.errno == 115 #FIXME: Should be 0
# Size needs to be 0
ret = gkfs_client.statx(0, file_a, 0, 0)
ret = gkfs_client.stat(file_a)
assert ret.retval == 0
assert (stat.S_ISDIR(ret.statbuf.stx_mode)==0)
assert (ret.statbuf.stx_size == 0)
assert (stat.S_ISDIR(ret.statbuf.st_mode)==0)
assert (ret.statbuf.st_size == 0)
# next commands write at the end of the file (pos0), as the filedescriptor is not shared
......@@ -97,11 +97,13 @@ def test_lseek(gkfs_daemon, gkfs_client):
assert ret.retval == 2
# Size should be 2
ret = gkfs_client.statx(0, file_a, 0, 0)
ret = gkfs_client.stat(file_a)
assert ret.retval == 0
assert (ret.statbuf.stx_size == 2)
assert (stat.S_ISDIR(ret.statbuf.st_mode)==0)
assert (ret.statbuf.st_size == 2)
ret = gkfs_client.lseek(file_a, 0, os.SEEK_END)
assert ret.retval == 2 #FAILS
assert ret.errno == 115 #FIXME: Should be 0
......