I proposed in place editing support earlier (21 Mar 2009), but then you could not accept the patch because (a) I had not done legal paperwork (b) it was not awesome. Now legal matters should no longer be issue, but quite frankly the point b could still be a problem.
Last time I got few instructions how the in place could be better. So here is the new version, that uses conventions of other commands; like SIMPLE_BACKUP_SUFFIX & VERSION_CONTROL. Even without making in place working with fmt, fold, nl, tac, tr & expand the patch is pretty large, and potentially causes flock of bugs. There is also issue with permission bits when combined with backups. To fix that copy.c requires some sort of, small or big, change, and I don't feel good doing such before talking to maintainer first. I am afraid Pádraig was right last time; '[...] On the other hand it's simple enough to achieve this using existing commands'. How about you, now when it's more visible what's required when in place uses copy.c etc. Is it time to document this change to rejected ideas area, and never propose this again? Or keep on hacking to this direction? lib/in-place.c | 340 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/in-place.h | 24 ++++ m4/in-place.m4 | 18 +++ m4/prereq.m4 | 1 + src/Makefile.am | 5 + src/cp.c | 20 +--- src/install.c | 14 +-- src/ln.c | 19 +--- src/mv.c | 14 +-- src/system.h | 24 ++++ src/unexpand.c | 107 +++++++++++++---- 11 files changed, 498 insertions(+), 88 deletions(-) -- Sami Kerola http://www.iki.fi/kerolasa/
From d2efb1fb0ebf49b06df59f35a2589ced96d4468c Mon Sep 17 00:00:00 2001 From: Sami Kerola <[email protected]> Date: Sun, 7 Nov 2010 20:24:03 +0100 Subject: [PATCH] unexpand: support --in-place option * src/unexpand.c: In-place editing introduced. * lib/in-place.c: A new file, shared in-place functionality. * lib/in-place.h: A new file, header for in-place. * m4/in-place.m4: A new file, necessary m4 macros that the in-place will build. * m4/prereq.m4: Directives for automake to notice in-place files. * src/Makefile.am: The unexpand is using functions copy & remove. * src/system.h: New function emit_backup_info. * src/cp.c: Print emit_backup_info instead of own copy of the message. * src/ln.c: Print emit_backup_info instead of own copy of the message. * src/mv.c: Print emit_backup_info instead of own copy of the message. --- lib/in-place.c | 340 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/in-place.h | 24 ++++ m4/in-place.m4 | 18 +++ m4/prereq.m4 | 1 + src/Makefile.am | 5 + src/cp.c | 20 +--- src/install.c | 14 +-- src/ln.c | 19 +--- src/mv.c | 14 +-- src/system.h | 24 ++++ src/unexpand.c | 107 +++++++++++++---- 11 files changed, 498 insertions(+), 88 deletions(-) create mode 100644 lib/in-place.c create mode 100644 lib/in-place.h create mode 100644 m4/in-place.m4 diff --git a/lib/in-place.c b/lib/in-place.c new file mode 100644 index 0000000..60c292c --- /dev/null +++ b/lib/in-place.c @@ -0,0 +1,340 @@ +/* in-place.c -- support functions for --in-place option + + Copyright (C) 2010 Free Software Foundation, Inc. + + This program 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. + + This program 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, see <http://www.gnu.org/licenses/>. */ + +/* Written by Sami Kerola. */ + +#include <config.h> +#include <selinux/selinux.h> +#include <unistd.h> +#include <stdlib.h> +#include <signal.h> +#include <assert.h> + +#include "in-place.h" +#include "backupfile.h" +#include "../src/copy.h" +#include "../src/cp-hash.h" +#include "../src/remove.h" +#include "root-dev-ino.h" +#include "filenamecat.h" +#include "tempname.h" +#include "quote.h" +#include "error.h" +#include "gettext.h" +#include "closeout.h" +#include "argmatch.h" +#include "xalloc.h" + +#define _(msgid) gettext (msgid) + +/* Path to mktemp file, needed when signal handler does cleanup. */ +const char *in_place_tmpfile; + +/* The set of signals that are caught. */ +static sigset_t caught_signals; + +/* Settings to put mktemp file back in place. */ +struct cp_options x; + +/* Critical section status. */ +struct cs_status +{ + bool valid; + sigset_t sigs; +}; + +/* Enter a critical section. */ +static struct cs_status +cs_enter (void) +{ + struct cs_status status; + status.valid = (sigprocmask (SIG_BLOCK, &caught_signals, &status.sigs) == 0); + return status; +} + +/* Leave a critical section. */ +static void +cs_leave (struct cs_status status) +{ + if (status.valid) + { + /* Ignore failure when restoring the signal mask. */ + sigprocmask (SIG_SETMASK, &status.sigs, NULL); + } +} + +static void +exit_cleanup (void) +{ + if (in_place_tmpfile != NULL) + { + /* Clean up any remaining temporary files in a critical section so + that a signal handler does not try to clean them too. */ + struct cs_status cs = cs_enter (); + unlink(in_place_tmpfile); + cs_leave (cs); + } + + close_stdout (); +} + +/* Handle interrupts and hangups. */ +static void +sighandler (int sig) +{ + if (! SA_NOCLDSTOP) + signal (sig, SIG_IGN); + + exit_cleanup (); + + signal (sig, SIG_DFL); + raise (sig); +} + +static void +cp_option_init (struct cp_options *x) +{ + cp_options_default (x); + x->copy_as_regular = true; + x->reflink_mode = REFLINK_NEVER; + x->dereference = DEREF_NEVER; + x->unlink_dest_before_opening = false; + x->unlink_dest_after_failed_open = false; + x->hard_link = false; + x->interactive = I_UNSPECIFIED; + x->move_mode = false; + x->one_file_system = false; + + x->preserve_ownership = false; + x->preserve_links = false; + x->preserve_mode = false; + x->preserve_timestamps = false; + x->preserve_security_context = false; + x->require_preserve = false; + x->require_preserve_context = false; + x->preserve_xattr = false; + x->require_preserve_xattr = false; + + x->data_copy_required = true; + x->recursive = false; + x->sparse_mode = SPARSE_AUTO; + x->symbolic_link = false; + x->set_mode = false; + x->mode = 0; + x->stdin_tty = false; + + x->open_dangling_dest_symlink = false; + x->update = false; + x->reduce_diagnostics = false; + x->verbose = false; + x->dest_info = NULL; + x->src_info = NULL; +} + +static void +rm_option_init (struct rm_options *x) +{ + x->ignore_missing_files = false; + x->recursive = false; + x->one_file_system = true; + + /* In place should never prompt for removal */ + x->interactive = RMI_NEVER; + x->stdin_tty = false; + + x->verbose = false; + + /* Since this program may well have to process additional command + line arguments after any call to `rm', that function must preserve + the initial working directory, in case one of those is a + `.'-relative name. */ + x->require_restore_cwd = true; + + { + static struct dev_ino dev_ino_buf; + x->root_dev_ino = get_root_dev_ino (&dev_ino_buf); + if (x->root_dev_ino == NULL) + error (EXIT_FAILURE, errno, _("failed to get attributes of %s"), + quote ("/")); + } +} + +void +in_place_init(const struct in_place_options *y) +{ + char *backup_suffix_string; + + cp_option_init (&x); + if (y->make_backups) + { + if (y->backup_suffix_string) + simple_backup_suffix = xstrdup (y->backup_suffix_string); + x.backup_type = xget_version (_("backup type"), + y->version_control_string); + } + else + { + x.backup_type = no_backups; + } + + hash_init (); + + in_place_tmpfile = NULL; + + { + size_t i; + static int const sig[] = + { + /* The usual suspects. */ + SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM, +#ifdef SIGPOLL + SIGPOLL, +#endif +#ifdef SIGPROF + SIGPROF, +#endif +#ifdef SIGVTALRM + SIGVTALRM, +#endif +#ifdef SIGXCPU + SIGXCPU, +#endif +#ifdef SIGXFSZ + SIGXFSZ, +#endif + }; + enum { nsigs = ARRAY_CARDINALITY (sig) }; + +#if SA_NOCLDSTOP + struct sigaction act; + + sigemptyset (&caught_signals); + for (i = 0; i < nsigs; i++) + { + sigaction (sig[i], NULL, &act); + if (act.sa_handler != SIG_IGN) + sigaddset (&caught_signals, sig[i]); + } + + act.sa_handler = sighandler; + act.sa_mask = caught_signals; + act.sa_flags = 0; + + for (i = 0; i < nsigs; i++) + if (sigismember (&caught_signals, sig[i])) + sigaction (sig[i], &act, NULL); +#else + for (i = 0; i < nsigs; i++) + if (signal (sig[i], SIG_IGN) != SIG_IGN) + { + signal (sig[i], sighandler); + siginterrupt (sig[i], 1); + } +#endif + } + signal (SIGCHLD, SIG_DFL); /* Don't inherit CHLD handling from parent. */ + + /* The signal mask is known, so it is safe to invoke exit_cleanup. */ + atexit (exit_cleanup); +} + +int +in_place_open(struct iofiles *iofile, const struct in_place_options *y) +{ + char *template = "in-place.XXXXXXXXXX"; + char *dest_dir; + char *dest_name; + char *env = getenv ("TMPDIR"); + int fd; + + iofile->in = fopen (iofile->in_name, "r"); + if (iofile->in == NULL) + { + error (0, errno, "%s", iofile->in_name); + exit (EXIT_FAILURE); + } + + dest_dir = (env && *env ? env : "/tmp"); + dest_name = file_name_concat (dest_dir, template, NULL); + /* Static lengths are not stylish but they work. */ + fd = gen_tempname_len (dest_name, 0, 10, 0, (size_t) 10); + if (fd < 0) + { + error (0, errno, _("failed to create file via template %s"), + quote (template)); + exit (EXIT_FAILURE); + } + + in_place_tmpfile = dest_name; + + iofile->out = fdopen(fd, "w"); + if (iofile->out == NULL) + { + error (0, errno, "%s", dest_name); + return false; + } + iofile->out_name = dest_name; + + return true; +} + +int +in_place_close(struct iofiles *iofile, const struct in_place_options *y) +{ + bool unused; + struct rm_options rm_options; + enum RM_status status; + char const *dir[2]; + + rm_option_init (&rm_options); + rm_options.verbose = x.verbose; + dir[0] = iofile->out_name; + dir[1] = NULL; + + if (fclose(iofile->in)) + { + error (0, errno, "%s", iofile->in_name); + return false; + } + if (fclose(iofile->out)) + { + error (0, errno, "%s", iofile->out_name); + return false; + } + + + /* Return value of the copy is not meaningful for in place. */ + /* FIXME: If backup is asked file permissions are 0600, which + come from mktemp. The permissions should be same as + destination file had. Unfortunately the copy_internal + dst_backup string is not reachable, but if it would copy_acl + could be used. For time being this remains as bug. */ + copy (iofile->out_name, iofile->in_name, false, &x, &unused, &unused); + + /* Why copy instead of moving file? With copy destination + permissions can survice, move will use source permissisons + which in case of mktemp are rather restrictive, and wrong to + be used as default. */ + status = rm ((void*) dir, &rm_options); + assert (VALID_STATUS (status)); + if (status == RM_ERROR) + return false; + + in_place_tmpfile = NULL; + + return true; +} diff --git a/lib/in-place.h b/lib/in-place.h new file mode 100644 index 0000000..cd563b7 --- /dev/null +++ b/lib/in-place.h @@ -0,0 +1,24 @@ +#include <stdio.h> +#include <stdbool.h> + +/* This struct is expected to be globally available if in place + editing is attempted to use. */ +struct iofiles { + char *in_name; + FILE *in; + char *out_name; + FILE *out; +}; + +/* In place editing needs to receive few getopt arguments. */ +struct in_place_options +{ + bool is_in_place_called; + bool make_backups; + char *backup_suffix_string; + char *version_control_string; +}; + +void in_place_init (const struct in_place_options *); +int in_place_open (struct iofiles *, const struct in_place_options *); +int in_place_close (struct iofiles *, const struct in_place_options *); diff --git a/m4/in-place.m4 b/m4/in-place.m4 new file mode 100644 index 0000000..a564934 --- /dev/null +++ b/m4/in-place.m4 @@ -0,0 +1,18 @@ +# For in-place option + +dnl Copyright (C) 2010 Free Software Foundation, Inc. + +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl Written by Sami Kerola. + +AC_DEFUN([gl_IN_PLACE], +[ + AC_LIBSOURCES([in-place.c, in-place.h]) + AC_LIBOBJ([in-place]) + + dnl Prerequisites of lib/in-place.c. + AC_REQUIRE([AC_INLINE]) +]) diff --git a/m4/prereq.m4 b/m4/prereq.m4 index 8caea38..731f16e 100644 --- a/m4/prereq.m4 +++ b/m4/prereq.m4 @@ -42,4 +42,5 @@ AC_DEFUN([gl_PREREQ], AC_REQUIRE([gl_FUNC_XFTS]) AC_REQUIRE([gl_STRINTCMP]) AC_REQUIRE([gl_STRNUMCMP]) + AC_REQUIRE([gl_IN_PLACE]) ]) diff --git a/src/Makefile.am b/src/Makefile.am index 00c7ff7..4f05950 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -299,10 +299,12 @@ copy_LDADD = cp_LDADD += $(copy_LDADD) ginstall_LDADD += $(copy_LDADD) mv_LDADD += $(copy_LDADD) +unexpand_LDADD += $(copy_LDADD) remove_LDADD = mv_LDADD += $(remove_LDADD) rm_LDADD += $(remove_LDADD) +unexpand_LDADD += $(remove_LDADD) # for eaccess, euidaccess copy_LDADD += $(LIB_EACCESS) @@ -320,6 +322,7 @@ mkfifo_LDADD += $(LIB_SELINUX) mknod_LDADD += $(LIB_SELINUX) runcon_LDADD += $(LIB_SELINUX) stat_LDADD += $(LIB_SELINUX) +unexpand_LDADD += $(LIB_SELINUX) # for gettime, settime, utimecmp, utimens copy_LDADD += $(LIB_CLOCK_GETTIME) @@ -501,6 +504,8 @@ sha512sum_CPPFLAGS = -DHASH_ALGO_SHA512=1 $(AM_CPPFLAGS) ginstall_CPPFLAGS = -DENABLE_MATCHPATHCON=1 $(AM_CPPFLAGS) +unexpand_SOURCES = unexpand.c remove.c $(copy_sources) + # Ensure we don't link against libcoreutils.a as that lib is # not compiled with -fPIC which causes issues on 64 bit at least libstdbuf_so_LDADD = diff --git a/src/cp.c b/src/cp.c index 5b14f3a..7c7bec1 100644 --- a/src/cp.c +++ b/src/cp.c @@ -241,25 +241,7 @@ When --reflink[=always] is specified, perform a lightweight copy, where the\n\ data blocks are copied only when modified. If this is not possible the copy\n\ fails, or if --reflink=auto is specified, fall back to a standard copy.\n\ "), stdout); - fputs (_("\ -\n\ -The backup suffix is `~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\ -The version control method may be selected via the --backup option or through\n\ -the VERSION_CONTROL environment variable. Here are the values:\n\ -\n\ -"), stdout); - fputs (_("\ - none, off never make backups (even if --backup is given)\n\ - numbered, t make numbered backups\n\ - existing, nil numbered if numbered backups exist, simple otherwise\n\ - simple, never always make simple backups\n\ -"), stdout); - fputs (_("\ -\n\ -As a special case, cp makes a backup of SOURCE when the force and backup\n\ -options are given and SOURCE and DEST are the same name for an existing,\n\ -regular file.\n\ -"), stdout); + emit_backup_info (); emit_ancillary_info (); } exit (status); diff --git a/src/install.c b/src/install.c index 467e500..fede4b3 100644 --- a/src/install.c +++ b/src/install.c @@ -991,19 +991,7 @@ Mandatory arguments to long options are mandatory for short options too.\n\ fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); - fputs (_("\ -\n\ -The backup suffix is `~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\ -The version control method may be selected via the --backup option or through\n\ -the VERSION_CONTROL environment variable. Here are the values:\n\ -\n\ -"), stdout); - fputs (_("\ - none, off never make backups (even if --backup is given)\n\ - numbered, t make numbered backups\n\ - existing, nil numbered if numbered backups exist, simple otherwise\n\ - simple, never always make simple backups\n\ -"), stdout); + emit_backup_info (); emit_ancillary_info (); } exit (status); diff --git a/src/ln.c b/src/ln.c index 672964c..c90ff81 100644 --- a/src/ln.c +++ b/src/ln.c @@ -378,24 +378,7 @@ Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); - fputs (_("\ -\n\ -The backup suffix is `~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\ -The version control method may be selected via the --backup option or through\n\ -the VERSION_CONTROL environment variable. Here are the values:\n\ -\n\ -"), stdout); - printf (_("\ -Using -s ignores -L and -P. Otherwise, the last option specified controls\n\ -behavior when the source is a symbolic link, defaulting to %s.\n\ -\n\ -"), LINK_FOLLOWS_SYMLINKS ? "-L" : "-P"); - fputs (_("\ - none, off never make backups (even if --backup is given)\n\ - numbered, t make numbered backups\n\ - existing, nil numbered if numbered backups exist, simple otherwise\n\ - simple, never always make simple backups\n\ -"), stdout); + emit_backup_info (); emit_ancillary_info (); } exit (status); diff --git a/src/mv.c b/src/mv.c index 91aadfa..d700e26 100644 --- a/src/mv.c +++ b/src/mv.c @@ -318,19 +318,7 @@ If you specify more than one of -i, -f, -n, only the final one takes effect.\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); - fputs (_("\ -\n\ -The backup suffix is `~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\ -The version control method may be selected via the --backup option or through\n\ -the VERSION_CONTROL environment variable. Here are the values:\n\ -\n\ -"), stdout); - fputs (_("\ - none, off never make backups (even if --backup is given)\n\ - numbered, t make numbered backups\n\ - existing, nil numbered if numbered backups exist, simple otherwise\n\ - simple, never always make simple backups\n\ -"), stdout); + emit_backup_info (); emit_ancillary_info (); } exit (status); diff --git a/src/system.h b/src/system.h index 9e14681..506e228 100644 --- a/src/system.h +++ b/src/system.h @@ -591,6 +591,30 @@ Otherwise, units default to 1024 bytes (or 512 if POSIXLY_CORRECT is set).\n\ } static inline void +emit_backup_info (void) +{ + fputs (_("\ +\n\ +The backup suffix is `~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\ +The version control method may be selected via the --backup option or through\n\ +the VERSION_CONTROL environment variable. Here are the values:\n\ +\n\ +"), stdout); + fputs (_("\ + none, off never make backups (even if --backup is given)\n\ + numbered, t make numbered backups\n\ + existing, nil numbered if numbered backups exist, simple otherwise\n\ + simple, never always make simple backups\n\ +"), stdout); + fputs (_("\ +\n\ +As a special case, cp makes a backup of SOURCE when the force and backup\n\ +options are given and SOURCE and DEST are the same name for an existing,\n\ +regular file.\n\ +"), stdout); +} + +static inline void emit_ancillary_info (void) { printf (_("\nReport %s bugs to %s\n"), last_component (program_name), diff --git a/src/unexpand.c b/src/unexpand.c index 14b8df0..e05e479 100644 --- a/src/unexpand.c +++ b/src/unexpand.c @@ -44,6 +44,7 @@ #include "fadvise.h" #include "quote.h" #include "xstrndup.h" +#include "in-place.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "unexpand" @@ -54,6 +55,9 @@ read on the line. */ static bool convert_entire_line; +/* In and output files and streams. */ +struct iofiles iofile; + /* If nonzero, the size of all tab stops. If zero, use `tab_list' instead. */ static size_t tab_size; @@ -91,7 +95,8 @@ static int exit_status; non-character as a pseudo short option, starting with CHAR_MAX + 1. */ enum { - CONVERT_FIRST_ONLY_OPTION = CHAR_MAX + 1 + CONVERT_FIRST_ONLY_OPTION = CHAR_MAX + 1, + IN_PLACE_OPTION }; static struct option const longopts[] = @@ -99,6 +104,9 @@ static struct option const longopts[] = {"tabs", required_argument, NULL, 't'}, {"all", no_argument, NULL, 'a'}, {"first-only", no_argument, NULL, CONVERT_FIRST_ONLY_OPTION}, + {"in-place", no_argument, NULL, IN_PLACE_OPTION}, + {"backup", optional_argument, NULL, 'b'}, + {"suffix", required_argument, NULL, 'S'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} @@ -129,9 +137,14 @@ Mandatory arguments to long options are mandatory for short options too.\n\ --first-only convert only leading sequences of blanks (overrides -a)\n\ -t, --tabs=N have tabs N characters apart instead of 8 (enables -a)\n\ -t, --tabs=LIST use comma separated LIST of tab positions (enables -a)\n\ + --in-place edit files in place\n\ + --backup[=CONTROL] make a backup of each in place edited file\n\ + -b like --backup but does not accept an argument\n\ + -S, --suffix=SUFFIX override the usual backup suffix\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); + emit_backup_info (); emit_ancillary_info (); } exit (status); @@ -236,64 +249,85 @@ validate_tab_stops (uintmax_t const *tabs, size_t entries) Open a filename of `-' as the standard input. Return NULL if there are no more input files. */ -static FILE * -next_file (FILE *fp) +bool +next_file(struct iofiles *iofile, struct in_place_options *y) { static char *prev_file; char *file; - if (fp) + if (iofile->in) { - if (ferror (fp)) + if (ferror (iofile->in)) { error (0, errno, "%s", prev_file); exit_status = EXIT_FAILURE; } if (STREQ (prev_file, "-")) - clearerr (fp); /* Also clear EOF. */ - else if (fclose (fp) != 0) + clearerr (iofile->in); /* Also clear EOF. */ + else if (y->is_in_place_called) + { + if ((in_place_close(iofile, y)) != 0) + exit_status = EXIT_FAILURE; + } + else if (fclose (iofile->in) != 0) { error (0, errno, "%s", prev_file); exit_status = EXIT_FAILURE; } } - while ((file = *file_list++) != NULL) + while ((iofile->in_name = *file_list++) != NULL) { - if (STREQ (file, "-")) + if (STREQ (iofile->in_name, "-")) { have_read_stdin = true; - fp = stdin; + iofile->in = stdin; + if (y->is_in_place_called) + { + fprintf (stderr, + _("warning: reading from stdin, in-place editing is turned off\n")); + y->is_in_place_called = false; + } + } + else if (y->is_in_place_called) + { + if ((in_place_open(iofile, y)) != 0) + exit_status = EXIT_FAILURE; } else - fp = fopen (file, "r"); - if (fp) + iofile->in = fopen (iofile->in_name, "r"); + if (iofile->in) { - prev_file = file; - fadvise (fp, FADVISE_SEQUENTIAL); - return fp; + prev_file = iofile->in_name; + fadvise (iofile->in, FADVISE_SEQUENTIAL); + return true; } error (0, errno, "%s", file); exit_status = EXIT_FAILURE; } - return NULL; + return false; } /* Change blanks to tabs, writing to stdout. Read each file in `file_list', in order. */ static void -unexpand (void) +unexpand (struct in_place_options *y) { - /* Input stream. */ - FILE *fp = next_file (NULL); + /* Input and output file handles and paths. */ + iofile.in_name = NULL; + iofile.in = NULL; + iofile.out_name = NULL; + iofile.out = stdout; + + next_file (&iofile, y); /* The array of pending blanks. In non-POSIX locales, blanks can include characters other than spaces, so the blanks must be stored, not merely counted. */ char *pending_blank; - if (!fp) + if (!iofile.in) return; /* The worst case is a non-blank character, then one blank, then a @@ -338,7 +372,8 @@ unexpand (void) do { - while ((c = getc (fp)) < 0 && (fp = next_file (fp))) + while ((c = getc (iofile.in)) < 0 && + (next_file (&iofile, y))) continue; if (convert) @@ -425,7 +460,7 @@ unexpand (void) if (pending) { - if (fwrite (pending_blank, 1, pending, stdout) != pending) + if (fwrite (pending_blank, 1, pending, iofile.out) != pending) error (EXIT_FAILURE, errno, _("write error")); pending = 0; one_blank_before_tab_stop = false; @@ -441,7 +476,7 @@ unexpand (void) return; } - if (putchar (c) < 0) + if (fputc (c, iofile.out) < 0) error (EXIT_FAILURE, errno, _("write error")); } while (c != '\n'); @@ -452,8 +487,13 @@ int main (int argc, char **argv) { bool have_tabval = false; + bool make_backups = false; uintmax_t tabval IF_LINT ( = 0); int c; + struct in_place_options y; + y.is_in_place_called = false; + y.backup_suffix_string = NULL; + y.version_control_string = NULL; /* If true, cancel the effect of any -a (explicit or implicit in -t), so that only leading blanks will be considered. */ @@ -473,7 +513,7 @@ main (int argc, char **argv) tab_list = NULL; first_free_tab = 0; - while ((c = getopt_long (argc, argv, ",0123456789at:", longopts, NULL)) + while ((c = getopt_long (argc, argv, ",0123456789abt:S:", longopts, NULL)) != -1) { switch (c) @@ -490,6 +530,20 @@ main (int argc, char **argv) case CONVERT_FIRST_ONLY_OPTION: convert_first_only = true; break; + case IN_PLACE_OPTION: + y.is_in_place_called = true; + break; + case 'b': + y.make_backups = true; + if (optarg) + y.version_control_string = optarg; + if (y.backup_suffix_string == NULL) + y.backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX"); + break; + case 'S': + y.make_backups = true; + y.backup_suffix_string = optarg; + break; case ',': if (have_tabval) add_tab_stop (tabval); @@ -509,6 +563,9 @@ main (int argc, char **argv) } } + if (y.is_in_place_called) + in_place_init (&y); + if (convert_first_only) convert_entire_line = false; @@ -526,7 +583,7 @@ main (int argc, char **argv) file_list = (optind < argc ? &argv[optind] : stdin_argv); - unexpand (); + unexpand (&y); if (have_read_stdin && fclose (stdin) != 0) error (EXIT_FAILURE, errno, "-"); -- 1.7.3.2
