(root)/
gawk-5.2.2/
extension/
inplace.c
       1  /*
       2   * inplace.c - Provide support for in-place editing.
       3   */
       4  
       5  /*
       6   * Copyright (C) 2013-2015, 2017, 2018, the Free Software Foundation, Inc.
       7   *
       8   * This file is part of GAWK, the GNU implementation of the
       9   * AWK Programming Language.
      10   *
      11   * GAWK is free software; you can redistribute it and/or modify
      12   * it under the terms of the GNU General Public License as published by
      13   * the Free Software Foundation; either version 3 of the License, or
      14   * (at your option) any later version.
      15   *
      16   * GAWK is distributed in the hope that it will be useful,
      17   * but WITHOUT ANY WARRANTY; without even the implied warranty of
      18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      19   * GNU General Public License for more details.
      20   *
      21   * You should have received a copy of the GNU General Public License
      22   * along with this program; if not, write to the Free Software
      23   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
      24   */
      25  
      26  #ifdef HAVE_CONFIG_H
      27  #include <config.h>
      28  #endif
      29  
      30  #ifndef _XOPEN_SOURCE
      31  # define _XOPEN_SOURCE 1
      32  #endif
      33  #ifndef _XOPEN_SOURCE_EXTENDED
      34  # define _XOPEN_SOURCE_EXTENDED 1
      35  #endif
      36  
      37  #include <stdio.h>
      38  #include <assert.h>
      39  #include <errno.h>
      40  #include <fcntl.h>
      41  #include <stdlib.h>
      42  #include <string.h>
      43  #include <unistd.h>
      44  
      45  #include <sys/types.h>
      46  #include <sys/stat.h>
      47  
      48  #include "gawkapi.h"
      49  
      50  #include "gettext.h"
      51  #define _(msgid)  gettext(msgid)
      52  #define N_(msgid) msgid
      53  
      54  #if ! defined(S_ISREG) && defined(S_IFREG)
      55  #define	S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
      56  #endif
      57  
      58  #ifdef __MINGW32__
      59  # define chown(x,y,z)  (0)
      60  # define link(f1,f2)   rename(f1,f2)
      61  int
      62  mkstemp (char *template)
      63  {
      64    char *tmp_fname = _mktemp (template);
      65  
      66    if (tmp_fname)
      67      return _open (tmp_fname, O_RDWR | O_CREAT | O_EXCL, S_IREAD | S_IWRITE);
      68    return -1;
      69  }
      70  #endif
      71  
      72  static const gawk_api_t *api;	/* for convenience macros to work */
      73  static awk_ext_id_t ext_id;
      74  static const char *ext_version = "inplace extension: version 1.0";
      75  
      76  int plugin_is_GPL_compatible;
      77  
      78  static struct {
      79  	char *tname;
      80  	int default_stdout;
      81  	int posrc;	/* return code from fgetpos */
      82  	fpos_t pos;
      83  } state = { NULL, -1 };
      84  
      85  /*
      86   * XXX Known problems:
      87   * 1. Should copy ACL.
      88   * 2. Not reentrant, so will not work if multiple files are open at
      89   *    the same time.  I'm not sure this is a meaningful problem in practice.
      90   */
      91  
      92  static void
      93  at_exit(void *data, int exit_status)
      94  {
      95  	(void) data;		/* silence warnings */
      96  	(void) exit_status;	/* silence warnings */
      97  	if (state.tname) {
      98  		unlink(state.tname);
      99  		gawk_free(state.tname);
     100  		state.tname = NULL;
     101  	}
     102  }
     103  
     104  /*
     105   * N.B. Almost everything is a fatal error because this feature is typically
     106   * used for one-liners where the user is not going to be worrying about
     107   * checking errors.  If anything unexpected occurs, we want to abort
     108   * immediately!
     109   */
     110  
     111  static int
     112  invalid_filename(const awk_string_t *filename)
     113  {
     114  	return filename->len == 0 ||
     115  		(filename->len == 1 && *filename->str == '-');
     116  }
     117  
     118  /* do_inplace_begin --- start in-place editing */
     119  
     120  static awk_value_t *
     121  do_inplace_begin(int nargs, awk_value_t *result, struct awk_ext_func *unused)
     122  {
     123  	awk_value_t filename;
     124  	struct stat sbuf;
     125  	int fd;
     126  
     127  	assert(result != NULL);
     128  	fflush(stdout);
     129  
     130  	if (state.tname)
     131  		fatal(ext_id, _("inplace::begin: in-place editing already active"));
     132  
     133  	if (nargs != 2)
     134  		fatal(ext_id, _("inplace::begin: expects 2 arguments but called with %d"), nargs);
     135  
     136  	if (! get_argument(0, AWK_STRING, &filename))
     137  		fatal(ext_id, _("inplace::begin: cannot retrieve 1st argument as a string filename"));
     138  
     139  	/*
     140  	 * N.B. In the current implementation, the 2nd suffix arg is not used
     141  	 * in this function.  It is used only in the inplace_end function.
     142  	 */
     143  
     144  	if (invalid_filename(&filename.str_value)) {
     145  		warning(ext_id, _("inplace::begin: disabling in-place editing for invalid FILENAME `%s'"),
     146  			filename.str_value.str);
     147  		unset_ERRNO();
     148  		return make_number(-1, result);
     149  	}
     150  
     151  	if (stat(filename.str_value.str, & sbuf) < 0) {
     152  		warning(ext_id, _("inplace::begin: Cannot stat `%s' (%s)"),
     153  			filename.str_value.str, strerror(errno));
     154  		update_ERRNO_int(errno);
     155  		return make_number(-1, result);
     156  	}
     157  
     158  	if (! S_ISREG(sbuf.st_mode)) {
     159  		warning(ext_id, _("inplace::begin: `%s' is not a regular file"),
     160  			filename.str_value.str);
     161  		unset_ERRNO();
     162  		return make_number(-1, result);
     163  	}
     164  
     165  	/* create a temporary file to which to redirect stdout */
     166  	emalloc(state.tname, char *, filename.str_value.len+14, "do_inplace_begin");
     167  	sprintf(state.tname, "%s.gawk.XXXXXX", filename.str_value.str);
     168  
     169  	if ((fd = mkstemp(state.tname)) < 0)
     170  		fatal(ext_id, _("inplace::begin: mkstemp(`%s') failed (%s)"),
     171  			state.tname, strerror(errno));
     172  
     173  	/* N.B. chown/chmod should be more portable than fchown/fchmod */
     174  	if (chown(state.tname, sbuf.st_uid, sbuf.st_gid) < 0) {
     175  		/* jumping through hoops to silence gcc and clang. :-( */
     176  		int junk;
     177  		junk = chown(state.tname, -1, sbuf.st_gid);
     178  		++junk;
     179  	}
     180  
     181  	if (chmod(state.tname, sbuf.st_mode) < 0)
     182  		fatal(ext_id, _("inplace::begin: chmod failed (%s)"),
     183  			strerror(errno));
     184  
     185  	fflush(stdout);
     186  	/* N.B. fgetpos fails when stdout is a tty */
     187  	state.posrc = fgetpos(stdout, &state.pos);
     188  	if ((state.default_stdout = dup(STDOUT_FILENO)) < 0)
     189  		fatal(ext_id, _("inplace::begin: dup(stdout) failed (%s)"),
     190  			strerror(errno));
     191  	if (dup2(fd, STDOUT_FILENO) < 0)
     192  		fatal(ext_id, _("inplace::begin: dup2(%d, stdout) failed (%s)"),
     193  			fd, strerror(errno));
     194  	if (close(fd) < 0)
     195  		fatal(ext_id, _("inplace::begin: close(%d) failed (%s)"),
     196  			fd, strerror(errno));
     197  	rewind(stdout);
     198  	return make_number(0, result);
     199  }
     200  
     201  /* do_inplace_end --- finish in-place editing */
     202  
     203  static awk_value_t *
     204  do_inplace_end(int nargs, awk_value_t *result, struct awk_ext_func *unused)
     205  {
     206  	awk_value_t filename, suffix;
     207  
     208  	assert(result != NULL);
     209  
     210  	if (nargs != 2)
     211  		fatal(ext_id, _("inplace::end: expects 2 arguments but called with %d"), nargs);
     212  
     213  	if (! get_argument(0, AWK_STRING, &filename))
     214  		fatal(ext_id, _("inplace::end: cannot retrieve 1st argument as a string filename"));
     215  
     216  	if (! get_argument(1, AWK_STRING, &suffix))
     217  		suffix.str_value.str = NULL;
     218  
     219  	if (! state.tname) {
     220  		if (! invalid_filename(&filename.str_value))
     221  			warning(ext_id, _("inplace::end: in-place editing not active"));
     222  		return make_number(0, result);
     223  	}
     224  
     225  	fflush(stdout);
     226  	if (dup2(state.default_stdout, STDOUT_FILENO) < 0)
     227  		fatal(ext_id, _("inplace::end: dup2(%d, stdout) failed (%s)"),
     228  			state.default_stdout, strerror(errno));
     229  	if (close(state.default_stdout) < 0)
     230  		fatal(ext_id, _("inplace::end: close(%d) failed (%s)"),
     231  			state.default_stdout, strerror(errno));
     232  	state.default_stdout = -1;
     233  	if (state.posrc == 0 && fsetpos(stdout, &state.pos) < 0)
     234  		fatal(ext_id, _("inplace::end: fsetpos(stdout) failed (%s)"),
     235  			strerror(errno));
     236  
     237  	if (suffix.str_value.str && suffix.str_value.str[0]) {
     238  		/* backup requested */
     239  		char *bakname;
     240  
     241  		emalloc(bakname, char *, filename.str_value.len+suffix.str_value.len+1,
     242  			"do_inplace_end");
     243  		sprintf(bakname, "%s%s",
     244  			filename.str_value.str, suffix.str_value.str);
     245  		unlink(bakname); /* if backup file exists already, remove it */
     246  		if (link(filename.str_value.str, bakname) < 0)
     247  			fatal(ext_id, _("inplace::end: link(`%s', `%s') failed (%s)"),
     248  				filename.str_value.str, bakname, strerror(errno));
     249  		gawk_free(bakname);
     250  	}
     251  
     252  #ifdef __MINGW32__
     253  	unlink(filename.str_value.str);
     254  #endif
     255  
     256  	if (rename(state.tname, filename.str_value.str) < 0)
     257  		fatal(ext_id, _("inplace::end: rename(`%s', `%s') failed (%s)"),
     258  			state.tname, filename.str_value.str, strerror(errno));
     259  	gawk_free(state.tname);
     260  	state.tname = NULL;
     261  	return make_number(0, result);
     262  }
     263  
     264  static awk_ext_func_t func_table[] = {
     265  	{ "begin", do_inplace_begin, 2, 2, awk_false, NULL },
     266  	{ "end", do_inplace_end, 2, 2, awk_false, NULL },
     267  };
     268  
     269  static awk_bool_t init_inplace(void)
     270  {
     271  	awk_atexit(at_exit, NULL);
     272  	return awk_true;
     273  }
     274  
     275  static awk_bool_t (*init_func)(void) = init_inplace;
     276  
     277  /* define the dl_load function using the boilerplate macro */
     278  
     279  dl_load_func(func_table, inplace, "inplace")