Created
August 12, 2019 13:09
-
-
Save Joker-vD/6ed97c0573a830e168336fe0c86922a6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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