aboutsummaryrefslogtreecommitdiffstats
path: root/extension
diff options
context:
space:
mode:
Diffstat (limited to 'extension')
-rw-r--r--extension/ChangeLog5
-rw-r--r--extension/Makefile.am5
-rw-r--r--extension/Makefile.in29
-rw-r--r--extension/inplace.c241
4 files changed, 272 insertions, 8 deletions
diff --git a/extension/ChangeLog b/extension/ChangeLog
index 3091fac8..7fea3bf0 100644
--- a/extension/ChangeLog
+++ b/extension/ChangeLog
@@ -1,3 +1,8 @@
+2013-01-08 Andrew J. Schorr <aschorr@telemetry-investments.com>
+
+ * inplace.c: New extension to implement in-place editing.
+ * Makefile.am: Add inplace extension.
+
2012-12-25 Arnold D. Robbins <arnold@skeeve.com>
* filefuncs.3am, fnmatch.3am: Predefined variables are no
diff --git a/extension/Makefile.am b/extension/Makefile.am
index 33902a6d..d5977f2e 100644
--- a/extension/Makefile.am
+++ b/extension/Makefile.am
@@ -35,6 +35,7 @@ pkgextension_LTLIBRARIES = \
filefuncs.la \
fnmatch.la \
fork.la \
+ inplace.la \
ordchr.la \
readdir.la \
readfile.la \
@@ -60,6 +61,10 @@ fork_la_SOURCES = fork.c
fork_la_LDFLAGS = $(MY_MODULE_FLAGS)
fork_la_LIBADD = $(MY_LIBS)
+inplace_la_SOURCES = inplace.c
+inplace_la_LDFLAGS = $(MY_MODULE_FLAGS)
+inplace_la_LIBADD = $(MY_LIBS)
+
ordchr_la_SOURCES = ordchr.c
ordchr_la_LDFLAGS = $(MY_MODULE_FLAGS)
ordchr_la_LIBADD = $(MY_LIBS)
diff --git a/extension/Makefile.in b/extension/Makefile.in
index e0bb2430..21a56636 100644
--- a/extension/Makefile.in
+++ b/extension/Makefile.in
@@ -155,6 +155,12 @@ fork_la_OBJECTS = $(am_fork_la_OBJECTS)
fork_la_LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
--mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(fork_la_LDFLAGS) \
$(LDFLAGS) -o $@
+inplace_la_DEPENDENCIES = $(am__DEPENDENCIES_2)
+am_inplace_la_OBJECTS = inplace.lo
+inplace_la_OBJECTS = $(am_inplace_la_OBJECTS)
+inplace_la_LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(inplace_la_LDFLAGS) $(LDFLAGS) -o $@
ordchr_la_DEPENDENCIES = $(am__DEPENDENCIES_2)
am_ordchr_la_OBJECTS = ordchr.lo
ordchr_la_OBJECTS = $(am_ordchr_la_OBJECTS)
@@ -217,15 +223,15 @@ LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
--mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \
$(LDFLAGS) -o $@
SOURCES = $(filefuncs_la_SOURCES) $(fnmatch_la_SOURCES) \
- $(fork_la_SOURCES) $(ordchr_la_SOURCES) $(readdir_la_SOURCES) \
- $(readfile_la_SOURCES) $(revoutput_la_SOURCES) \
- $(revtwoway_la_SOURCES) $(rwarray_la_SOURCES) \
- $(testext_la_SOURCES) $(time_la_SOURCES)
+ $(fork_la_SOURCES) $(inplace_la_SOURCES) $(ordchr_la_SOURCES) \
+ $(readdir_la_SOURCES) $(readfile_la_SOURCES) \
+ $(revoutput_la_SOURCES) $(revtwoway_la_SOURCES) \
+ $(rwarray_la_SOURCES) $(testext_la_SOURCES) $(time_la_SOURCES)
DIST_SOURCES = $(filefuncs_la_SOURCES) $(fnmatch_la_SOURCES) \
- $(fork_la_SOURCES) $(ordchr_la_SOURCES) $(readdir_la_SOURCES) \
- $(readfile_la_SOURCES) $(revoutput_la_SOURCES) \
- $(revtwoway_la_SOURCES) $(rwarray_la_SOURCES) \
- $(testext_la_SOURCES) $(time_la_SOURCES)
+ $(fork_la_SOURCES) $(inplace_la_SOURCES) $(ordchr_la_SOURCES) \
+ $(readdir_la_SOURCES) $(readfile_la_SOURCES) \
+ $(revoutput_la_SOURCES) $(revtwoway_la_SOURCES) \
+ $(rwarray_la_SOURCES) $(testext_la_SOURCES) $(time_la_SOURCES)
RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \
html-recursive info-recursive install-data-recursive \
install-dvi-recursive install-exec-recursive \
@@ -434,6 +440,7 @@ pkgextension_LTLIBRARIES = \
filefuncs.la \
fnmatch.la \
fork.la \
+ inplace.la \
ordchr.la \
readdir.la \
readfile.la \
@@ -455,6 +462,9 @@ fnmatch_la_LIBADD = $(MY_LIBS)
fork_la_SOURCES = fork.c
fork_la_LDFLAGS = $(MY_MODULE_FLAGS)
fork_la_LIBADD = $(MY_LIBS)
+inplace_la_SOURCES = inplace.c
+inplace_la_LDFLAGS = $(MY_MODULE_FLAGS)
+inplace_la_LIBADD = $(MY_LIBS)
ordchr_la_SOURCES = ordchr.c
ordchr_la_LDFLAGS = $(MY_MODULE_FLAGS)
ordchr_la_LIBADD = $(MY_LIBS)
@@ -587,6 +597,8 @@ fnmatch.la: $(fnmatch_la_OBJECTS) $(fnmatch_la_DEPENDENCIES) $(EXTRA_fnmatch_la_
$(fnmatch_la_LINK) -rpath $(pkgextensiondir) $(fnmatch_la_OBJECTS) $(fnmatch_la_LIBADD) $(LIBS)
fork.la: $(fork_la_OBJECTS) $(fork_la_DEPENDENCIES) $(EXTRA_fork_la_DEPENDENCIES)
$(fork_la_LINK) -rpath $(pkgextensiondir) $(fork_la_OBJECTS) $(fork_la_LIBADD) $(LIBS)
+inplace.la: $(inplace_la_OBJECTS) $(inplace_la_DEPENDENCIES) $(EXTRA_inplace_la_DEPENDENCIES)
+ $(inplace_la_LINK) -rpath $(pkgextensiondir) $(inplace_la_OBJECTS) $(inplace_la_LIBADD) $(LIBS)
ordchr.la: $(ordchr_la_OBJECTS) $(ordchr_la_DEPENDENCIES) $(EXTRA_ordchr_la_DEPENDENCIES)
$(ordchr_la_LINK) -rpath $(pkgextensiondir) $(ordchr_la_OBJECTS) $(ordchr_la_LIBADD) $(LIBS)
readdir.la: $(readdir_la_OBJECTS) $(readdir_la_DEPENDENCIES) $(EXTRA_readdir_la_DEPENDENCIES)
@@ -614,6 +626,7 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fnmatch.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fork.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gawkfts.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/inplace.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ordchr.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/readdir.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/readfile.Plo@am__quote@
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, "")