Blame extension/inplace.c

Packit Service f629e6
/*
Packit Service f629e6
 * inplace.c - Provide support for in-place editing.
Packit Service f629e6
 */
Packit Service f629e6
Packit Service f629e6
/*
Packit Service f629e6
 * Copyright (C) 2013-2015 the Free Software Foundation, Inc.
Packit Service f629e6
 *
Packit Service f629e6
 * This file is part of GAWK, the GNU implementation of the
Packit Service f629e6
 * AWK Programming Language.
Packit Service f629e6
 *
Packit Service f629e6
 * GAWK is free software; you can redistribute it and/or modify
Packit Service f629e6
 * it under the terms of the GNU General Public License as published by
Packit Service f629e6
 * the Free Software Foundation; either version 3 of the License, or
Packit Service f629e6
 * (at your option) any later version.
Packit Service f629e6
 *
Packit Service f629e6
 * GAWK is distributed in the hope that it will be useful,
Packit Service f629e6
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service f629e6
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit Service f629e6
 * GNU General Public License for more details.
Packit Service f629e6
 *
Packit Service f629e6
 * You should have received a copy of the GNU General Public License
Packit Service f629e6
 * along with this program; if not, write to the Free Software
Packit Service f629e6
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
Packit Service f629e6
 */
Packit Service f629e6
Packit Service f629e6
#ifdef HAVE_CONFIG_H
Packit Service f629e6
#include <config.h>
Packit Service f629e6
#endif
Packit Service f629e6
Packit Service f629e6
#ifndef _XOPEN_SOURCE
Packit Service f629e6
# define _XOPEN_SOURCE 1
Packit Service f629e6
#endif
Packit Service f629e6
#ifndef _XOPEN_SOURCE_EXTENDED
Packit Service f629e6
# define _XOPEN_SOURCE_EXTENDED 1
Packit Service f629e6
#endif
Packit Service f629e6
Packit Service f629e6
#include <stdio.h>
Packit Service f629e6
#include <assert.h>
Packit Service f629e6
#include <errno.h>
Packit Service f629e6
#include <fcntl.h>
Packit Service f629e6
#include <stdlib.h>
Packit Service f629e6
#include <string.h>
Packit Service f629e6
#include <unistd.h>
Packit Service f629e6
Packit Service f629e6
#include <sys/types.h>
Packit Service f629e6
#include <sys/stat.h>
Packit Service f629e6
Packit Service f629e6
#include "gawkapi.h"
Packit Service f629e6
Packit Service f629e6
#include "gettext.h"
Packit Service f629e6
#define _(msgid)  gettext(msgid)
Packit Service f629e6
#define N_(msgid) msgid
Packit Service f629e6
Packit Service f629e6
#if ! defined(S_ISREG) && defined(S_IFREG)
Packit Service f629e6
#define	S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
Packit Service f629e6
#endif
Packit Service f629e6
Packit Service f629e6
#ifdef __MINGW32__
Packit Service f629e6
# define chown(x,y,z)  (0)
Packit Service f629e6
# define link(f1,f2)   rename(f1,f2)
Packit Service f629e6
int
Packit Service f629e6
mkstemp (char *template)
Packit Service f629e6
{
Packit Service f629e6
  char *tmp_fname = _mktemp (template);
Packit Service f629e6
Packit Service f629e6
  if (tmp_fname)
Packit Service f629e6
    return _open (tmp_fname, O_RDWR | O_CREAT | O_EXCL, S_IREAD | S_IWRITE);
Packit Service f629e6
  return -1;
Packit Service f629e6
}
Packit Service f629e6
#endif
Packit Service f629e6
Packit Service f629e6
static const gawk_api_t *api;	/* for convenience macros to work */
Packit Service f629e6
static awk_ext_id_t ext_id;
Packit Service f629e6
static const char *ext_version = "inplace extension: version 1.0";
Packit Service f629e6
Packit Service f629e6
int plugin_is_GPL_compatible;
Packit Service f629e6
Packit Service f629e6
static struct {
Packit Service f629e6
	char *tname;
Packit Service f629e6
	int default_stdout;
Packit Service f629e6
	int posrc;	/* return code from fgetpos */
Packit Service f629e6
	fpos_t pos;
Packit Service f629e6
} state = { NULL, -1 };
Packit Service f629e6
Packit Service f629e6
/*
Packit Service f629e6
 * XXX Known problems:
Packit Service f629e6
 * 1. Should copy ACL.
Packit Service f629e6
 * 2. Not reentrant, so will not work if multiple files are open at
Packit Service f629e6
 *    the same time.  I'm not sure this is a meaningful problem in practice.
Packit Service f629e6
 */
Packit Service f629e6
Packit Service f629e6
static void
Packit Service f629e6
at_exit(void *data, int exit_status)
Packit Service f629e6
{
Packit Service f629e6
	(void) data;		/* silence warnings */
Packit Service f629e6
	(void) exit_status;	/* silence warnings */
Packit Service f629e6
	if (state.tname) {
Packit Service f629e6
		unlink(state.tname);
Packit Service f629e6
		gawk_free(state.tname);
Packit Service f629e6
		state.tname = NULL;
Packit Service f629e6
	}
Packit Service f629e6
}
Packit Service f629e6
Packit Service f629e6
/*
Packit Service f629e6
 * N.B. Almost everything is a fatal error because this feature is typically
Packit Service f629e6
 * used for one-liners where the user is not going to be worrying about
Packit Service f629e6
 * checking errors.  If anything unexpected occurs, we want to abort
Packit Service f629e6
 * immediately!
Packit Service f629e6
 */
Packit Service f629e6
Packit Service f629e6
static int
Packit Service f629e6
invalid_filename(const awk_string_t *filename)
Packit Service f629e6
{
Packit Service f629e6
	return filename->len == 0 ||
Packit Service f629e6
		(filename->len == 1 && *filename->str == '-');
Packit Service f629e6
}
Packit Service f629e6
Packit Service f629e6
/* do_inplace_begin --- start in-place editing */
Packit Service f629e6
Packit Service f629e6
static awk_value_t *
Packit Service f629e6
do_inplace_begin(int nargs, awk_value_t *result, struct awk_ext_func *unused)
Packit Service f629e6
{
Packit Service f629e6
	awk_value_t filename;
Packit Service f629e6
	struct stat sbuf;
Packit Service f629e6
	int fd;
Packit Service f629e6
Packit Service f629e6
	assert(result != NULL);
Packit Service f629e6
	fflush(stdout);
Packit Service f629e6
Packit Service f629e6
	if (state.tname)
Packit Service f629e6
		fatal(ext_id, _("inplace_begin: in-place editing already active"));
Packit Service f629e6
Packit Service f629e6
	if (nargs != 2)
Packit Service f629e6
		fatal(ext_id, _("inplace_begin: expects 2 arguments but called with %d"), nargs);
Packit Service f629e6
Packit Service f629e6
	if (! get_argument(0, AWK_STRING, &filename))
Packit Service f629e6
		fatal(ext_id, _("inplace_begin: cannot retrieve 1st argument as a string filename"));
Packit Service f629e6
Packit Service f629e6
	/*
Packit Service f629e6
	 * N.B. In the current implementation, the 2nd suffix arg is not used
Packit Service f629e6
	 * in this function.  It is used only in the inplace_end function.
Packit Service f629e6
	 */
Packit Service f629e6
Packit Service f629e6
	if (invalid_filename(&filename.str_value)) {
Packit Service f629e6
		warning(ext_id, _("inplace_begin: disabling in-place editing for invalid FILENAME `%s'"),
Packit Service f629e6
			filename.str_value.str);
Packit Service f629e6
		unset_ERRNO();
Packit Service f629e6
		return make_number(-1, result);
Packit Service f629e6
	}
Packit Service f629e6
Packit Service f629e6
	if (stat(filename.str_value.str, & sbuf) < 0) {
Packit Service f629e6
		warning(ext_id, _("inplace_begin: Cannot stat `%s' (%s)"),
Packit Service f629e6
			filename.str_value.str, strerror(errno));
Packit Service f629e6
		update_ERRNO_int(errno);
Packit Service f629e6
		return make_number(-1, result);
Packit Service f629e6
	}
Packit Service f629e6
Packit Service f629e6
	if (! S_ISREG(sbuf.st_mode)) {
Packit Service f629e6
		warning(ext_id, _("inplace_begin: `%s' is not a regular file"),
Packit Service f629e6
			filename.str_value.str);
Packit Service f629e6
		unset_ERRNO();
Packit Service f629e6
		return make_number(-1, result);
Packit Service f629e6
	}
Packit Service f629e6
Packit Service f629e6
	/* create a temporary file to which to redirect stdout */
Packit Service f629e6
	emalloc(state.tname, char *, filename.str_value.len+14, "do_inplace_begin");
Packit Service f629e6
	sprintf(state.tname, "%s.gawk.XXXXXX", filename.str_value.str);
Packit Service f629e6
Packit Service f629e6
	if ((fd = mkstemp(state.tname)) < 0)
Packit Service f629e6
		fatal(ext_id, _("inplace_begin: mkstemp(`%s') failed (%s)"),
Packit Service f629e6
			state.tname, strerror(errno));
Packit Service f629e6
Packit Service f629e6
	/* N.B. chown/chmod should be more portable than fchown/fchmod */
Packit Service f629e6
	if (chown(state.tname, sbuf.st_uid, sbuf.st_gid) < 0) {
Packit Service f629e6
		/* jumping through hoops to silence gcc and clang. :-( */
Packit Service f629e6
		int junk;
Packit Service f629e6
		junk = chown(state.tname, -1, sbuf.st_gid);
Packit Service f629e6
		++junk;
Packit Service f629e6
	}
Packit Service f629e6
Packit Service f629e6
	if (chmod(state.tname, sbuf.st_mode) < 0)
Packit Service f629e6
		fatal(ext_id, _("inplace_begin: chmod failed (%s)"),
Packit Service f629e6
			strerror(errno));
Packit Service f629e6
Packit Service f629e6
	fflush(stdout);
Packit Service f629e6
	/* N.B. fgetpos fails when stdout is a tty */
Packit Service f629e6
	state.posrc = fgetpos(stdout, &state.pos);
Packit Service f629e6
	if ((state.default_stdout = dup(STDOUT_FILENO)) < 0)
Packit Service f629e6
		fatal(ext_id, _("inplace_begin: dup(stdout) failed (%s)"),
Packit Service f629e6
			strerror(errno));
Packit Service f629e6
	if (dup2(fd, STDOUT_FILENO) < 0)
Packit Service f629e6
		fatal(ext_id, _("inplace_begin: dup2(%d, stdout) failed (%s)"),
Packit Service f629e6
			fd, strerror(errno));
Packit Service f629e6
	if (close(fd) < 0)
Packit Service f629e6
		fatal(ext_id, _("inplace_begin: close(%d) failed (%s)"),
Packit Service f629e6
			fd, strerror(errno));
Packit Service f629e6
	rewind(stdout);
Packit Service f629e6
	return make_number(0, result);
Packit Service f629e6
}
Packit Service f629e6
Packit Service f629e6
/* do_inplace_end --- finish in-place editing */
Packit Service f629e6
Packit Service f629e6
static awk_value_t *
Packit Service f629e6
do_inplace_end(int nargs, awk_value_t *result, struct awk_ext_func *unused)
Packit Service f629e6
{
Packit Service f629e6
	awk_value_t filename, suffix;
Packit Service f629e6
Packit Service f629e6
	assert(result != NULL);
Packit Service f629e6
Packit Service f629e6
	if (nargs != 2)
Packit Service f629e6
		fatal(ext_id, _("inplace_end: expects 2 arguments but called with %d"), nargs);
Packit Service f629e6
Packit Service f629e6
	if (! get_argument(0, AWK_STRING, &filename))
Packit Service f629e6
		fatal(ext_id, _("inplace_end: cannot retrieve 1st argument as a string filename"));
Packit Service f629e6
Packit Service f629e6
	if (! get_argument(1, AWK_STRING, &suffix))
Packit Service f629e6
		suffix.str_value.str = NULL;
Packit Service f629e6
Packit Service f629e6
	if (! state.tname) {
Packit Service f629e6
		if (! invalid_filename(&filename.str_value))
Packit Service f629e6
			warning(ext_id, _("inplace_end: in-place editing not active"));
Packit Service f629e6
		return make_number(0, result);
Packit Service f629e6
	}
Packit Service f629e6
Packit Service f629e6
	fflush(stdout);
Packit Service f629e6
	if (dup2(state.default_stdout, STDOUT_FILENO) < 0)
Packit Service f629e6
		fatal(ext_id, _("inplace_end: dup2(%d, stdout) failed (%s)"),
Packit Service f629e6
			state.default_stdout, strerror(errno));
Packit Service f629e6
	if (close(state.default_stdout) < 0)
Packit Service f629e6
		fatal(ext_id, _("inplace_end: close(%d) failed (%s)"),
Packit Service f629e6
			state.default_stdout, strerror(errno));
Packit Service f629e6
	state.default_stdout = -1;
Packit Service f629e6
	if (state.posrc == 0 && fsetpos(stdout, &state.pos) < 0)
Packit Service f629e6
		fatal(ext_id, _("inplace_end: fsetpos(stdout) failed (%s)"),
Packit Service f629e6
			strerror(errno));
Packit Service f629e6
Packit Service f629e6
	if (suffix.str_value.str && suffix.str_value.str[0]) {
Packit Service f629e6
		/* backup requested */
Packit Service f629e6
		char *bakname;
Packit Service f629e6
Packit Service f629e6
		emalloc(bakname, char *, filename.str_value.len+suffix.str_value.len+1,
Packit Service f629e6
			"do_inplace_end");
Packit Service f629e6
		sprintf(bakname, "%s%s",
Packit Service f629e6
			filename.str_value.str, suffix.str_value.str);
Packit Service f629e6
		unlink(bakname); /* if backup file exists already, remove it */
Packit Service f629e6
		if (link(filename.str_value.str, bakname) < 0)
Packit Service f629e6
			fatal(ext_id, _("inplace_end: link(`%s', `%s') failed (%s)"),
Packit Service f629e6
				filename.str_value.str, bakname, strerror(errno));
Packit Service f629e6
		gawk_free(bakname);
Packit Service f629e6
	}
Packit Service f629e6
Packit Service f629e6
#ifdef __MINGW32__
Packit Service f629e6
	unlink(filename.str_value.str);
Packit Service f629e6
#endif
Packit Service f629e6
Packit Service f629e6
	if (rename(state.tname, filename.str_value.str) < 0)
Packit Service f629e6
		fatal(ext_id, _("inplace_end: rename(`%s', `%s') failed (%s)"),
Packit Service f629e6
			state.tname, filename.str_value.str, strerror(errno));
Packit Service f629e6
	gawk_free(state.tname);
Packit Service f629e6
	state.tname = NULL;
Packit Service f629e6
	return make_number(0, result);
Packit Service f629e6
}
Packit Service f629e6
Packit Service f629e6
static awk_ext_func_t func_table[] = {
Packit Service f629e6
	{ "inplace_begin", do_inplace_begin, 2, 2, awk_false, NULL },
Packit Service f629e6
	{ "inplace_end", do_inplace_end, 2, 2, awk_false, NULL },
Packit Service f629e6
};
Packit Service f629e6
Packit Service f629e6
static awk_bool_t init_inplace(void)
Packit Service f629e6
{
Packit Service f629e6
	awk_atexit(at_exit, NULL);
Packit Service f629e6
	return awk_true;
Packit Service f629e6
}
Packit Service f629e6
Packit Service f629e6
static awk_bool_t (*init_func)(void) = init_inplace;
Packit Service f629e6
Packit Service f629e6
/* define the dl_load function using the boilerplate macro */
Packit Service f629e6
Packit Service f629e6
dl_load_func(func_table, inplace, "")