Просмотр исходного кода

Improve option sanity checking

Any two conflicting options are reported only if they both occur in
the command line.  Otherwise, options defined in the command line
silently override those set in the TAR_OPTIONS environment variable.

* src/common.h (option_source): New enum.
(option_locus): New struct.
* src/names.c (name_elt): New member: line.
(name_add_file): Initialize line.
(read_name_from_file): Keep track of input line number for diagnostic
purposes.
(handle_option): Take a pointer to struct name_elt as 2nd parameter;
pass locus info to more_options().
* src/tar.c (tar_args): New member: loc.
(option_class): New enum.
(optloc_save,optloc_lookup)
(option_set_in_cl,optloc_eq): New functions.
(set_use_compress_program_option): Take into account option location.
(set_old_files_option): New function.
(parse_opt): Keep track of option locations.
(more_options): Improve error reporting.
(parse_default_options): New function.
(decode_options): Parse TAR_OPTION and command line separately.
Options from the latter silently override those from the former.

* lib/prepargs.c: Remove.
* lib/prepargs.h: Remove.
* lib/Makefile.am: Update.
Sergey Poznyakoff 9 лет назад
Родитель
Сommit
ae23a57d70
6 измененных файлов с 280 добавлено и 141 удалено
  1. 0 1
      lib/Makefile.am
  2. 0 83
      lib/prepargs.c
  3. 0 3
      lib/prepargs.h
  4. 20 1
      src/common.h
  5. 11 4
      src/names.c
  6. 249 49
      src/tar.c

+ 0 - 1
lib/Makefile.am

@@ -41,7 +41,6 @@ noinst_HEADERS = \
 
 libtar_a_SOURCES = \
   paxerror.c paxexit-status.c paxlib.h paxnames.c \
-  prepargs.c prepargs.h \
   rtapelib.c \
   rmt.h \
   stdopen.c stdopen.h \

+ 0 - 83
lib/prepargs.c

@@ -1,83 +0,0 @@
-/* Parse arguments from a string and prepend them to an argv.
-   Copyright 1999-2001, 2007, 2013-2014 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, 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 Paul Eggert <[email protected]>.  */
-
-#ifdef HAVE_CONFIG_H
-# include <config.h>
-#endif
-#include "prepargs.h"
-#include <sys/types.h>
-#include <xalloc.h>
-
-#if HAVE_STRING_H
-# include <string.h>
-#endif
-
-#include <ctype.h>
-
-/* Find the white-space-separated options specified by OPTIONS, and
-   using BUF to store copies of these options, set ARGV[0], ARGV[1],
-   etc. to the option copies.  Return the number N of options found.
-   Do not set ARGV[N].  If ARGV is null, do not store ARGV[0]
-   etc.  Backslash can be used to escape whitespace (and backslashes).  */
-static int
-prepend_args (char const *options, char *buf, char **argv)
-{
-  char const *o = options;
-  char *b = buf;
-  int n = 0;
-
-  for (;;)
-    {
-      while (isspace ((unsigned char) *o))
-	o++;
-      if (!*o)
-	return n;
-      if (argv)
-	argv[n] = b;
-      n++;
-
-      do
-	if ((*b++ = *o++) == '\\' && *o)
-	  b[-1] = *o++;
-      while (*o && ! isspace ((unsigned char) *o));
-
-      *b++ = '\0';
-    }
-}
-
-/* Prepend the whitespace-separated options in OPTIONS to the argument
-   vector of a main program with argument count *PARGC and argument
-   vector *PARGV.  */
-void
-prepend_default_options (char const *options, int *pargc, char ***pargv)
-{
-  if (options)
-    {
-      char *buf = xmalloc (strlen (options) + 1);
-      int prepended = prepend_args (options, buf, (char **) 0);
-      int argc = *pargc;
-      char * const *argv = *pargv;
-      char **pp = (char **) xmalloc ((prepended + argc + 1) * sizeof *pp);
-      *pargc = prepended + argc;
-      *pargv = pp;
-      *pp++ = *argv++;
-      pp += prepend_args (options, buf, pp);
-      while ((*pp++ = *argv++))
-	continue;
-    }
-}

+ 0 - 3
lib/prepargs.h

@@ -1,3 +0,0 @@
-/* Parse arguments from a string and prepend them to an argv.  */
-
-void prepend_default_options (char const *, int *, char ***);

+ 20 - 1
src/common.h

@@ -777,7 +777,26 @@ const char *subcommand_string (enum subcommand c);
 void set_exit_status (int val);
 
 void request_stdin (const char *option);
-void more_options (int argc, char **argv);
+
+/* Where an option comes from: */
+enum option_source
+  {
+    OPTS_ENVIRON,        /* Environment variable TAR_OPTIONS */
+    OPTS_COMMAND_LINE,   /* Command line */
+    OPTS_FILE            /* File supplied by --files-from */
+  };
+
+/* Option location */
+struct option_locus
+{
+  enum option_source source;  /* Option origin */
+  char const *name;           /* File or variable name */
+  size_t line;                /* Number of input line if source is OPTS_FILE */
+  struct option_locus *prev;  /* Previous occurrence of the option of same
+				 class */
+};
+
+void more_options (int argc, char **argv, struct option_locus *loc);
 
 /* Module update.c.  */
 

+ 11 - 4
src/names.c

@@ -227,6 +227,7 @@ struct name_elt        /* A name_array element. */
     struct             /* File, if type == NELT_FILE */
     {
       const char *name;/* File name */
+      size_t line;     /* Input line number */
       int term;        /* File name terminator in the list */
       bool verbatim;   /* Verbatim handling of file names: no white-space
 			  trimming, no option processing */
@@ -321,6 +322,7 @@ name_add_file (const char *name, int term, bool verbatim, int matflags)
 
   ep->type = NELT_FILE;
   ep->v.file.name = name;
+  ep->v.file.line = 0;
   ep->v.file.term = term;
   ep->v.file.verbatim = verbatim;
   ep->v.file.fp = NULL;
@@ -430,6 +432,7 @@ read_name_from_file (struct name_elt *ent)
   FILE *fp = ent->v.file.fp;
   int term = ent->v.file.term;
 
+  ++ent->v.file.line;
   for (c = getc (fp); c != EOF && c != term; c = getc (fp))
     {
       if (counter == name_buffer_length)
@@ -454,11 +457,12 @@ read_name_from_file (struct name_elt *ent)
 }
 
 static int
-handle_option (const char *str)
+handle_option (const char *str, struct name_elt const *ent)
 {
   struct wordsplit ws;
   int i;
-
+  struct option_locus loc;
+  
   while (*str && isspace (*str))
     ++str;
   if (*str != '-')
@@ -469,7 +473,10 @@ handle_option (const char *str)
     FATAL_ERROR ((0, 0, _("cannot split string '%s': %s"),
 		  str, wordsplit_strerror (&ws)));
   ws.ws_wordv[0] = program_invocation_short_name;
-  more_options (ws.ws_wordc+ws.ws_offs, ws.ws_wordv);
+  loc.source = OPTS_FILE;
+  loc.name = ent->v.file.name;
+  loc.line = ent->v.file.line;
+  more_options (ws.ws_wordc+ws.ws_offs, ws.ws_wordv, &loc);
   for (i = 0; i < ws.ws_wordc+ws.ws_offs; i++)
     ws.ws_wordv[i] = NULL;
 
@@ -515,7 +522,7 @@ read_next_name (struct name_elt *ent, struct name_elt *ret)
 	case file_list_success:
 	  if (unquote_option)
 	    unquote_string (name_buffer);
-	  if (!ent->v.file.verbatim && handle_option (name_buffer) == 0)
+	  if (!ent->v.file.verbatim && handle_option (name_buffer, ent) == 0)
 	    {
 	      name_list_adjust ();
 	      return 1;

+ 249 - 49
src/tar.c

@@ -45,7 +45,8 @@
 #include <parse-datetime.h>
 #include <rmt.h>
 #include <rmt-command.h>
-#include <prepargs.h>
+#include <wordsplit.h>
+#include <sysexits.h>
 #include <quotearg.h>
 #include <version-etc.h>
 #include <xstrtol.h>
@@ -911,6 +912,8 @@ enum wildcards
 
 struct tar_args        /* Variables used during option parsing */
 {
+  struct option_locus *loc;
+
   struct textual_date *textual_date; /* Keeps the arguments to --newer-mtime
 					and/or --date option if they are
 					textual dates */
@@ -1009,7 +1012,93 @@ format_default_settings (void)
 #endif
 	    );
 }
+
+static void
+option_conflict_error (const char *a, const char *b)
+{
+  /* TRANSLATORS: Both %s in this statement are replaced with
+     option names. */
+  USAGE_ERROR ((0, 0, _("'%s' cannot be used with '%s'"), a, b));
+}
+
+/* Classes of options that can conflict: */
+enum option_class
+  {
+    OC_COMPRESS,                 /* Compress options: -JjZz, -I, etc. */
+    OC_OCCURRENCE,               /* --occurrence */
+    OC_LISTED_INCREMENTAL,       /* --listed-incremental */
+    OC_NEWER,                    /* --newer, --newer-mtime, --after-date */ 
+    OC_VERIFY,                   /* --verify */  
+    OC_STARTING_FILE,            /* --starting-file */
+    OC_SAME_ORDER,               /* --same-order */
+    OC_ONE_TOP_LEVEL,            /* --one-top-level */
+    OC_ABSOLUTE_NAMES,           /* --absolute-names */
+    OC_OLD_FILES,                /* --keep-old-files, --overwrite, etc. */
+    OC_MAX
+  };
 
+/* Table of locations of potentially conflicting options.  Two options can
+   conflict only if they procede from the command line.  Otherwise, options
+   in command line silently override those defined in TAR_OPTIONS. */
+static struct option_locus *option_class[OC_MAX];
+
+/* Save location of an option of class ID.  Return location of a previous
+   occurrence of an option of that class, or NULL. */
+static struct option_locus *
+optloc_save (unsigned int id, struct option_locus *loc)
+{
+  struct option_locus *optloc;
+  char *p;
+  size_t s;
+
+  if (id >= sizeof (option_class) / sizeof (option_class[0]))
+    abort ();
+  s = sizeof (*loc);
+  if (loc->name)
+    s += strlen (loc->name) + 1;
+  optloc = xmalloc (s);
+  if (loc->name)
+    {
+      p = (char*) optloc + sizeof (*loc);
+      strcpy (p, loc->name);
+      optloc->name = p;
+    }
+  else
+    optloc->name = NULL;
+  optloc->source = loc->source;
+  optloc->line = loc->line;
+  optloc->prev = option_class[id];
+  option_class[id] = optloc;
+  return optloc->prev;
+}
+
+/* Return location of a recent option of class ID */
+static struct option_locus *
+optloc_lookup (int id)
+{
+  return option_class[id];
+}
+
+/* Return true if the latest occurrence of option ID was in the command line */
+int
+option_set_in_cl (int id)
+{
+  struct option_locus *loc = optloc_lookup (id);
+  if (!loc)
+    return 0;
+  return loc->source == OPTS_COMMAND_LINE;
+}
+
+/* Compare two option locations */
+int
+optloc_eq (struct option_locus *a, struct option_locus *b)
+{
+  if (a->source != b->source)
+    return 0;
+  if (a->source == OPTS_COMMAND_LINE)
+    return 1;
+  return strcmp (a->name, b->name) == 0;
+}
 
 static void
 set_subcommand_option (enum subcommand subcommand)
@@ -1023,10 +1112,12 @@ set_subcommand_option (enum subcommand subcommand)
 }
 
 static void
-set_use_compress_program_option (const char *string)
+set_use_compress_program_option (const char *string, struct option_locus *loc)
 {
+  struct option_locus *p = optloc_save (OC_COMPRESS, loc);
   if (use_compress_program_option
-      && strcmp (use_compress_program_option, string) != 0)
+      && strcmp (use_compress_program_option, string) != 0
+      && p->source == OPTS_COMMAND_LINE)
     USAGE_ERROR ((0, 0, _("Conflicting compression options")));
 
   use_compress_program_option = string;
@@ -1366,12 +1457,33 @@ static int sort_mode_flag[] = {
 };
 
 ARGMATCH_VERIFY (sort_mode_arg, sort_mode_flag);
+
+void
+set_old_files_option (int code, struct option_locus *loc)
+{
+  struct option_locus *prev;
+  static char *code_to_opt[] = {
+    "--overwrite-dir",
+    "--no-overwrite-dir",
+    "--overwrite",
+    "--unlink-first",
+    "--keep-old-files",
+    "--skip-old-files",
+    "--keep-newer-files"
+  };
 
+  prev = optloc_save (OC_OLD_FILES, loc);
+  if (prev && optloc_eq (loc, prev) && code != old_files_option)
+    option_conflict_error (code_to_opt[code], code_to_opt[old_files_option]);
+   
+  old_files_option = code;
+}
+
 static error_t
 parse_opt (int key, char *arg, struct argp_state *state)
 {
   struct tar_args *args = state->input;
-
+  
   switch (key)
     {
     case ARGP_KEY_ARG:
@@ -1449,6 +1561,7 @@ parse_opt (int key, char *arg, struct argp_state *state)
       break;
 
     case 'g':
+      optloc_save (OC_LISTED_INCREMENTAL, args->loc);
       listed_incremental_option = arg;
       after_date_option = true;
       /* Fall through.  */
@@ -1479,19 +1592,20 @@ parse_opt (int key, char *arg, struct argp_state *state)
       break;
 
     case 'j':
-      set_use_compress_program_option (BZIP2_PROGRAM);
+      set_use_compress_program_option (BZIP2_PROGRAM, args->loc);
       break;
 
     case 'J':
-      set_use_compress_program_option (XZ_PROGRAM);
+      set_use_compress_program_option (XZ_PROGRAM, args->loc);
       break;
 
     case 'k':
       /* Don't replace existing files.  */
-      old_files_option = KEEP_OLD_FILES;
+      set_old_files_option (KEEP_OLD_FILES, args->loc);
       break;
 
     case 'K':
+      optloc_save (OC_STARTING_FILE, args->loc);
       starting_file_option = true;
       addname (arg, 0, true, NULL);
       break;
@@ -1503,6 +1617,7 @@ parse_opt (int key, char *arg, struct argp_state *state)
       break;
 
     case ONE_TOP_LEVEL_OPTION:
+      optloc_save (OC_ONE_TOP_LEVEL, args->loc);
       one_top_level_option = true;
       one_top_level_dir = arg;
       break;
@@ -1537,15 +1652,15 @@ parse_opt (int key, char *arg, struct argp_state *state)
       break;
 
     case LZIP_OPTION:
-      set_use_compress_program_option (LZIP_PROGRAM);
+      set_use_compress_program_option (LZIP_PROGRAM, args->loc);
       break;
 
     case LZMA_OPTION:
-      set_use_compress_program_option (LZMA_PROGRAM);
+      set_use_compress_program_option (LZMA_PROGRAM, args->loc);
       break;
 
     case LZOP_OPTION:
-      set_use_compress_program_option (LZOP_PROGRAM);
+      set_use_compress_program_option (LZOP_PROGRAM, args->loc);
       break;
 
     case 'm':
@@ -1582,6 +1697,7 @@ parse_opt (int key, char *arg, struct argp_state *state)
       get_date_or_file (args,
 			key == NEWER_MTIME_OPTION ? "--newer-mtime"
 			: "--after-date", arg, &newer_mtime_option);
+      optloc_save (OC_NEWER, args->loc);
       break;
 
     case 'o':
@@ -1597,6 +1713,7 @@ parse_opt (int key, char *arg, struct argp_state *state)
       break;
 
     case 'P':
+      optloc_save (OC_ABSOLUTE_NAMES, args->loc);
       absolute_names_option = true;
       break;
 
@@ -1617,7 +1734,7 @@ parse_opt (int key, char *arg, struct argp_state *state)
 
     case 's':
       /* Names to extract are sorted.  */
-
+      optloc_save (OC_SAME_ORDER, args->loc);
       same_order_option = true;
       break;
 
@@ -1626,7 +1743,7 @@ parse_opt (int key, char *arg, struct argp_state *state)
       break;
 
     case SKIP_OLD_FILES_OPTION:
-      old_files_option = SKIP_OLD_FILES;
+      set_old_files_option (SKIP_OLD_FILES, args->loc);
       break;
 
     case SPARSE_VERSION_OPTION:
@@ -1668,7 +1785,7 @@ parse_opt (int key, char *arg, struct argp_state *state)
       break;
 
     case 'U':
-      old_files_option = UNLINK_FIRST_OLD_FILES;
+      set_old_files_option (UNLINK_FIRST_OLD_FILES, args->loc);
       break;
 
     case UTC_OPTION:
@@ -1689,6 +1806,7 @@ parse_opt (int key, char *arg, struct argp_state *state)
       break;
 
     case 'W':
+      optloc_save (OC_VERIFY, args->loc);
       verify_option = true;
       break;
 
@@ -1707,11 +1825,11 @@ parse_opt (int key, char *arg, struct argp_state *state)
       break;
 
     case 'z':
-      set_use_compress_program_option (GZIP_PROGRAM);
+      set_use_compress_program_option (GZIP_PROGRAM, args->loc);
       break;
 
     case 'Z':
-      set_use_compress_program_option (COMPRESS_PROGRAM);
+      set_use_compress_program_option (COMPRESS_PROGRAM, args->loc);
       break;
 
     case ANCHORED_OPTION:
@@ -1859,7 +1977,7 @@ parse_opt (int key, char *arg, struct argp_state *state)
       break;
 
     case KEEP_NEWER_FILES_OPTION:
-      old_files_option = KEEP_NEWER_FILES;
+      set_old_files_option (KEEP_NEWER_FILES, args->loc);
       break;
 
     case GROUP_OPTION:
@@ -1899,7 +2017,7 @@ parse_opt (int key, char *arg, struct argp_state *state)
       break;
 
     case NO_OVERWRITE_DIR_OPTION:
-      old_files_option = NO_OVERWRITE_DIR_OLD_FILES;
+      set_old_files_option (NO_OVERWRITE_DIR_OLD_FILES, args->loc);
       break;
 
     case NO_QUOTE_CHARS_OPTION:
@@ -1930,6 +2048,7 @@ parse_opt (int key, char *arg, struct argp_state *state)
       break;
 
     case OCCURRENCE_OPTION:
+      optloc_save (OC_OCCURRENCE, args->loc);
       if (!arg)
 	occurrence_option = 1;
       else
@@ -1948,11 +2067,11 @@ parse_opt (int key, char *arg, struct argp_state *state)
       break;
 
     case OVERWRITE_DIR_OPTION:
-      old_files_option = DEFAULT_OLD_FILES;
+      set_old_files_option (DEFAULT_OLD_FILES, args->loc);
       break;
 
     case OVERWRITE_OPTION:
-      old_files_option = OVERWRITE_OLD_FILES;
+      set_old_files_option (OVERWRITE_OLD_FILES, args->loc);
       break;
 
     case OWNER_OPTION:
@@ -1994,6 +2113,7 @@ parse_opt (int key, char *arg, struct argp_state *state)
 
     case PRESERVE_OPTION:
       /* FIXME: What it is good for? */
+      optloc_save (OC_SAME_ORDER, args->loc);
       same_permissions_option = true;
       same_order_option = true;
       WARN ((0, 0, _("The --preserve option is deprecated, "
@@ -2097,7 +2217,7 @@ parse_opt (int key, char *arg, struct argp_state *state)
       break;
 
     case 'I':
-      set_use_compress_program_option (arg);
+      set_use_compress_program_option (arg, args->loc);
       break;
 
     case VOLNO_FILE_OPTION:
@@ -2248,6 +2368,14 @@ parse_opt (int key, char *arg, struct argp_state *state)
 
 #endif /* not DEVICE_PREFIX */
 
+    case ARGP_KEY_ERROR:
+      if (args->loc->source == OPTS_FILE)
+	error (0, 0, _("%s:%lu: location of the error"), args->loc->name,
+	       (unsigned long) args->loc->line);
+      else if (args->loc->source == OPTS_ENVIRON)
+	error (0, 0, _("error parsing %s"), args->loc->name);
+      exit (EX_USAGE);
+      
     default:
       return ARGP_ERR_UNKNOWN;
     }
@@ -2320,19 +2448,47 @@ static int subcommand_class[] = {
 
 static struct tar_args args;
 
-static void
-option_conflict_error (const char *a, const char *b)
+void
+more_options (int argc, char **argv, struct option_locus *loc)
 {
-  /* TRANSLATORS: Both %s in this statement are replaced with
-     option names. */
-  USAGE_ERROR ((0, 0, _("'%s' cannot be used with '%s'"), a, b));
+  int idx;
+
+  args.loc = loc;
+  if (argp_parse (&argp, argc, argv, ARGP_IN_ORDER|ARGP_NO_EXIT, &idx, &args))
+    abort (); /* shouldn't happen */
+  if (loc->source == OPTS_ENVIRON && args.input_files)
+    USAGE_ERROR ((0, 0, _("non-option arguments in %s"), loc->name));    
+}
+
+void
+parse_default_options ()
+{
+  char *opts = getenv ("TAR_OPTIONS");
+  struct wordsplit ws;
+  struct option_locus loc = { OPTS_ENVIRON, "TAR_OPTIONS", 0 };
+  
+  if (!opts)
+    return;
+
+  ws.ws_offs = 1;
+  if (wordsplit (opts, &ws, WRDSF_DEFFLAGS|WRDSF_DOOFFS))
+    FATAL_ERROR ((0, 0, _("cannot split TAR_OPTIONS: %s"),
+		  wordsplit_strerror (&ws)));
+  if (ws.ws_wordc)
+    {
+      ws.ws_wordv[0] = program_invocation_short_name;
+      more_options (ws.ws_offs + ws.ws_wordc, ws.ws_wordv, &loc);
+    }
+  
+  wordsplit_free (&ws);
 }
 
 static void
 decode_options (int argc, char **argv)
 {
   int idx;
-
+  struct option_locus loc = { OPTS_COMMAND_LINE };
+  
   argp_version_setup ("tar", tar_authors);
 
   /* Set some default option values.  */
@@ -2429,9 +2585,9 @@ decode_options (int argc, char **argv)
     }
 
   /* Parse all options and non-options as they appear.  */
+  parse_default_options ();
 
-  prepend_default_options (getenv ("TAR_OPTIONS"), &argc, &argv);
-
+  args.loc = &loc;
   if (argp_parse (&argp, argc, argv, ARGP_IN_ORDER, &idx, &args))
     exit (TAREXIT_FAILURE);
 
@@ -2493,10 +2649,15 @@ decode_options (int argc, char **argv)
 	USAGE_ERROR ((0, 0,
 		      _("--occurrence is meaningless without a file list")));
       if (!IS_SUBCOMMAND_CLASS (SUBCL_OCCUR))
-	option_conflict_error ("--occurrence",
-			       subcommand_string (subcommand_option));
+	{
+	  if (option_set_in_cl (OC_OCCURRENCE))
+	    option_conflict_error ("--occurrence",
+				   subcommand_string (subcommand_option));
+	  else
+	    occurrence_option = 0;
+	}
     }
-
+  
   if (archive_names == 0)
     {
       /* If no archive file name given, try TAPE from the environment, or
@@ -2516,7 +2677,16 @@ decode_options (int argc, char **argv)
 
   if (listed_incremental_option
       && NEWER_OPTION_INITIALIZED (newer_mtime_option))
-    option_conflict_error ("--listed-incremental", "--newer");
+    {
+      struct option_locus *listed_loc = optloc_lookup (OC_LISTED_INCREMENTAL);
+      struct option_locus *newer_loc = optloc_lookup (OC_NEWER);
+      if (optloc_eq (listed_loc, newer_loc))
+	option_conflict_error ("--listed-incremental", "--newer");
+      else if (listed_loc->source == OPTS_COMMAND_LINE)
+	listed_incremental_option = NULL;
+      else
+	memset (&newer_mtime_option, 0, sizeof (newer_mtime_option));
+    }
   
   if (incremental_level != -1 && !listed_incremental_option)
     WARN ((0, 0,
@@ -2554,8 +2724,13 @@ decode_options (int argc, char **argv)
       if (use_compress_program_option)
 	USAGE_ERROR ((0, 0, _("Cannot verify compressed archives")));
       if (!IS_SUBCOMMAND_CLASS (SUBCL_WRITE))
-	option_conflict_error ("--verify",
-			       subcommand_string (subcommand_option));
+	{
+	  if (option_set_in_cl (OC_VERIFY))
+	    option_conflict_error ("--verify",
+				   subcommand_string (subcommand_option));
+	  else
+	    verify_option = false;
+	}
     }
 
   if (use_compress_program_option)
@@ -2595,21 +2770,43 @@ decode_options (int argc, char **argv)
     USAGE_ERROR ((0, 0, _("--xattrs can be used only on POSIX archives")));
 
   if (starting_file_option && !IS_SUBCOMMAND_CLASS (SUBCL_READ))
-    option_conflict_error ("--starting-file",
-			   subcommand_string (subcommand_option));
+    {
+      if (option_set_in_cl (OC_STARTING_FILE))
+	option_conflict_error ("--starting-file",
+			       subcommand_string (subcommand_option));
+      else
+	starting_file_option = false;
+    }
 
   if (same_order_option && !IS_SUBCOMMAND_CLASS (SUBCL_READ))
-    option_conflict_error ("--same-order",
-			   subcommand_string (subcommand_option));
+    {
+      if (option_set_in_cl (OC_SAME_ORDER))
+	option_conflict_error ("--same-order",
+			       subcommand_string (subcommand_option));
+      else
+	same_order_option = false;
+    }
 
   if (one_top_level_option)
     {
       char *base;
       
       if (absolute_names_option)
-	option_conflict_error ("--one-top-level", "--absolute-names");
+	{
+	  struct option_locus *one_top_level_loc =
+	    optloc_lookup (OC_ONE_TOP_LEVEL);
+	  struct option_locus *absolute_names_loc =
+	    optloc_lookup (OC_ABSOLUTE_NAMES);
+
+	  if (optloc_eq (one_top_level_loc, absolute_names_loc))
+	    option_conflict_error ("--one-top-level", "--absolute-names");
+	  else if (one_top_level_loc->source == OPTS_COMMAND_LINE)
+	    absolute_names_option = false;
+	  else
+	    one_top_level_option = false;
+	}
       
-      if (!one_top_level_dir)
+      if (one_top_level_option && !one_top_level_dir)
 	{
 	  /* If the user wants to guarantee that everything is under one
 	     directory, determine its name now and let it be created later.  */
@@ -2655,7 +2852,18 @@ decode_options (int argc, char **argv)
     USAGE_ERROR ((0, 0, _("Volume length cannot be less than record size")));
 
   if (same_order_option && listed_incremental_option)
-    option_conflict_error ("--preserve-order", "--listed-incremental");
+    {
+      struct option_locus *preserve_order_loc = optloc_lookup (OC_SAME_ORDER);
+      struct option_locus *listed_incremental_loc =
+	optloc_lookup (OC_LISTED_INCREMENTAL);
+
+      if (optloc_eq (preserve_order_loc, listed_incremental_loc))
+	option_conflict_error ("--preserve-order", "--listed-incremental");
+      else if (preserve_order_loc->source == OPTS_COMMAND_LINE)
+	listed_incremental_option = false;
+      else
+	same_order_option = false;
+    }
 
   /* Forbid using -c with no input files whatsoever.  Check that '-f -',
      explicit or implied, is used correctly.  */
@@ -2728,14 +2936,6 @@ decode_options (int argc, char **argv)
   report_textual_dates (&args);
 }
 
-void
-more_options (int argc, char **argv)
-{
-  int idx;
-  if (argp_parse (&argp, argc, argv, ARGP_IN_ORDER,
-		  &idx, &args))
-    exit (TAREXIT_FAILURE);
-}
 
 /* Tar proper.  */