Subject: bug#76197: [PROPOSED] gzip: drop support for the GZIP env var

From: Paul Eggert <[email-masked]>
Date: Mon, 10 Feb 2025 23:22:34 -0800

The GZIP environment variable is too hazardous.
* gzip.c (env, ENV_OPTION):
* tailor.h (OPTIONS_VAR):
* util.c (SEPARATOR, add_envopt):
Remove.  All uses removed.
* gzip.c (main): Ignore GZIP.
* tests/gzip-env: Test that GZIP is ignored.
---
 NEWS           |  6 ++++
 doc/gzip.texi  | 11 ++-----
 gzip.1         | 18 ++++--------
 gzip.c         | 79 +-------------------------------------------------
 gzip.h         |  1 -
 tailor.h       |  4 ---
 tests/gzip-env | 19 +++++-------
 util.c         | 60 --------------------------------------
 znew.in        |  2 +-
 9 files changed, 24 insertions(+), 176 deletions(-)

diff --git a/NEWS b/NEWS
index e348355..0c4a2fc 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,12 @@ GNU gzip NEWS                                    -*- outline -*-
 
 * Noteworthy changes in release ?.? (????-??-??) [?]
 
+** Changes in behavior
+
+  The GZIP environment variable, which was marked as obsolescent in
+  gzip 1.7 (2016) due to its hazards, has been removed.  You can use
+  an alias or script instead.
+
 ** Bug fixes
 
   'gzip -d' no longer omits the last partial output buffer when the
diff --git a/doc/gzip.texi b/doc/gzip.texi
index cc05b60..1bfca98 100644
--- a/doc/gzip.texi
+++ b/doc/gzip.texi
@@ -487,14 +487,9 @@ complement to @command{tar}, not as a replacement.
 @chapter Environment
 @cindex Environment
 
-The obsolescent environment variable @env{GZIP} can hold a set of
-default options for @command{gzip}.  These options are interpreted
-first and can be overwritten by explicit command line parameters.  As
-this can cause problems when using scripts, this feature is supported
-only for options that are reasonably likely to not cause too much
-harm, and @command{gzip} warns if it is used.  This feature will be
-removed in a future release of @command{gzip}.
-
+Although previous verions of @command{gzip} examined the environment
+variable @env{GZIP} for a set of default options,
+this feature has been removed because it caused problems when using scripts.
 You can use an alias or script instead.  For example, if
 @command{gzip} is in the directory @samp{/usr/bin} you can prepend
 @file{$HOME/bin} to your @env{PATH} and create an executable script
diff --git a/gzip.1 b/gzip.1
index 0efe399..4afa243 100644
--- a/gzip.1
+++ b/gzip.1
@@ -416,20 +416,12 @@ such as tar or zip.
 GNU tar supports the \-z option to invoke gzip transparently.
 gzip is designed as a complement to tar, not as a replacement.
 .SH "ENVIRONMENT"
-The obsolescent environment variable
-.B GZIP
-can hold a set of default options for
-.BR gzip .
-These options are interpreted first and can be overwritten by explicit
-command line parameters.
-As this can cause problems when using scripts,
-this feature is supported only for options that are
-reasonably likely to not cause too much harm, and
+Although previous versions of
 .B gzip
-warns if it is used.
-This feature will be removed in a future release of
-.BR gzip .
-.PP
+examined the environment variable
+.B GZIP
+for a set of default options, this feature has been removed
+because it caused problems when using scripts.
 You can use an alias or script instead.
 For example, if
 .B gzip
diff --git a/gzip.c b/gzip.c
index a33841a..1fe589b 100644
--- a/gzip.c
+++ b/gzip.c
@@ -182,7 +182,6 @@ static int foreground = 0;   /* set if program run in foreground */
 static int last_member;      /* set for .zip and .Z files */
 static int part_nb;          /* number of parts in .gz file */
        off_t ifile_size;      /* input file size, -1 for devices (debug only) */
-static char *env;            /* contents of GZIP env variable */
 static char const *z_suffix; /* default suffix (can be set with --suffix) */
 static size_t z_len;         /* strlen(z_suffix) */
 
@@ -249,10 +248,6 @@ enum
   PRESUME_INPUT_TTY_OPTION = CHAR_MAX + 1,
   RSYNCABLE_OPTION,
   SYNCHRONOUS_OPTION,
-
-  /* A value greater than all valid long options, used as a flag to
-     distinguish options derived from the GZIP environment variable.  */
-  ENV_OPTION
 };
 
 static char const shortopts[] = "ab:cdfhH?klLmMnNqrS:tvVZ123456789";
@@ -413,9 +408,6 @@ int main (int argc, char **argv)
 {
     int file_count;     /* number of files to process */
     size_t proglen;     /* length of program_name */
-    char **argv_copy;
-    int env_argc;
-    char **env_argv;
 
     EXPAND(argc, argv); /* wild card expansion if necessary */
 
@@ -426,11 +418,6 @@ int main (int argc, char **argv)
     if (4 < proglen && strequ (program_name + proglen - 4, ".exe"))
       program_name[proglen - 4] = '\0';
 
-    /* Add options in GZIP environment variable if there is one */
-    argv_copy = argv;
-    env = add_envopt (&env_argc, &argv_copy, OPTIONS_VAR);
-    env_argv = env ? argv_copy : NULL;
-
 #ifndef GNU_STANDARD
 # define GNU_STANDARD 1
 #endif
@@ -454,49 +441,8 @@ int main (int argc, char **argv)
     z_len = strlen(z_suffix);
 
     while (true) {
-        int optc;
         int longind = -1;
-
-        if (env_argv)
-          {
-            if (env_argv[optind] && strequ (env_argv[optind], "--"))
-              optc = ENV_OPTION + '-';
-            else
-              {
-                optc = getopt_long (env_argc, env_argv, shortopts, longopts,
-                                    &longind);
-                if (0 <= optc)
-                  optc += ENV_OPTION;
-                else
-                  {
-                    if (optind != env_argc)
-                      {
-                        fprintf (stderr,
-                                 ("%s: %s: non-option in "OPTIONS_VAR
-                                  " environment variable\n"),
-                                 program_name, env_argv[optind]);
-                        try_help ();
-                      }
-
-                    /* Wait until here before warning, so that GZIP='-q'
-                       doesn't warn.  */
-                    if (env_argc != 1 && !quiet)
-                      fprintf (stderr,
-                               ("%s: warning: "OPTIONS_VAR" environment variable"
-                                " is deprecated; use an alias or script\n"),
-                               program_name);
-
-                    /* Start processing ARGC and ARGV instead.  */
-                    free (env_argv);
-                    env_argv = NULL;
-                    optind = 1;
-                    longind = -1;
-                  }
-              }
-          }
-
-        if (!env_argv)
-          optc = getopt_long (argc, argv, shortopts, longopts, &longind);
+        int optc = getopt_long (argc, argv, shortopts, longopts, &longind);
         if (optc < 0)
           break;
 
@@ -532,15 +478,12 @@ int main (int argc, char **argv)
         case 'M': /* undocumented, may change later */
             no_time = 0; break;
         case 'n':
-        case 'n' + ENV_OPTION:
             no_name = no_time = 1; break;
         case 'N':
-        case 'N' + ENV_OPTION:
             no_name = no_time = 0; break;
         case PRESUME_INPUT_TTY_OPTION:
             presume_input_tty = true; break;
         case 'q':
-        case 'q' + ENV_OPTION:
             quiet = 1; verbose = 0; break;
         case 'r':
 #if NO_DIR
@@ -553,7 +496,6 @@ int main (int argc, char **argv)
             break;
 
         case RSYNCABLE_OPTION:
-        case RSYNCABLE_OPTION + ENV_OPTION:
             rsync = 1;
             break;
         case 'S':
@@ -575,7 +517,6 @@ int main (int argc, char **argv)
             test = decompress = to_stdout = 1;
             break;
         case 'v':
-        case 'v' + ENV_OPTION:
             verbose++; quiet = 0; break;
         case 'V':
             version (); finish_out (); break;
@@ -584,28 +525,12 @@ int main (int argc, char **argv)
                     program_name);
             try_help ();
             break;
-        case '1' + ENV_OPTION:  case '2' + ENV_OPTION:  case '3' + ENV_OPTION:
-        case '4' + ENV_OPTION:  case '5' + ENV_OPTION:  case '6' + ENV_OPTION:
-        case '7' + ENV_OPTION:  case '8' + ENV_OPTION:  case '9' + ENV_OPTION:
-            optc -= ENV_OPTION;
-            FALLTHROUGH;
         case '1':  case '2':  case '3':  case '4':
         case '5':  case '6':  case '7':  case '8':  case '9':
             level = optc - '0';
             break;
 
         default:
-            if (ENV_OPTION <= optc && optc != ENV_OPTION + '?')
-              {
-                /* Output a diagnostic, since getopt_long didn't.  */
-                fprintf (stderr, "%s: ", program_name);
-                if (longind < 0)
-                  fprintf (stderr, "-%c: ", optc - ENV_OPTION);
-                else
-                  fprintf (stderr, "--%s: ", longopts[longind].name);
-                fprintf (stderr, ("option not valid in "OPTIONS_VAR
-                                  " environment variable\n"));
-              }
             try_help ();
         }
     } /* loop on all arguments */
@@ -2042,8 +1967,6 @@ do_exit (int exitcode)
 
     if (in_exit) exit(exitcode);
     in_exit = 1;
-    free(env);
-    env  = NULL;
     FREE(inbuf);
     FREE(outbuf);
     FREE(d_buf);
diff --git a/gzip.h b/gzip.h
index 855a88b..66b504b 100644
--- a/gzip.h
+++ b/gzip.h
@@ -314,7 +314,6 @@ extern char *strlwr       (char *s);
 extern char *gzip_base_name (char *fname) _GL_ATTRIBUTE_PURE;
 extern int xunlink        (char *fname);
 extern void make_simple_name (char *name);
-extern char *add_envopt   (int *argcp, char ***argvp, char const *env);
 _Noreturn extern void gzip_error (char const *m);
 _Noreturn extern void xalloc_die (void);
 extern void warning       (char const *m);
diff --git a/tailor.h b/tailor.h
index c9147b1..095d7ce 100644
--- a/tailor.h
+++ b/tailor.h
@@ -163,10 +163,6 @@
 #  define casemap(c) (c)
 #endif
 
-#ifndef OPTIONS_VAR
-#  define OPTIONS_VAR "GZIP"
-#endif
-
 #ifndef Z_SUFFIX
 #  define Z_SUFFIX ".gz"
 #endif
diff --git a/tests/gzip-env b/tests/gzip-env
index b7382d9..2da5915 100755
--- a/tests/gzip-env
+++ b/tests/gzip-env
@@ -1,5 +1,5 @@
 #!/bin/sh
-# Test the obsolescent GZIP environment variable.
+# Test that gzip is unaffected by the GZIP environment variable.
 
 # Copyright 2015-2025 Free Software Foundation, Inc.
 
@@ -23,20 +23,17 @@ echo a >exp || framework_failure_
 gzip <exp >in || framework_failure_
 
 fail=0
-GZIP=-qv gzip -d <in >out 2>err || fail=1
-compare exp out || fail=1
 
-for badopt in -- -c --stdout -d --decompress -f --force -h --help -k --keep \
+for opt in -qv \
+  -- -c --stdout -d --decompress -f --force -h --help -k --keep \
   -l --list -L --license -r --recursive -Sxxx --suffix=xxx '--suffix xxx' \
-  -t --test -V --version
-do
-  GZIP=$badopt gzip -d <in >out 2>err && fail=1
-done
-
-for goodopt in -n --no-name -N --name -q --quiet -v --verbose \
+  -t --test -V --version \
+  -n --no-name -N --name -q --quiet -v --verbose \
   -1 --fast -2 -3 -4 -5 -6 -7 -8 -9 --best
 do
-  GZIP=$goodopt gzip -d <in >out 2>err || fail=1
+  GZIP=$opt gzip <exp >inopt 2>err || fail=1
+  compare in inopt || fail=1
+  GZIP=$opt gzip -d <in >out 2>err || fail=1
   compare exp out || fail=1
 done
 
diff --git a/util.c b/util.c
index e3eb739..c799ac1 100644
--- a/util.c
+++ b/util.c
@@ -291,66 +291,6 @@ make_simple_name (char *name)
 }
 #endif
 
-/* Convert the value of the environment variable ENVVAR_NAME
-   to a newly allocated argument vector, and set *ARGCP and *ARGVP
-   to the number of arguments and to the vector, respectively.
-   Make the new vector's zeroth element equal to the old **ARGVP.
-   Return a pointer to the newly allocated string storage.
-
-   If the vector would be empty, do not allocate storage,
-   do not set *ARGCP and *ARGVP, and return NULL.  */
-
-#define SEPARATOR	" \t"	/* separators in env variable */
-
-char *add_envopt(
-    int *argcp,          /* pointer to argc */
-    char ***argvp,       /* pointer to argv */
-    char const *envvar_name) /* name of environment variable */
-{
-    char *p;             /* running pointer through env variable */
-    char **oargv;        /* runs through old argv array */
-    char **nargv;        /* runs through new argv array */
-    int  nargc = 0;      /* number of arguments in env variable */
-    char *env_val;
-
-    env_val = getenv(envvar_name);
-    if (env_val == NULL) return NULL;
-
-    env_val = xstrdup (env_val);
-
-    for (p = env_val; *p; nargc++ ) {        /* move through env_val */
-        p += strspn(p, SEPARATOR);	     /* skip leading separators */
-        if (*p == '\0') break;
-
-        p += strcspn(p, SEPARATOR);	     /* find end of word */
-        if (*p) *p++ = '\0';		     /* mark it */
-    }
-    if (nargc == 0) {
-        free(env_val);
-        return NULL;
-    }
-    *argcp = nargc + 1;
-    /* Allocate the new argv array, with an extra element just in case
-     * the original arg list did not end with a NULL.
-     */
-    nargv = xcalloc (*argcp + 1, sizeof (char *));
-    oargv  = *argvp;
-    *argvp = nargv;
-
-    /* Copy the program name first */
-    *nargv++ = *oargv;
-
-    /* Then copy the environment args */
-    for (p = env_val; nargc > 0; nargc--) {
-        p += strspn(p, SEPARATOR);	     /* skip separators */
-        *(nargv++) = p;			     /* store start */
-        while (*p++) ;			     /* skip over word */
-    }
-
-    *nargv = NULL;
-    return env_val;
-}
-
 /* ========================================================================
  * Error handlers.
  */
diff --git a/znew.in b/znew.in
index b09d967..e9d87f3 100644
--- a/znew.in
+++ b/znew.in
@@ -55,7 +55,7 @@ new=0
 block=1024
 # block is the disk block size (best guess, need not be exact)
 
-# Beware -s or --suffix in $GZIP.
+# Beware -s or --suffix in $GZIP, used by gzip 1.13 and earlier.
 unset GZIP
 ext=.gz
 
-- 
2.45.2