diff options
author | Andrew J. Schorr <aschorr@telemetry-investments.com> | 2013-01-08 20:56:33 -0500 |
---|---|---|
committer | Andrew J. Schorr <aschorr@telemetry-investments.com> | 2013-01-08 20:56:33 -0500 |
commit | 1abfe5e82822a9e81a3bd1df2e7747afbc0ae1b9 (patch) | |
tree | 2deac40bf78a9acbb4d777f5f4520fd5731df5e5 /extension/inplace.c | |
parent | 155fc0deca8d8976915fdc5edc84c4c6a1af652b (diff) | |
download | egawk-1abfe5e82822a9e81a3bd1df2e7747afbc0ae1b9.tar.gz egawk-1abfe5e82822a9e81a3bd1df2e7747afbc0ae1b9.tar.bz2 egawk-1abfe5e82822a9e81a3bd1df2e7747afbc0ae1b9.zip |
Add inplace file editing extension.
Diffstat (limited to 'extension/inplace.c')
-rw-r--r-- | extension/inplace.c | 241 |
1 files changed, 241 insertions, 0 deletions
diff --git a/extension/inplace.c b/extension/inplace.c new file mode 100644 index 00000000..69b188b8 --- /dev/null +++ b/extension/inplace.c @@ -0,0 +1,241 @@ +/* + * inplace.c - Provide support for in-place editing. + */ + +/* + * Copyright (C) 2013 the Free Software Foundation, Inc. + * + * This file is part of GAWK, the GNU implementation of the + * AWK Programming Language. + * + * GAWK is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GAWK is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <sys/types.h> +#include <sys/stat.h> + +#include "gawkapi.h" + +#include "gettext.h" +#define _(msgid) gettext(msgid) +#define N_(msgid) msgid + +static const gawk_api_t *api; /* for convenience macros to work */ +static awk_ext_id_t *ext_id; +static const char *ext_version = "inplace extension: version 1.0"; + +int plugin_is_GPL_compatible; + +static struct { + char *tname; + int default_stdout; + int posrc; /* return code from fgetpos */ + fpos_t pos; +} state = { NULL, -1 }; + +/* + * XXX Known problems: + * 1. Should copy ACL. + * 2. Not reentrant, so will not work if multiple files are open at + * the same time. I'm not sure this is a meaningful problem in practice. + */ + +static void +at_exit(void *data, int exit_status) +{ + if (state.tname) { + unlink(state.tname); + free(state.tname); + state.tname = NULL; + } +} + +/* + * N.B. Almost everything is a fatal error because this feature is typically + * used for one-liners where the user is not going to be worrying about + * checking errors. If anything unexpected occurs, we want to abort + * immediately! + */ + +static int +invalid_filename(const awk_string_t *filename) +{ + return filename->len == 0 || + (filename->len == 1 && *filename->str == '-'); +} + +/* do_inplace_begin --- start in-place editing */ + +static awk_value_t * +do_inplace_begin(int nargs, awk_value_t *result) +{ + awk_value_t filename, suffix; + struct stat sbuf; + char *p; + int fd; + + assert(result != NULL); + + if (state.tname) + fatal(ext_id, _("inplace_begin: in-place editing already active")); + + if (nargs != 2) + fatal(ext_id, _("inplace_begin: expects 2 arguments but called with %d"), nargs); + + if (! get_argument(0, AWK_STRING, &filename)) + fatal(ext_id, _("inplace_begin: cannot retrieve 1st argument as a string filename")); + + if (! get_argument(1, AWK_STRING, &suffix)) + suffix.str_value.str = NULL; + + if (invalid_filename(&filename.str_value)) { + warning(ext_id, _("inplace_begin: disabling in-place editing for invalid FILENAME `%s'"), + filename.str_value.str); + unset_ERRNO(); + return make_number(-1, result); + } + + if (stat(filename.str_value.str, & sbuf) < 0) { + warning(ext_id, _("inplace_begin: Cannot stat `%s' (%s)"), + filename.str_value.str, strerror(errno)); + update_ERRNO_int(errno); + return make_number(-1, result); + } + + if (! S_ISREG(sbuf.st_mode)) { + warning(ext_id, _("inplace_begin: `%s' is not a regular file"), + filename.str_value.str); + unset_ERRNO(); + return make_number(-1, result); + } + + /* create a temporary file to which to redirect stdout */ + emalloc(state.tname, char *, filename.str_value.len+14, "do_inplace_begin"); + sprintf(state.tname, "%s.gawk.XXXXXX", filename.str_value.str); + + if ((fd = mkstemp(state.tname)) < 0) + fatal(ext_id, _("inplace_begin: mkstemp(`%s') failed (%s)"), + state.tname, strerror(errno)); + + /* N.B. chown/chmod should be more portable than fchown/fchmod */ + if (chown(state.tname, sbuf.st_uid, sbuf.st_gid) < 0) + chown(state.tname, -1, sbuf.st_gid); + if (chmod(state.tname, sbuf.st_mode) < 0) + fatal(ext_id, _("inplace_begin: chmod failed (%s)"), + strerror(errno)); + + fflush(stdout); + /* N.B. fgetpos fails when stdout is a tty */ + state.posrc = fgetpos(stdout, &state.pos); + if ((state.default_stdout = dup(STDOUT_FILENO)) < 0) + fatal(ext_id, _("inplace_begin: dup(stdout) failed (%s)"), + strerror(errno)); + if (dup2(fd, STDOUT_FILENO) < 0) + fatal(ext_id, _("inplace_begin: dup2(%d, stdout) failed (%s)"), + fd, strerror(errno)); + if (close(fd) < 0) + fatal(ext_id, _("inplace_begin: close(%d) failed (%s)"), + fd, strerror(errno)); + rewind(stdout); + return make_number(0, result); +} + +/* do_inplace_end --- finish in-place editing */ + +static awk_value_t * +do_inplace_end(int nargs, awk_value_t *result) +{ + awk_value_t filename, suffix; + + assert(result != NULL); + + if (nargs != 2) + fatal(ext_id, _("inplace_begin: expects 2 arguments but called with %d"), nargs); + + if (! get_argument(0, AWK_STRING, &filename)) + fatal(ext_id, _("inplace_end: cannot retrieve 1st argument as a string filename")); + + if (! get_argument(1, AWK_STRING, &suffix)) + suffix.str_value.str = NULL; + + if (! state.tname) { + if (! invalid_filename(&filename.str_value)) + warning(ext_id, _("inplace_end: in-place editing not active")); + return make_number(0, result); + } + + fflush(stdout); + if (dup2(state.default_stdout, STDOUT_FILENO) < 0) + fatal(ext_id, _("inplace_end: dup2(%d, stdout) failed (%s)"), + state.default_stdout, strerror(errno)); + if (close(state.default_stdout) < 0) + fatal(ext_id, _("inplace_end: close(%d) failed (%s)"), + state.default_stdout, strerror(errno)); + state.default_stdout = -1; + if (state.posrc == 0 && fsetpos(stdout, &state.pos) < 0) + fatal(ext_id, _("inplace_end: fsetpos(stdout) failed (%s)"), + strerror(errno)); + + if (suffix.str_value.str && suffix.str_value.str[0]) { + /* backup requested */ + char *bakname; + + emalloc(bakname, char *, filename.str_value.len+suffix.str_value.len+1, + "do_inplace_end"); + sprintf(bakname, "%s%s", + filename.str_value.str, suffix.str_value.str); + unlink(bakname); /* if backup file exists already, remove it */ + if (link(filename.str_value.str, bakname) < 0) + fatal(ext_id, _("inplace_end: link(`%s', `%s') failed (%s)"), + filename.str_value.str, bakname, strerror(errno)); + free(bakname); + } + + if (rename(state.tname, filename.str_value.str) < 0) + fatal(ext_id, _("inplace_end: rename(`%s', `%s') failed (%s)"), + state.tname, filename.str_value.str, strerror(errno)); + free(state.tname); + state.tname = NULL; + return make_number(0, result); +} + +static awk_ext_func_t func_table[] = { + { "inplace_begin", do_inplace_begin, 2 }, + { "inplace_end", do_inplace_end, 2 }, +}; + +static awk_bool_t init_inplace(void) +{ + awk_atexit(at_exit, NULL); + return awk_true; +} + +static awk_bool_t (*init_func)(void) = init_inplace; + +/* define the dl_load function using the boilerplate macro */ + +dl_load_func(func_table, inplace, "") |