ref: bc01e227b5831df0d4f88f4c11ebc2b6968d2535
dir: /sysmisc.c/
#include "sysmisc.h" #include "gbuffer.h" #include "oso.h" #include <ctype.h> #include <errno.h> #include <sys/stat.h> ORCA_FORCE_NO_INLINE Cboard_error cboard_copy(Glyph const *gbuffer, Usz field_height, Usz field_width, Usz rect_y, Usz rect_x, Usz rect_h, Usz rect_w) { (void)field_height; FILE *fp = popen("xclip -i -selection clipboard 2>/dev/null", "w"); if (!fp) return Cboard_error_popen_failed; for (Usz iy = 0; iy < rect_h; iy++) { Glyph const *row = gbuffer + (rect_y + iy) * field_width + rect_x; fwrite(row, sizeof(Glyph), rect_w, fp); if (iy + 1 < rect_h) fputc('\n', fp); } int status = pclose(fp); return status ? Cboard_error_process_exit_error : Cboard_error_none; } ORCA_FORCE_NO_INLINE Cboard_error cboard_paste(Glyph *gbuffer, Usz height, Usz width, Usz y, Usz x, Usz *out_h, Usz *out_w) { FILE *fp = popen("xclip -o -selection clipboard 2>/dev/null", "r"); Usz start_y = y, start_x = x, max_y = y, max_x = x; if (!fp) return Cboard_error_popen_failed; char inbuff[512]; for (;;) { size_t n = fread(inbuff, 1, sizeof inbuff, fp); for (size_t i = 0; i < n; i++) { char c = inbuff[i]; if (c == '\r' || c == '\n') { y++; x = start_x; continue; } if (c != ' ' && y < height && x < width) { Glyph g = is_valid_glyph((Glyph)c) ? (Glyph)c : '.'; gbuffer_poke(gbuffer, height, width, y, x, g); if (x > max_x) max_x = x; if (y > max_y) max_y = y; } x++; } if (n < sizeof inbuff) break; } int status = pclose(fp); *out_h = max_y - start_y + 1; *out_w = max_x - start_x + 1; return status ? Cboard_error_process_exit_error : Cboard_error_none; } ORCA_FORCE_NO_INLINE Conf_read_result conf_read_line(FILE *file, char *buf, Usz bufsize, char **out_left, Usz *out_leftsize, char **out_right, Usz *out_rightsize) { // a0 and a1 are the start and end positions of the left side of an "foo=bar" // pair. b0 and b1 are the positions right side. Leading and trailing spaces // will be removed. Usz len, a0, a1, b0, b1; char *s; if (bufsize < 2) goto insufficient_buffer; #if SIZE_MAX > INT_MAX if (bufsize > (Usz)INT_MAX) exit(1); // he boot too big #endif s = fgets(buf, (int)bufsize, file); if (!s) { if (feof(file)) goto eof; goto ioerror; } len = strlen(buf); if (len == bufsize - 1 && buf[len - 1] != '\n' && !feof(file)) goto insufficient_buffer; a0 = 0; for (;;) { // scan for first non-space in " foo=bar" if (a0 == len) goto ignore; char c = s[a0]; if (c == ';' || c == '#') // comment line, ignore goto ignore; if (c == '=') // '=' before any other char, bad goto ignore; if (!isspace(c)) break; a0++; } a1 = a0; for (;;) { // scan for '=' a1++; if (a1 == len) goto ignore; char c = s[a1]; Usz x = a1; // don't include any whitespace preceeding the '=' while (isspace(c)) { x++; if (x == len) goto ignore; c = s[x]; } if (c == '=') { b0 = x; break; } a1 = x; } for (;;) { // scan for first non-whitespace after '=' b0++; if (b0 == len) goto ignore; char c = s[b0]; if (!isspace(c)) break; } b1 = b0; for (;;) { // scan for end of useful stuff for right-side value b1++; if (b1 == len) goto ok; char c = s[b1]; Usz x = b1; // don't include any whitespace preceeding the EOL while (isspace(c)) { x++; if (x == len) goto ok; c = s[x]; } b1 = x; } Conf_read_result err; insufficient_buffer: err = Conf_read_buffer_too_small; goto fail; eof: err = Conf_read_eof; goto fail; ioerror: err = Conf_read_io_error; goto fail; fail: *out_left = NULL; *out_leftsize = 0; goto no_right; ignore: s[len - 1] = '\0'; *out_left = s; *out_leftsize = len; err = Conf_read_irrelevant; goto no_right; no_right: *out_right = NULL; *out_rightsize = 0; return err; ok: s[a1] = '\0'; s[b1] = '\0'; *out_left = s + a0; *out_leftsize = a1 - a0; *out_right = s + b0; *out_rightsize = b1 - b0; return Conf_read_left_and_right; } bool conf_read_match(FILE **pfile, char const *const *names, Usz nameslen, char *buf, Usz bufsize, Usz *out_index, char **out_value) { FILE *file = *pfile; if (!file) return false; char *left; Usz leftsz, rightsz; next_line:; Conf_read_result res = conf_read_line(file, buf, bufsize, &left, &leftsz, out_value, &rightsz); switch (res) { case Conf_read_left_and_right: for (Usz i = 0; i < nameslen; i++) { if (strcmp(names[i], left) == 0) { *out_index = i; return true; } } goto next_line; case Conf_read_irrelevant: goto next_line; case Conf_read_buffer_too_small: case Conf_read_eof: case Conf_read_io_error: break; } fclose(file); *pfile = NULL; return false; } typedef enum { Conf_dir_ok = 0, Conf_dir_no_home, } Conf_dir_error; static char const *const xdg_config_home_env = "XDG_CONFIG_HOME"; static char const *const home_env = "HOME"; static char const *const conf_file_name = "/orca.conf"; static Conf_dir_error try_get_conf_dir(oso **out) { char const *xdgcfgdir = getenv(xdg_config_home_env); if (xdgcfgdir) { Usz xdgcfgdirlen = strlen(xdgcfgdir); if (xdgcfgdirlen > 0) { osoputlen(out, xdgcfgdir, xdgcfgdirlen); return Conf_dir_ok; } } char const *homedir = getenv(home_env); if (homedir) { Usz homedirlen = strlen(homedir); if (homedirlen > 0) { osoputprintf(out, "%s/.config", homedir); return Conf_dir_ok; } } return Conf_dir_no_home; } FILE *conf_file_open_for_reading(void) { oso *path = NULL; if (try_get_conf_dir(&path)) return NULL; osocat(&path, conf_file_name); if (!path) return NULL; FILE *file = fopen(osoc(path), "r"); osofree(path); return file; } Conf_save_start_error conf_save_start(Conf_save *p) { memset(p, 0, sizeof(Conf_save)); oso *dir = NULL; Conf_save_start_error err; if (try_get_conf_dir(&dir)) { err = Conf_save_start_no_home; goto cleanup; } if (!dir) { err = Conf_save_start_alloc_failed; goto cleanup; } osoputoso(&p->canonpath, dir); osocat(&p->canonpath, conf_file_name); if (!p->canonpath) { err = Conf_save_start_alloc_failed; goto cleanup; } osoputoso(&p->temppath, p->canonpath); osocat(&p->temppath, ".tmp"); if (!p->temppath) { err = Conf_save_start_alloc_failed; goto cleanup; } // Remove old temp file if it exists. If it exists and we can't remove it, // error. if (unlink(osoc(p->temppath)) == -1 && errno != ENOENT) { switch (errno) { case ENOTDIR: err = Conf_save_start_conf_dir_not_dir; break; case EACCES: err = Conf_save_start_temp_file_perm_denied; break; default: err = Conf_save_start_old_temp_file_stuck; break; } goto cleanup; } p->tempfile = fopen(osoc(p->temppath), "w"); if (!p->tempfile) { // Try to create config dir, in case it doesn't exist. (XDG says we should // do this, and use mode 0700.) mkdir(osoc(dir), 0700); p->tempfile = fopen(osoc(p->temppath), "w"); } if (!p->tempfile) { err = Conf_save_start_temp_file_open_failed; goto cleanup; } // This may be left as NULL. p->origfile = fopen(osoc(p->canonpath), "r"); // We did it, boys. osofree(dir); return Conf_save_start_ok; cleanup: osofree(dir); conf_save_cancel(p); return err; } void conf_save_cancel(Conf_save *p) { osofree(p->canonpath); osofree(p->temppath); if (p->origfile) fclose(p->origfile); if (p->tempfile) fclose(p->tempfile); memset(p, 0, sizeof(Conf_save)); } Conf_save_commit_error conf_save_commit(Conf_save *p) { Conf_save_commit_error err; fclose(p->tempfile); p->tempfile = NULL; if (p->origfile) { fclose(p->origfile); p->origfile = NULL; } // This isn't really atomic. But if we want to close and move a file // simultaneously, I think we have to use OS-specific facilities. So I guess // this is the best we can do for now. I could be wrong, though. But I // couldn't find any good information about it. if (rename(osoc(p->temppath), osoc(p->canonpath)) == -1) { err = Conf_save_commit_rename_failed; goto cleanup; } err = Conf_save_commit_ok; cleanup: conf_save_cancel(p); return err; } char const *prefs_save_error_string(Prefs_save_error error) { switch (error) { case Prefs_save_ok: return "No error"; case Prefs_save_oom: return "Out of memory"; case Prefs_save_no_home: return "Unable to resolve $XDG_CONFIG_HOME or $HOME"; case Prefs_save_mkdir_failed: return "Unable to create $XDG_CONFIG_HOME or $HOME/.config directory"; case Prefs_save_conf_dir_not_dir: return "Config directory path is not a directory"; case Prefs_save_old_temp_file_stuck: return "Unable to remove old orca.conf.tmp file"; case Prefs_save_temp_file_perm_denied: return "Permission denied for config directory"; case Prefs_save_temp_open_failed: return "Unable to open orca.conf.tmp for writing"; case Prefs_save_temp_fsync_failed: return "fsync() reported an when writing temp file.\n" "Refusing to continue."; case Prefs_save_temp_close_failed: return "Unable to close temp file"; case Prefs_save_rename_failed: return "Unable to rename orca.conf.tmp to orca.conf"; case Prefs_save_line_too_long: return "Line in file is too long"; case Prefs_save_existing_read_error: return "Error when reading existing configuration file"; case Prefs_save_unknown_error: break; } return "Unknown"; } void ezconf_read_start(Ezconf_read *ezcr) { ezcr->file = conf_file_open_for_reading(); ezcr->index = 0; ezcr->value = NULL; } bool ezconf_read_step(Ezconf_read *ezcr, char const *const *names, Usz nameslen) { return conf_read_match(&ezcr->file, names, nameslen, ezcr->buffer, sizeof ezcr->buffer, &ezcr->index, &ezcr->value); }