Skip to content

Instantly share code, notes, and snippets.

@Joker-vD
Created August 12, 2019 13:09
Show Gist options
  • Save Joker-vD/6ed97c0573a830e168336fe0c86922a6 to your computer and use it in GitHub Desktop.
Save Joker-vD/6ed97c0573a830e168336fe0c86922a6 to your computer and use it in GitHub Desktop.
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstdio>
#include <cstring>
#include <stdexcept>
#include <string>
void usage(const char* program, int exit_code) {
fprintf(exit_code ? stderr : stdout,
"USAGE: %s (r | w | a) <file>\n"
" %s --help\n",
program, program);
exit(exit_code);
}
enum class open_mode {
read,
write,
append
};
int flags_from_open_mode(open_mode mode) {
switch (mode) {
case open_mode::read: return O_RDONLY;
case open_mode::write: return O_WRONLY | O_TRUNC | O_CREAT;
case open_mode::append: return O_WRONLY | O_APPEND | O_CREAT;
default: throw std::runtime_error("Unknown open_mode value " + std::to_string((int)mode));
}
}
struct options {
open_mode access;
std::string filename;
};
options parse_options(int argc, char** argv) {
if (argc < 1) { usage("async_file_rw", 1); }
if (argc == 2 && std::string(argv[1]) == "--help") { usage(argv[0], 0); }
if (argc != 3) { usage(argv[0], 1); }
options result;
result.filename = argv[2];
std::string raw_access = argv[1];
if (raw_access == "r" || raw_access == "R") { result.access = open_mode::read; }
else if (raw_access == "w" || raw_access == "W") { result.access = open_mode::write; }
else if (raw_access == "a" || raw_access == "A") { result.access = open_mode::append; }
else { usage(argv[0], 1); }
return result;
}
void string_colon_appender(std::string& accumulator, const std::string& s) {
if (!s.empty()) {
accumulator += s;
accumulator += ": ";
}
}
std::string build_error_string(const std::string& call, const std::string& context, const std::string& error_info) {
std::string result;
string_colon_appender(result, call);
string_colon_appender(result, context);
result += error_info;
return result;
}
class os_error: public std::runtime_error {
public:
const int error;
os_error(int error, const std::string& call, const std::string& context)
: std::runtime_error(build_error_string(call, context, strerror(error)))
, error(error)
{ }
};
struct fd {
int value;
explicit fd(int value)
: value(value)
{ }
};
std::string to_string(fd file) {
return "fd(" + std::to_string(file.value) + ")";
}
fd checked_open(const std::string& path, int flags, mode_t mode) {
int result;
while ((result = open(path.c_str(), flags, mode)) == -1 && errno == EINTR);
if (result == -1) {
throw os_error(errno, "open", path);
}
return fd(result);
}
ssize_t checked_read(fd file, void* buf, size_t count) {
ssize_t result;
while ((result = read(file.value, buf, count)) == -1 && errno == EINTR);
if (result == -1) {
throw os_error(errno, "read", to_string(file));
}
return result;
}
ssize_t checked_write(fd file, const void* buf, size_t count) {
ssize_t result;
while ((result = write(file.value, buf, count)) == -1 && errno == EINTR);
if (result == -1) {
throw os_error(errno, "write", to_string(file));
}
if (count != 0 && result == 0) {
throw std::runtime_error(build_error_string("write", to_string(file), "Zero bytes written"));
}
return result;
}
void checked_fsync(fd file) {
int result;
while ((result = fsync(file.value)) == -1 && errno == EINTR);
if (result == -1) {
throw os_error(errno, "fsync", to_string(file));
}
}
void fsync_ignore_einval(fd file) {
try {
checked_fsync(file);
}
catch (os_error& e) {
if (e.error != EINVAL) {
throw;
}
}
}
void work(const options& options) {
fd file = checked_open(options.filename, flags_from_open_mode(options.access), 0666);
const size_t BUFFSIZE = 8 * 1024;
char buffer[BUFFSIZE];
fd r = (options.access == open_mode::read) ? file : fd(0);
fd w = (options.access == open_mode::read) ? fd(1) : file;
struct call_fsync_on_exit {
fd file;
call_fsync_on_exit(fd file) : file(file) { }
~call_fsync_on_exit() { fsync_ignore_einval(file); }
} call_fsync_on_exit(w);
while (true) {
ssize_t read_result = read_result = checked_read(r, buffer, BUFFSIZE);
if (read_result == 0) {
return;
}
char* write_point = buffer;
while (read_result > 0) {
ssize_t write_result = checked_write(w, write_point, (size_t)read_result);
read_result -= write_result;
write_point += write_result;
}
}
}
int main(int argc, char** argv) {
options options = parse_options(argc, argv);
try {
work(options);
}
catch (std::exception& e) {
fprintf(stderr, "%s\n", e.what());
return 1;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment