diff --git a/Makefile b/Makefile index 13782bb..28c5a29 100644 --- a/Makefile +++ b/Makefile @@ -6,11 +6,12 @@ PREFIX = /usr/local sources = src/enchive.c src/chacha.c src/curve25519-donna.c src/sha256.c objects = $(sources:.c=.o) -headers = config.h src/docs.h src/chacha.h src/sha256.h src/optparse.h +headers = config.h src/docs.h src/chacha.h src/sha256.h src/optparse.h \ + src/w32-compat.h enchive: $(objects) $(CC) $(LDFLAGS) -o $@ $(objects) $(LDLIBS) -src/enchive.o: src/enchive.c config.h src/docs.h +src/enchive.o: src/enchive.c config.h src/docs.h src/w32-compat.h src/chacha.o: src/chacha.c config.h src/curve25519-donna.o: src/curve25519-donna.c config.h src/sha256.o: src/sha256.c config.h diff --git a/src/enchive.c b/src/enchive.c index 159e756..11cb5b5 100644 --- a/src/enchive.c +++ b/src/enchive.c @@ -5,6 +5,18 @@ #include #include +#ifdef _WIN32 +#include "w32-compat.h" +#else +#include +#include +#include +#include +#include +#include +#include +#endif + #include "docs.h" #include "sha256.h" #include "chacha.h" @@ -24,55 +36,9 @@ static int global_agent_timeout = 0; static const char enchive_suffix[] = ".enchive"; -static struct { - char *name; - FILE *file; -} cleanup[2]; - -/** - * Register a file for deletion should fatal() be called. - */ -static void -cleanup_register(FILE *file, char *name) -{ - if (file) { - unsigned i; - for (i = 0; i < sizeof(cleanup) / sizeof(*cleanup); i++) { - if (!cleanup[i].name) { - cleanup[i].name = name; - cleanup[i].file = file; - return; - } - } - } - abort(); -} - -/** - * Update cleanup registry to indicate FILE has been closed. - */ -static void -cleanup_closed(FILE *file) -{ - unsigned i; - for (i = 0; i < sizeof(cleanup) / sizeof(*cleanup); i++) { - if (file == cleanup[i].file) - cleanup[i].file = 0; - return; - } - abort(); -} - -/** - * Free resources held by the cleanup registry. - */ -static void -cleanup_free(void) -{ - size_t i; - for (i = 0; i < sizeof(cleanup) / sizeof(*cleanup); i++) - free(cleanup[i].name); -} +static const char *cleanup_pubfile; +static const char *cleanup_secfile; +static const char *cleanup_outfile; /** * Print a message, cleanup, and exit the program with a failure code. @@ -80,18 +46,17 @@ cleanup_free(void) static void fatal(const char *fmt, ...) { - unsigned i; va_list ap; va_start(ap, fmt); fprintf(stderr, "enchive: "); vfprintf(stderr, fmt, ap); fputc('\n', stderr); - for (i = 0; i < sizeof(cleanup) / sizeof(*cleanup); i++) { - if (cleanup[i].file) - fclose(cleanup[i].file); - if (cleanup[i].name) - remove(cleanup[i].name); - } + if (cleanup_pubfile) + remove(cleanup_pubfile); + if (cleanup_secfile) + remove(cleanup_secfile); + if (cleanup_outfile) + remove(cleanup_outfile); va_end(ap); exit(EXIT_FAILURE); } @@ -164,6 +129,21 @@ joinstr(int n, ...) return str; } +static ssize_t +full_read(int fd, void *buf, size_t len) +{ + size_t z = 0; + while (z < len) { + ssize_t r = read(fd, (char *)buf + z, len - z); + if (r == -1) + return -1; + if (r == 0) + break; + z += r; + } + return z; +} + /** * Read the protection key from a key agent identified by its IV. */ @@ -175,11 +155,6 @@ static int agent_read(u8 *key, const u8 *id); static int agent_run(const u8 *key, const u8 *id); #if ENCHIVE_OPTION_AGENT -#include -#include -#include -#include -#include /** * Fill ADDR with a unix domain socket name for the agent. @@ -299,6 +274,7 @@ agent_read(u8 *key, const u8 *id) { (void)key; (void)id; + (void)warning; return 0; } @@ -408,118 +384,46 @@ storage_directory(char *file) /** * Read a passphrase directly from the keyboard without echo. */ -static void get_passphrase(char *buf, size_t len, char *prompt); - -/** - * Read a passphrase without any fanfare (fallback). - */ -static void -get_passphrase_dumb(char *buf, size_t len, char *prompt) -{ - size_t passlen; - warning("reading passphrase from stdin with echo"); - fputs(prompt, stderr); - fflush(stderr); - if (!fgets(buf, len, stdin)) - fatal("could not read passphrase"); - passlen = strlen(buf); - if (buf[passlen - 1] < ' ') - buf[passlen - 1] = 0; -} - -#if defined(__unix__) || defined(__APPLE__) -#include -#include -#include - static void get_passphrase(char *buf, size_t len, char *prompt) { - int tty = open("/dev/tty", O_RDWR); - if (tty == -1) { - get_passphrase_dumb(buf, len, prompt); + struct termios old, new; + ssize_t z; + int tty; + + tty = open("/dev/tty", O_RDWR); + if (tty == -1) + fatal("could not open /dev/tty -- %s", strerror(errno)); + + if (write(tty, prompt, strlen(prompt)) != (ssize_t)strlen(prompt)) + fatal("could not write to /dev/tty -- %s", strerror(errno)); + + if (tcgetattr(tty, &old) == -1) + fatal("tcgetattr() -- %s", strerror(errno)); + new = old; + new.c_lflag &= ~ECHO; + if (tcsetattr(tty, TCSANOW, &new) == -1) + fatal("tcsetattr() -- %s", strerror(errno)); + + z = read(tty, buf, len); + (void)tcsetattr(tty, TCSANOW, &old); /* don't care if this fails */ + (void)write(tty, "\n", 1); /* don't care if this fails */ + + if (z == -1) { + fatal("error readin /dev/tty -- %s", strerror(errno)); + } else if (z == 0) { + buf[z] = 0; } else { - char newline = '\n'; - size_t i = 0; - struct termios old, new; - if (write(tty, prompt, strlen(prompt)) == -1) - fatal("error asking for passphrase"); - tcgetattr(tty, &old); - new = old; - new.c_lflag &= ~ECHO; - tcsetattr(tty, TCSANOW, &new); - errno = 0; - while (i < len - 1 && read(tty, buf + i, 1) == 1) { - if (buf[i] == '\n' || buf[i] == '\r') - break; - i++; - } - buf[i] = 0; - tcsetattr(tty, TCSANOW, &old); - if (write(tty, &newline, 1) == -1) - fatal("error asking for passphrase"); - close(tty); - if (errno) - fatal("could not read passphrase from /dev/tty"); + char *end; + buf[z] = 0; + if ((end = strchr(buf, '\r'))) + *end = 0; + else if ((end = strchr(buf, '\n'))) + *end = 0; } + close(tty); } -#elif defined(_WIN32) -#include - -static void -get_passphrase(char *buf, size_t len, char *prompt) -{ - DWORD orig; - HANDLE in = GetStdHandle(STD_INPUT_HANDLE); - if (!GetConsoleMode(in, &orig)) { - get_passphrase_dumb(buf, len, prompt); - } else { - size_t passlen; - SetConsoleMode(in, orig & ~ENABLE_ECHO_INPUT); - fputs(prompt, stderr); - if (!fgets(buf, len, stdin)) - fatal("could not read passphrase"); - fputc('\n', stderr); - passlen = strlen(buf); - if (buf[passlen - 1] < ' ') - buf[passlen - 1] = 0; - } -} - -#else -static void -get_passphrase(char *buf, size_t len, char *prompt) -{ - get_passphrase_dumb(buf, len, prompt); -} -#endif - -/** - * Create/truncate a file with paranoid permissions using OS calls. - */ -static FILE *secure_creat(const char *file); - -#if defined(__unix__) || defined(__APPLE__) -#include - -static FILE * -secure_creat(const char *file) -{ - int fd = open(file, O_CREAT | O_WRONLY, 00600); - if (fd == -1) - return 0; - return fdopen(fd, "wb"); -} - -#else -static FILE * -secure_creat(const char *file) -{ - return fopen(file, "wb"); -} -#endif - /** * Initialize a SHA-256 context for HMAC-SHA256. * All message data will go into the resulting context. @@ -607,36 +511,17 @@ key_derive(const char *passphrase, u8 *buf, int iexp, const u8 *salt) * Get secure entropy suitable for key generation from OS. * Abort the program if the entropy could not be retrieved. */ -static void secure_entropy(void *buf, size_t len); - -#if defined(__unix__) || defined(__APPLE__) static void secure_entropy(void *buf, size_t len) { - FILE *r = fopen("/dev/urandom", "rb"); - if (!r) + int fd = open("/dev/urandom", O_RDONLY); + if (fd == -1) fatal("failed to open %s", "/dev/urandom"); - if (!fread(buf, len, 1, r)) + if (read(fd, buf, len) != (ssize_t)len) fatal("failed to gather entropy"); - fclose(r); + close(fd); } -#elif defined(_WIN32) -#include - -static void -secure_entropy(void *buf, size_t len) -{ - HCRYPTPROV h = 0; - DWORD type = PROV_RSA_FULL; - DWORD flags = CRYPT_VERIFYCONTEXT | CRYPT_SILENT; - if (!CryptAcquireContext(&h, 0, 0, type, flags) || - !CryptGenRandom(h, len, buf)) - fatal("failed to gather entropy"); - CryptReleaseContext(h, 0); -} -#endif - /** * Generate a brand new Curve25519 secret key from system entropy. */ @@ -672,7 +557,7 @@ compute_shared(u8 *sh, const u8 *s, const u8 *p) * Encrypt from file to file using key/iv, aborting on any error. */ static void -symmetric_encrypt(FILE *in, FILE *out, const u8 *key, const u8 *iv) +symmetric_encrypt(int in, int out, const u8 *key, const u8 *iv) { static u8 buffer[2][CHACHA_BLOCKLENGTH * 1024]; u8 mac[SHA256_BLOCK_SIZE]; @@ -684,34 +569,33 @@ symmetric_encrypt(FILE *in, FILE *out, const u8 *key, const u8 *iv) hmac_init(hmac, key); for (;;) { - size_t z = fread(buffer[0], 1, sizeof(buffer[0]), in); - if (!z) { - if (ferror(in)) - fatal("error reading plaintext file"); + ssize_t z = full_read(in, buffer[0], sizeof(buffer[0])); + if (z < 0) + fatal("error reading plaintext file -- %s", strerror(errno)); + if (z == 0) break; - } sha256_update(hmac, buffer[0], z); chacha_encrypt_bytes(ctx, buffer[0], buffer[1], z); - if (!fwrite(buffer[1], z, 1, out)) - fatal("error writing ciphertext file"); - if (z < sizeof(buffer[0])) + if (write(out, buffer[1], z) != z) + fatal("error writing ciphertext file -- %s", strerror(errno)); + if (z < (ssize_t)sizeof(buffer[0])) break; } hmac_final(hmac, key, mac); - if (!fwrite(mac, sizeof(mac), 1, out)) - fatal("error writing checksum to ciphertext file"); - if (fflush(out)) - fatal("error flushing to ciphertext file -- %s", strerror(errno)); + if (write(out, mac, sizeof(mac)) != sizeof(mac)) + fatal("error writing checksum to ciphertext file -- %s", + strerror(errno)); } /** * Decrypt from file to file using key/iv, aborting on any error. */ static void -symmetric_decrypt(FILE *in, FILE *out, const u8 *key, const u8 *iv) +symmetric_decrypt(int in, int out, const u8 *key, const u8 *iv) { + ssize_t r; static u8 buffer[2][CHACHA_BLOCKLENGTH * 1024 + SHA256_BLOCK_SIZE]; u8 mac[SHA256_BLOCK_SIZE]; SHA256_CTX hmac[1]; @@ -722,39 +606,34 @@ symmetric_decrypt(FILE *in, FILE *out, const u8 *key, const u8 *iv) hmac_init(hmac, key); /* Always keep SHA256_BLOCK_SIZE bytes in the buffer. */ - if (!(fread(buffer[0], SHA256_BLOCK_SIZE, 1, in))) { - if (ferror(in)) - fatal("cannot read ciphertext file"); - else - fatal("ciphertext file too short"); - } + r = full_read(in, buffer[0], SHA256_BLOCK_SIZE); + if (r < 0) + fatal("cannot read ciphertext file -- %s", strerror(errno)); + if (r != SHA256_BLOCK_SIZE) + fatal("ciphertext file too short"); for (;;) { u8 *p = buffer[0] + SHA256_BLOCK_SIZE; - size_t z = fread(p, 1, sizeof(buffer[0]) - SHA256_BLOCK_SIZE, in); - if (!z) { - if (ferror(in)) - fatal("error reading ciphertext file"); + ssize_t z = full_read(in, p, sizeof(buffer[0]) - SHA256_BLOCK_SIZE); + if (z < 0) + fatal("error reading ciphertext file"); + else if (z == 0) break; - } chacha_encrypt_bytes(ctx, buffer[0], buffer[1], z); sha256_update(hmac, buffer[1], z); - if (!fwrite(buffer[1], z, 1, out)) - fatal("error writing plaintext file"); + if (write(out, buffer[1], z) != z) + fatal("error writing plaintext file -- %s", strerror(errno)); /* Move last SHA256_BLOCK_SIZE bytes to the front. */ memmove(buffer[0], buffer[0] + z, SHA256_BLOCK_SIZE); - if (z < sizeof(buffer[0]) - SHA256_BLOCK_SIZE) + if (z < (ssize_t)sizeof(buffer[0]) - SHA256_BLOCK_SIZE) break; } hmac_final(hmac, key, mac); if (memcmp(buffer[0], mac, sizeof(mac)) != 0) fatal("checksum mismatch!"); - if (fflush(out)) - fatal("error flushing to plaintext file -- %s", strerror(errno)); - } /** @@ -781,16 +660,14 @@ default_secfile(void) static void write_pubkey(char *file, u8 *key) { - FILE *f = fopen(file, "wb"); - if (!f) + int fd = open(file, O_CREAT | O_WRONLY, 0600); + if (fd == -1) fatal("failed to open key file for writing '%s' -- %s", file, strerror(errno)); - cleanup_register(f, file); - if (!fwrite(key, 32, 1, f)) + cleanup_pubfile = file; + if (write(fd, key, 32) != 32) fatal("failed to write key file '%s'", file); - cleanup_closed(f); - if (fclose(f)) - fatal("failed to flush key file '%s' -- %s", file, strerror(errno)); + close(fd); } /* Layout of secret key file */ @@ -806,7 +683,7 @@ write_pubkey(char *file, u8 *key) static void write_seckey(char *file, const u8 *seckey, int iexp) { - FILE *secfile; + int secfd; chacha_ctx cha[1]; SHA256_CTX sha[1]; u8 buf[8 + 1 + 3 + 20 + 32] = {0}; /* entire file contents */ @@ -856,15 +733,13 @@ write_seckey(char *file, const u8 *seckey, int iexp) memcpy(buf_seckey, seckey, 32); } - secfile = secure_creat(file); - if (!secfile) + secfd = open(file, O_CREAT | O_WRONLY, 0600); + if (secfd == -1) fatal("failed to open key file for writing '%s'", file); - cleanup_register(secfile, file); - if (!fwrite(buf, sizeof(buf), 1, secfile)) + cleanup_secfile = file; + if (write(secfd, buf, sizeof(buf)) != sizeof(buf)) fatal("failed to write key file '%s'", file); - cleanup_closed(secfile); - if (fclose(secfile)) - fatal("failed to flush key file '%s' -- %s", file, strerror(errno)); + close(secfd); } /** @@ -1198,8 +1073,8 @@ command_archive(struct optparse *options) /* Options */ char *infile; char *outfile; - FILE *in = stdin; - FILE *out = stdout; + int in = STDIN_FILENO; + int out = STDOUT_FILENO; char *pubfile = dupstr(global_pubkey); int delete = 0; @@ -1229,8 +1104,8 @@ command_archive(struct optparse *options) infile = optparse_arg(options); if (infile) { - in = fopen(infile, "rb"); - if (!in) + in = open(infile, O_RDONLY); + if (in == -1) fatal("could not open input file '%s' -- %s", infile, strerror(errno)); } @@ -1241,11 +1116,11 @@ command_archive(struct optparse *options) outfile = joinstr(2, infile, enchive_suffix); } if (outfile) { - out = fopen(outfile, "wb"); - if (!out) + out = open(outfile, O_CREAT | O_WRONLY, 0600); + if (out == -1) fatal("could not open output file '%s' -- %s", outfile, strerror(errno)); - cleanup_register(out, outfile); + cleanup_outfile = outfile; } /* Generare ephemeral keypair. */ @@ -1258,22 +1133,16 @@ command_archive(struct optparse *options) sha256_update(sha, shared, sizeof(shared)); sha256_final(sha, iv); iv[0] += (unsigned)ENCHIVE_FORMAT_VERSION; - if (!fwrite(iv, 8, 1, out)) + if (write(out, iv, 8) != 8) fatal("failed to write IV to archive"); - if (!fwrite(epublic, sizeof(epublic), 1, out)) + if (write(out, epublic, sizeof(epublic)) != sizeof(epublic)) fatal("failed to write ephemeral key to archive"); symmetric_encrypt(in, out, shared, iv); - if (in != stdin) - fclose(in); - if (out != stdout) { - cleanup_closed(out); - fclose(out); /* already flushed */ - } - + close(in); + close(out); if (delete && infile) remove(infile); - } static void @@ -1287,8 +1156,8 @@ command_extract(struct optparse *options) /* Options */ char *infile; char *outfile; - FILE *in = stdin; - FILE *out = stdout; + int in = STDIN_FILENO; + int out = STDOUT_FILENO; char *secfile = dupstr(global_seckey); int delete = 0; @@ -1318,8 +1187,8 @@ command_extract(struct optparse *options) infile = optparse_arg(options); if (infile) { - in = fopen(infile, "rb"); - if (!in) + in = open(infile, O_RDONLY); + if (in == -1) fatal("could not open input file '%s' -- %s", infile, strerror(errno)); } @@ -1335,17 +1204,18 @@ command_extract(struct optparse *options) outfile[len - slen] = 0; } if (outfile) { - out = fopen(outfile, "wb"); - if (!out) + out = open(outfile, O_CREAT | O_WRONLY, 0600); + if (out == -1) fatal("could not open output file '%s' -- %s", infile, strerror(errno)); - cleanup_register(out, outfile); + cleanup_outfile = outfile; } - if (!(fread(iv, sizeof(iv), 1, in))) - fatal("failed to read IV from archive"); - if (!(fread(epublic, sizeof(epublic), 1, in))) - fatal("failed to read ephemeral key from archive"); + if (full_read(in, iv, sizeof(iv)) != sizeof(iv)) + fatal("failed to read IV from archive -- %s", strerror(errno)); + if (full_read(in, epublic, sizeof(epublic)) != sizeof(epublic)) + fatal("failed to read ephemeral key from archive -- %s", + strerror(errno)); compute_shared(shared, secret, epublic); /* Validate key before processing the file. */ @@ -1358,13 +1228,8 @@ command_extract(struct optparse *options) symmetric_decrypt(in, out, shared, iv); - if (in != stdin) - fclose(in); - if (out != stdout) { - cleanup_closed(out); - fclose(out); /* already flushed */ - } - + close(in); + close(out); if (delete && infile) remove(infile); } @@ -1481,6 +1346,5 @@ main(int argc, char **argv) break; } - cleanup_free(); return 0; } diff --git a/src/w32-compat.h b/src/w32-compat.h new file mode 100644 index 0000000..3f3c86b --- /dev/null +++ b/src/w32-compat.h @@ -0,0 +1,301 @@ +#if !defined(W32_COMPAT_H) && defined(_WIN32) +#define W32_COMPAT_H + +#include +#include +#include +#include + +#include + +#ifdef _MSC_VER +# pragma comment(lib, "advapi32.lib") +#endif + +typedef SSIZE_T ssize_t; +typedef unsigned mode_t; + +#define O_CREAT (1u << 0) +#define O_RDONLY (1u << 1) +#define O_WRONLY (1u << 2) +#define O_RDWR (1u << 3) + +#define ECHO 1 +#define TCSANOW 0 +struct termios { + int c_lflag; +}; + +enum w32_fd { + FD_NONE, + FD_STDIN, + FD_STDOUT, + FD_STDERR, + FD_URANDOM, + FD_TTY, + FD_FILE +}; + +static struct { + enum w32_fd type; + union { + HANDLE file; + HCRYPTPROV urandom; + struct { + HANDLE in; + HANDLE out; + } con; + } d; +} w32_fds[8] = { + {FD_STDIN, {INVALID_HANDLE_VALUE}}, + {FD_STDOUT, {INVALID_HANDLE_VALUE}}, + {FD_STDERR, {INVALID_HANDLE_VALUE}} +}; + +static int +open(const char *pathname, int flags, ...) +{ + int fd = -1; + + size_t i; + for (i = 0; fd < 0 && i < sizeof(w32_fds) / sizeof(*w32_fds); i++) + if (w32_fds[i].type == FD_NONE) + fd = i; + if (fd == -1) { + errno = EMFILE; + return -1; + } + + if (strcmp(pathname, "/dev/urandom") == 0) { + DWORD type = PROV_RSA_FULL; + DWORD flag = CRYPT_VERIFYCONTEXT | CRYPT_SILENT; + HCRYPTPROV *h = &w32_fds[fd].d.urandom; + if (!CryptAcquireContext(h, 0, 0, type, flag)) { + errno = EACCES; + return -1; + } + assert(flags == O_RDONLY); + w32_fds[fd].type = FD_URANDOM; + return fd; + } else if (strcmp(pathname, "/dev/tty") == 0) { + DWORD access = GENERIC_READ | GENERIC_WRITE; + DWORD disp = OPEN_EXISTING; + DWORD flag = FILE_ATTRIBUTE_NORMAL; + HANDLE in = CreateFile("CONIN$", access, 0, 0, disp, flag, 0); + HANDLE out = CreateFile("CONOUT$", access, 0, 0, disp, flag, 0); + if (in == INVALID_HANDLE_VALUE) { + CloseHandle(out); + errno = ENOENT; + return -1; + } + if (out == INVALID_HANDLE_VALUE) { + CloseHandle(in); + errno = ENOENT; + return -1; + } + assert(flags == O_RDWR); + w32_fds[fd].d.con.in = in; + w32_fds[fd].d.con.out = out; + w32_fds[fd].type = FD_TTY; + return fd; + } else { + HANDLE h; + DWORD access; + DWORD share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; + DWORD disp; + DWORD flag = FILE_ATTRIBUTE_NORMAL; + + if (flags & O_CREAT) + disp = CREATE_ALWAYS; + else + disp = OPEN_EXISTING; + + if (flags & O_RDWR) + access = GENERIC_READ | GENERIC_WRITE; + else if (flags & O_RDONLY) + access = GENERIC_READ; + else if (flags & O_WRONLY) + access = GENERIC_WRITE; + else + abort(); + + h = CreateFile(pathname, access, share, 0, disp, flag, 0); + if (h == INVALID_HANDLE_VALUE) { + errno = EACCES; + return -1; + } + + w32_fds[fd].d.file = h; + w32_fds[fd].type = FD_FILE; + return fd; + } +} + +static int +close(int fd) +{ + switch (w32_fds[fd].type) { + case FD_NONE: { + abort(); + } break; + case FD_STDIN: + case FD_STDOUT: + case FD_STDERR: { + abort(); /* unimplemented */ + } break; + case FD_URANDOM: { + CryptReleaseContext(w32_fds[fd].d.urandom, 0); + } break; + case FD_TTY: { + CloseHandle(w32_fds[fd].d.con.in); + CloseHandle(w32_fds[fd].d.con.out); + } break; + case FD_FILE: { + CloseHandle(w32_fds[fd].d.file); + } break; + } + w32_fds[fd].type = FD_NONE; + return 0; +} + +static ssize_t +read(int fd, void *buf, size_t len) +{ + switch (w32_fds[fd].type) { + case FD_NONE: { + abort(); + } + case FD_STDIN: { + if (w32_fds[fd].d.file == INVALID_HANDLE_VALUE) { + DWORD mode; + w32_fds[fd].d.file = GetStdHandle(STD_INPUT_HANDLE); + assert(!GetConsoleMode(w32_fds[fd].d.file, &mode)); + } + } /* FALLTHROUGH */ + case FD_FILE: { + DWORD actual; + HANDLE in = w32_fds[fd].d.file; + if (!ReadFile(in, buf, len, &actual, 0)) { + DWORD error = GetLastError(); + if (error == ERROR_BROKEN_PIPE) + return 0; /* actually an EOF */ + errno = EIO; + return -1; + } + return actual; + } break; + case FD_STDOUT: + case FD_STDERR: { + abort(); + } break; + case FD_URANDOM: { + if (!CryptGenRandom(w32_fds[fd].d.urandom, len, buf)) { + errno = EIO; + return -1; + } + return len; + } + case FD_TTY: { + DWORD actual; + BOOL r = ReadConsole(w32_fds[fd].d.con.in, buf, len, &actual, 0); + if (!r) { + errno = EIO; + return -1; + } + return actual; + } + } + abort(); +} + +static ssize_t +write(int fd, const void *buf, size_t len) +{ + switch (w32_fds[fd].type) { + case FD_URANDOM: + case FD_NONE: { + abort(); + } + case FD_STDIN: { + abort(); + } break; + case FD_STDOUT: + case FD_STDERR: { + if (w32_fds[fd].d.file == INVALID_HANDLE_VALUE) { + DWORD mode; + if (w32_fds[fd].type == FD_STDOUT) + w32_fds[fd].d.file = GetStdHandle(STD_OUTPUT_HANDLE); + else + w32_fds[fd].d.file = GetStdHandle(STD_ERROR_HANDLE); + assert(!GetConsoleMode(w32_fds[fd].d.file, &mode)); + } + } /* FALLTHROUGH */ + case FD_FILE: { + DWORD actual; + if (!WriteFile(w32_fds[fd].d.file, buf, len, &actual, 0)) { + errno = EIO; + return -1; + } + return actual; + } break; + case FD_TTY: { + DWORD actual; + BOOL r = WriteConsole(w32_fds[fd].d.con.out, buf, len, &actual, 0); + if (!r) { + errno = EIO; + return -1; + } + return actual; + } + } + abort(); +} + +static int +tcgetattr(int fd, struct termios *s) +{ + assert(w32_fds[fd].type == FD_TTY); + s->c_lflag = 1; + return 0; +} + +static int +tcsetattr(int fd, int actions, struct termios *s) +{ + DWORD orig; + HANDLE in = w32_fds[fd].d.con.in; + + assert(w32_fds[fd].type == FD_TTY); + assert(actions == TCSANOW); + if (GetConsoleMode(in, &orig)) { + if (s->c_lflag) + SetConsoleMode(in, orig | ENABLE_ECHO_INPUT); + else + SetConsoleMode(in, orig & ~ENABLE_ECHO_INPUT); + } + return 0; +} + +#if 0 +static int +mkdir(const char *pathname, mode_t mode) +{ + (void)mode; + if (CreateDirectory(pathname, 0)) + return 0; + switch (GetLastError()) { + case ERROR_ALREADY_EXISTS: { + errno = EEXIST; + } break; + case ERROR_PATH_NOT_FOUND: { + errno = ENOENT; + } break; + default: { + errno = EFAULT; + } + } + return -1; +} +#endif + +#endif