You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
622 lines
17 KiB
622 lines
17 KiB
#include "sysmisc.h"
|
|
#include "gbuffer.h"
|
|
#include "oso.h"
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <sys/stat.h>
|
|
|
|
static char const *const xdg_config_home_env = "XDG_CONFIG_HOME";
|
|
static char const *const home_env = "HOME";
|
|
|
|
void expand_home_tilde(oso **path) {
|
|
oso *s = *path;
|
|
size_t n = osolen(s);
|
|
if (n < 2)
|
|
return;
|
|
if (osoc(s)[0] != '~' || osoc(s)[1] != '/')
|
|
return;
|
|
char const *homedir = getenv(home_env);
|
|
if (!homedir)
|
|
return;
|
|
size_t hlen = strlen(homedir);
|
|
osoensurecap(&s, n + hlen - 1);
|
|
if (!s)
|
|
goto done;
|
|
memmove((char *)s + hlen, (char *)s + 1, n); // includes '\0'
|
|
memcpy((char *)s, homedir, hlen);
|
|
osopokelen(s, n + hlen - 1);
|
|
done:
|
|
*path = s;
|
|
}
|
|
|
|
ORCA_NOINLINE
|
|
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 =
|
|
#ifdef ORCA_OS_MAC
|
|
popen("pbcopy -pboard general 2>/dev/null", "w");
|
|
#else
|
|
popen("xclip -i -selection clipboard 2>/dev/null", "w");
|
|
#endif
|
|
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_NOINLINE
|
|
Cboard_error cboard_paste(Glyph *gbuffer, Usz height, Usz width, Usz y, Usz x,
|
|
Usz *out_h, Usz *out_w) {
|
|
FILE *fp =
|
|
#ifdef ORCA_OS_MAC
|
|
popen("pbpaste -pboard general -Prefer txt 2>/dev/null", "r");
|
|
#else
|
|
popen("xclip -o -selection clipboard 2>/dev/null", "r");
|
|
#endif
|
|
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 = orca_is_valid_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_NOINLINE
|
|
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) { // empty right side, but still valid pair
|
|
b1 = b0;
|
|
goto ok;
|
|
}
|
|
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 null_right;
|
|
ignore:
|
|
s[len - 1] = '\0';
|
|
*out_left = s;
|
|
*out_leftsize = len;
|
|
err = Conf_read_irrelevant;
|
|
goto null_right;
|
|
null_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 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;
|
|
}
|
|
|
|
static void conf_impl_catconfpath(oso **p, char const *conf_file_name,
|
|
size_t conflen) {
|
|
oso *path = *p;
|
|
size_t n = osolen(path);
|
|
osoensurecap(&path, n + 1 + conflen);
|
|
if (!path)
|
|
goto done;
|
|
((char *)path)[n] = '/';
|
|
memcpy((char *)path + n + 1, conf_file_name, conflen);
|
|
((char *)path)[n + 1 + conflen] = '\0';
|
|
osopokelen(path, n + 1 + conflen);
|
|
done:
|
|
*p = path;
|
|
}
|
|
|
|
FILE *conf_file_open_for_reading(char const *conf_file_name) {
|
|
if (!conf_file_name)
|
|
return NULL;
|
|
oso *path = NULL;
|
|
if (try_get_conf_dir(&path))
|
|
return NULL;
|
|
size_t conflen = strlen(conf_file_name);
|
|
if (conflen == 0)
|
|
return NULL;
|
|
conf_impl_catconfpath(&path, conf_file_name, conflen);
|
|
if (!path)
|
|
return NULL;
|
|
FILE *file = fopen(osoc(path), "r");
|
|
osofree(path);
|
|
return file;
|
|
}
|
|
|
|
Conf_save_start_error conf_save_start(Conf_save *p,
|
|
char const *conf_file_name) {
|
|
*p = (Conf_save){0};
|
|
oso *dir = NULL;
|
|
Conf_save_start_error err;
|
|
if (!conf_file_name) {
|
|
err = Conf_save_start_bad_conf_name;
|
|
goto cleanup;
|
|
}
|
|
if (try_get_conf_dir(&dir)) {
|
|
err = Conf_save_start_no_home;
|
|
goto cleanup;
|
|
}
|
|
if (!dir)
|
|
goto allocfail;
|
|
osoputoso(&p->canonpath, dir);
|
|
if (!p->canonpath)
|
|
goto allocfail;
|
|
size_t namelen = strlen(conf_file_name);
|
|
if (namelen == 0) {
|
|
err = Conf_save_start_bad_conf_name;
|
|
goto cleanup;
|
|
}
|
|
conf_impl_catconfpath(&p->canonpath, conf_file_name, namelen);
|
|
if (!p->canonpath)
|
|
goto allocfail;
|
|
osoputoso(&p->temppath, p->canonpath);
|
|
if (!p->temppath)
|
|
goto allocfail;
|
|
osocat(&p->temppath, ".tmp");
|
|
if (!p->temppath)
|
|
goto allocfail;
|
|
// 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;
|
|
|
|
allocfail:
|
|
err = Conf_save_start_alloc_failed;
|
|
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);
|
|
*p = (Conf_save){0};
|
|
}
|
|
|
|
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 *ezconf_w_errorstring(Ezconf_w_error error) {
|
|
switch (error) {
|
|
case Ezconf_w_ok:
|
|
return "No error";
|
|
case Ezconf_w_bad_conf_name:
|
|
return "Bad config file name";
|
|
case Ezconf_w_oom:
|
|
return "Out of memory";
|
|
case Ezconf_w_no_home:
|
|
return "Unable to resolve $XDG_CONFIG_HOME or $HOME";
|
|
case Ezconf_w_mkdir_failed:
|
|
return "Unable to create $XDG_CONFIG_HOME or $HOME/.config directory";
|
|
case Ezconf_w_conf_dir_not_dir:
|
|
return "Config directory path is not a directory";
|
|
case Ezconf_w_old_temp_file_stuck:
|
|
return "Unable to remove old .conf.tmp file";
|
|
case Ezconf_w_temp_file_perm_denied:
|
|
return "Permission denied for config directory";
|
|
case Ezconf_w_temp_open_failed:
|
|
return "Unable to open .conf.tmp for writing";
|
|
case Ezconf_w_temp_fsync_failed:
|
|
return "fsync() reported an when writing temp file.\n"
|
|
"Refusing to continue.";
|
|
case Ezconf_w_temp_close_failed:
|
|
return "Unable to close temp file";
|
|
case Ezconf_w_rename_failed:
|
|
return "Unable to rename .conf.tmp to .conf";
|
|
case Ezconf_w_line_too_long:
|
|
return "Line in file is too long";
|
|
case Ezconf_w_existing_read_error:
|
|
return "Error when reading existing configuration file";
|
|
case Ezconf_w_unknown_error:
|
|
break;
|
|
}
|
|
return "Unknown";
|
|
}
|
|
|
|
void ezconf_r_start(Ezconf_r *ezcr, char const *conf_file_name) {
|
|
ezcr->file = conf_file_open_for_reading(conf_file_name);
|
|
ezcr->index = 0;
|
|
ezcr->value = NULL;
|
|
}
|
|
|
|
bool ezconf_r_step(Ezconf_r *ezcr, char const *const *names, size_t nameslen) {
|
|
return conf_read_match(&ezcr->file, names, nameslen, ezcr->buffer,
|
|
sizeof ezcr->buffer, &ezcr->index, &ezcr->value);
|
|
}
|
|
|
|
enum {
|
|
Confwflag_add_newline = 1 << 0,
|
|
Ezconf_opt_written = 1 << 0,
|
|
};
|
|
|
|
void ezconf_w_start(Ezconf_w *ezcw, Ezconf_opt *optsbuffer, size_t buffercap,
|
|
char const *conf_file_name) {
|
|
*ezcw = (Ezconf_w){.save = {0}}; // Weird to silence clang warning
|
|
ezcw->opts = optsbuffer;
|
|
ezcw->optscap = buffercap;
|
|
Ezconf_w_error error = Ezconf_w_unknown_error;
|
|
switch (conf_save_start(&ezcw->save, conf_file_name)) {
|
|
case Conf_save_start_ok:
|
|
error = Ezconf_w_ok;
|
|
ezcw->file = ezcw->save.tempfile;
|
|
break;
|
|
case Conf_save_start_bad_conf_name:
|
|
error = Ezconf_w_bad_conf_name;
|
|
break;
|
|
case Conf_save_start_alloc_failed:
|
|
error = Ezconf_w_oom;
|
|
break;
|
|
case Conf_save_start_no_home:
|
|
error = Ezconf_w_no_home;
|
|
break;
|
|
case Conf_save_start_mkdir_failed:
|
|
error = Ezconf_w_mkdir_failed;
|
|
break;
|
|
case Conf_save_start_conf_dir_not_dir:
|
|
error = Ezconf_w_conf_dir_not_dir;
|
|
break;
|
|
case Conf_save_start_old_temp_file_stuck:
|
|
error = Ezconf_w_old_temp_file_stuck;
|
|
break;
|
|
case Conf_save_start_temp_file_perm_denied:
|
|
error = Ezconf_w_temp_file_perm_denied;
|
|
break;
|
|
case Conf_save_start_temp_file_open_failed:
|
|
error = Ezconf_w_temp_open_failed;
|
|
break;
|
|
}
|
|
ezcw->error = error;
|
|
}
|
|
void ezconf_w_addopt(Ezconf_w *ezcw, char const *key, intptr_t id) {
|
|
size_t count = ezcw->optscount, cap = ezcw->optscap;
|
|
if (count == cap)
|
|
return;
|
|
ezcw->opts[count] = (Ezconf_opt){.name = key, .id = id, .flags = 0};
|
|
ezcw->optscount = count + 1;
|
|
}
|
|
bool ezconf_w_step(Ezconf_w *ezcw) {
|
|
uint32_t stateflags = ezcw->stateflags;
|
|
FILE *origfile = ezcw->save.origfile, *tempfile = ezcw->save.tempfile;
|
|
Ezconf_opt *opts = ezcw->opts, *chosen = NULL;
|
|
size_t optscount = ezcw->optscount;
|
|
if (ezcw->error || !tempfile) // Already errored or finished ok
|
|
return false;
|
|
// If we set a flag to write a closing newline the last time we were called,
|
|
// write it now.
|
|
if (stateflags & Confwflag_add_newline) {
|
|
fputs("\n", tempfile);
|
|
stateflags &= ~(uint32_t)Confwflag_add_newline;
|
|
}
|
|
if (!optscount)
|
|
goto commit;
|
|
if (!origfile)
|
|
goto write_leftovers;
|
|
for (;;) { // Scan through file looking for known keys in key=value lines
|
|
char linebuff[1024];
|
|
char *left, *right;
|
|
size_t leftsz, rightsz;
|
|
Conf_read_result res = conf_read_line(origfile, linebuff, sizeof linebuff,
|
|
&left, &leftsz, &right, &rightsz);
|
|
switch (res) {
|
|
case Conf_read_left_and_right: {
|
|
for (size_t i = 0; i < optscount; i++) {
|
|
char const *name = opts[i].name;
|
|
if (!name)
|
|
continue;
|
|
if (strcmp(name, left) != 0)
|
|
continue;
|
|
// If we already wrote this one, comment out the line instead, and move
|
|
// on to the next line.
|
|
if (opts[i].flags & (uint8_t)Ezconf_opt_written) {
|
|
fputs("# ", tempfile);
|
|
goto write_landr;
|
|
}
|
|
chosen = opts + i;
|
|
goto return_for_writing;
|
|
}
|
|
write_landr:
|
|
fputs(left, tempfile);
|
|
fputs(" = ", tempfile);
|
|
fputs(right, tempfile);
|
|
fputs("\n", tempfile);
|
|
continue;
|
|
}
|
|
case Conf_read_irrelevant:
|
|
fputs(left, tempfile);
|
|
fputs("\n", tempfile);
|
|
continue;
|
|
case Conf_read_eof:
|
|
goto end_original;
|
|
case Conf_read_buffer_too_small:
|
|
ezcw->error = Ezconf_w_line_too_long;
|
|
goto cancel;
|
|
case Conf_read_io_error:
|
|
ezcw->error = Ezconf_w_existing_read_error;
|
|
goto cancel;
|
|
}
|
|
}
|
|
end_original: // Don't need original file anymore
|
|
fclose(origfile);
|
|
ezcw->save.origfile = origfile = NULL;
|
|
write_leftovers: // Write out any guys that weren't in original file.
|
|
for (;;) { // Find the first guy that wasn't already written.
|
|
if (!optscount)
|
|
goto commit;
|
|
chosen = opts;
|
|
// Drop the guy from the front of the list. This is to reduce super-linear
|
|
// complexity growth as the number of conf key-value pairs are increased.
|
|
// (Otherwise, we iterate the full set of guys on each call during the
|
|
// "write the leftovers" phase.)
|
|
opts++;
|
|
optscount--;
|
|
if (!(chosen->flags & (uint8_t)Ezconf_opt_written))
|
|
break;
|
|
}
|
|
// Once control has reached here, we're going to return true to the caller.
|
|
// Which means we expect to be called at least one more time. So update the
|
|
// pointers stored in the persistent state, so that we don't have to scan
|
|
// through as much of this list next time. (This might even end up finishing
|
|
// it off, making it empty.)
|
|
ezcw->opts = opts;
|
|
ezcw->optscount = optscount;
|
|
return_for_writing:
|
|
chosen->flags |= (uint8_t)Ezconf_opt_written;
|
|
fputs(chosen->name, tempfile);
|
|
fputs(" = ", tempfile);
|
|
ezcw->optid = chosen->id;
|
|
stateflags |= (uint32_t)Confwflag_add_newline;
|
|
ezcw->stateflags = stateflags;
|
|
return true;
|
|
cancel:
|
|
conf_save_cancel(&ezcw->save);
|
|
// ^- Sets tempfile to null, which we use as a guard at the top of this
|
|
// function.
|
|
ezcw->file = NULL;
|
|
ezcw->stateflags = 0;
|
|
return false;
|
|
commit:;
|
|
Ezconf_w_error error = Ezconf_w_unknown_error;
|
|
switch (conf_save_commit(&ezcw->save)) {
|
|
case Conf_save_commit_ok:
|
|
error = Ezconf_w_ok;
|
|
break;
|
|
case Conf_save_commit_temp_fsync_failed:
|
|
error = Ezconf_w_temp_fsync_failed;
|
|
break;
|
|
case Conf_save_commit_temp_close_failed:
|
|
error = Ezconf_w_temp_close_failed;
|
|
break;
|
|
case Conf_save_commit_rename_failed:
|
|
error = Ezconf_w_rename_failed;
|
|
break;
|
|
}
|
|
ezcw->file = NULL;
|
|
ezcw->error = error;
|
|
ezcw->stateflags = 0;
|
|
return false;
|
|
}
|
|
|