/* * 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 #endif #include #include #include #include #include #include #include #include #include #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; } state = { NULL, -1 }; /* * XXX Known problem: * 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; } } /* 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 (do_lint && nargs > 2) lintwarn(ext_id, _("inplace_begin: called with too many arguments")); unset_ERRNO(); if (!get_argument(0, AWK_STRING, &filename)) { if (do_lint) { if (nargs == 0) lintwarn(ext_id, _("inplace_begin: called with no arguments")); else lintwarn(ext_id, _("inplace_begin: called with inappropriate argument(s)")); } return make_number(-1, result); } if (!get_argument(1, AWK_STRING, &suffix)) suffix.str_value.str = NULL; if (stat(filename.str_value.str, & sbuf) < 0) { update_ERRNO_int(errno); warning(ext_id, _("inplace_begin: Cannot stat `%s'"), filename.str_value.str); 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); return make_number(-1, result); } /* create a temporary file to which to redirect stdout */ emalloc(state.tname, char *, strlen(filename.str_value.str)+14, "do_inplace_begin"); if ((p = strrchr(filename.str_value.str, '/')) != NULL) { memcpy(state.tname, filename.str_value.str, p-filename.str_value.str); p = state.tname+(p-filename.str_value.str); } else { state.tname[0] = '.'; p = state.tname+1; } strcpy(p, "/gawk.XXXXXX"); if ((fd = mkstemp(state.tname)) < 0) fatal(ext_id, _("inplace_begin: mkstemp(`%s') failed"), state.tname); fchown(fd, sbuf.st_uid, sbuf.st_gid); if (fchmod(fd, sbuf.st_mode) < 0) fatal(ext_id, _("inplace_begin: fchmod failed")); fflush(stdout); if ((state.default_stdout = dup(STDOUT_FILENO)) < 0) fatal(ext_id, _("inplace_begin: dup(stdout) failed")); if (dup2(fd, STDOUT_FILENO) < 0) fatal(ext_id, _("inplace_begin: dup2(%d, stdout) failed"), fd); if (close(fd) < 0) fatal(ext_id, _("inplace_begin: close(%d) failed"), fd); 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 (!state.tname) /* in-place editing is not active! */ return make_number(0, result); if (do_lint && nargs > 2) lintwarn(ext_id, _("inplace_end: called with too many arguments")); unset_ERRNO(); if (!get_argument(0, AWK_STRING, &filename)) { if (do_lint) { if (nargs == 0) lintwarn(ext_id, _("inplace_end: called with no arguments")); else lintwarn(ext_id, _("inplace_end: called with inappropriate argument(s)")); } return make_number(-1, result); } if (!get_argument(1, AWK_STRING, &suffix)) suffix.str_value.str = NULL; fflush(stdout); if (dup2(state.default_stdout, STDOUT_FILENO) < 0) fatal(ext_id, _("inplace_end: dup2(%d, stdout) failed"), state.default_stdout); if (close(state.default_stdout) < 0) fatal(ext_id, _("inplace_end: close(%d) failed"), state.default_stdout); state.default_stdout = -1; if (suffix.str_value.str && suffix.str_value.str[0]) { /* backup requested */ char *bakname; emalloc(bakname, char *, strlen(filename.str_value.str)+strlen(suffix.str_value.str)+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"), filename.str_value.str, bakname); free(bakname); } if (rename(state.tname, filename.str_value.str) < 0) fatal(ext_id, _("inplace_end: rename(`%s', `%s') failed"), state.tname, filename.str_value.str); 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, "")