/* pathchk -- check whether file names are valid or portable                    This is the pathchk utility
   Copyright (C) 1991-2018 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 <https://www.gnu.org/licenses/>.  */   The GNUv3 license
                                                                                
#include <config.h>                                                             Provides system specific information
#include <stdio.h>                                                              Provides standard I/O capability
#include <getopt.h>                                                             ...!includes auto-comment...
#include <sys/types.h>                                                          Provides system data types
#include <wchar.h>                                                              ...!includes auto-comment...
                                                                                
#include "system.h"                                                             ...!includes auto-comment...
#include "error.h"                                                              ...!includes auto-comment...
#include "quote.h"                                                              ...!includes auto-comment...
                                                                                
/* The official name of this program (e.g., no 'g' prefix).  */                 
#define PROGRAM_NAME "pathchk"                                                  Line 28
                                                                                
#define AUTHORS \                                                               Line 30
  proper_name ("Paul Eggert"), \                                                Line 31
  proper_name ("David MacKenzie"), \                                            Line 32
  proper_name ("Jim Meyering")                                                  Line 33
                                                                                
#ifndef _POSIX_PATH_MAX                                                         Line 35
# define _POSIX_PATH_MAX 256                                                    Line 36
#endif                                                                          Line 37
#ifndef _POSIX_NAME_MAX                                                         Line 38
# define _POSIX_NAME_MAX 14                                                     Line 39
#endif                                                                          Line 40
                                                                                
#ifdef _XOPEN_NAME_MAX                                                          Line 42
# define NAME_MAX_MINIMUM _XOPEN_NAME_MAX                                       Line 43
#else                                                                           Line 44
# define NAME_MAX_MINIMUM _POSIX_NAME_MAX                                       Line 45
#endif                                                                          Line 46
#ifdef _XOPEN_PATH_MAX                                                          Line 47
# define PATH_MAX_MINIMUM _XOPEN_PATH_MAX                                       Line 48
#else                                                                           Line 49
# define PATH_MAX_MINIMUM _POSIX_PATH_MAX                                       Line 50
#endif                                                                          Line 51
                                                                                
#if ! (HAVE_PATHCONF && defined _PC_NAME_MAX && defined _PC_PATH_MAX)           Line 53
# ifndef _PC_NAME_MAX                                                           Line 54
#  define _PC_NAME_MAX 0                                                        Line 55
#  define _PC_PATH_MAX 1                                                        Line 56
# endif                                                                         Line 57
# ifndef pathconf                                                               Line 58
#  define pathconf(file, flag) \                                                Line 59
     (flag == _PC_NAME_MAX ? NAME_MAX_MINIMUM : PATH_MAX_MINIMUM)               Line 60
# endif                                                                         Line 61
#endif                                                                          Line 62
                                                                                
static bool validate_file_name (char *, bool, bool);                            Line 64
                                                                                
/* For long options that have no equivalent short option, use a                 
   non-character as a pseudo short option, starting with CHAR_MAX + 1.  */      
enum                                                                            Line 68
{                                                                               
  PORTABILITY_OPTION = CHAR_MAX + 1                                             Line 70
};                                                                              Block 1
                                                                                
static struct option const longopts[] =                                         Line 73
{                                                                               
  {"portability", no_argument, NULL, PORTABILITY_OPTION},                       Line 75
  {GETOPT_HELP_OPTION_DECL},                                                    Line 76
  {GETOPT_VERSION_OPTION_DECL},                                                 Line 77
  {NULL, 0, NULL, 0}                                                            Line 78
};                                                                              Block 2
                                                                                
void                                                                            Line 81
usage (int status)                                                              Line 82
{                                                                               
  if (status != EXIT_SUCCESS)                                                   Line 84
    emit_try_help ();                                                           ...!common auto-comment...
  else                                                                          Line 86
    {                                                                           
      printf (_("Usage: %s [OPTION]... NAME...\n"), program_name);              Line 88
      fputs (_("\                                                               Line 89
Diagnose invalid or unportable file names.\n\                                   Line 90
\n\                                                                             
  -p                  check for most POSIX systems\n\                           Line 92
  -P                  check for empty names and leading \"-\"\n\                Line 93
      --portability   check for all POSIX systems (equivalent to -p -P)\n\      Line 94
"), stdout);                                                                    Line 95
      fputs (HELP_OPTION_DESCRIPTION, stdout);                                  Line 96
      fputs (VERSION_OPTION_DESCRIPTION, stdout);                               Line 97
      emit_ancillary_info (PROGRAM_NAME);                                       Line 98
    }                                                                           
  exit (status);                                                                Line 100
}                                                                               Block 3
                                                                                
int                                                                             
main (int argc, char **argv)                                                    Line 104
{                                                                               
  bool ok = true;                                                               Line 106
  bool check_basic_portability = false;                                         Line 107
  bool check_extra_portability = false;                                         Line 108
  int optc;                                                                     Line 109
                                                                                
  initialize_main (&argc, &argv);                                               VMS-specific entry point handling wildcard expansion
  set_program_name (argv[0]);                                                   Retains program name and discards path
  setlocale (LC_ALL, "");                                                       Sets up internationalization (i18n)
  bindtextdomain (PACKAGE, LOCALEDIR);                                          Assigns i18n directorySets text domain for _() [gettext()] function
  textdomain (PACKAGE);                                                         Sets text domain for _() [gettext()] function
                                                                                
  atexit (close_stdout);                                                        Close stdout on exit (see gnulib)
                                                                                
  while ((optc = getopt_long (argc, argv, "+pP", longopts, NULL)) != -1)        Line 119
    {                                                                           
      switch (optc)                                                             Line 121
        {                                                                       
        case PORTABILITY_OPTION:                                                Line 123
          check_basic_portability = true;                                       Line 124
          check_extra_portability = true;                                       Line 125
          break;                                                                Line 126
                                                                                
        case 'p':                                                               Line 128
          check_basic_portability = true;                                       Line 129
          break;                                                                Line 130
                                                                                
        case 'P':                                                               Line 132
          check_extra_portability = true;                                       Line 133
          break;                                                                Line 134
                                                                                
        case_GETOPT_HELP_CHAR;                                                  Line 136
                                                                                
        case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);                       Line 138
                                                                                
        default:                                                                Line 140
          usage (EXIT_FAILURE);                                                 Line 141
        }                                                                       
    }                                                                           
                                                                                
  if (optind == argc)                                                           Line 145
    {                                                                           
      error (0, 0, _("missing operand"));                                       Line 147
      usage (EXIT_FAILURE);                                                     Line 148
    }                                                                           
                                                                                
  for (; optind < argc; ++optind)                                               Line 151
    ok &= validate_file_name (argv[optind],                                     Line 152
                              check_basic_portability, check_extra_portability);Line 153
                                                                                
  return ok ? EXIT_SUCCESS : EXIT_FAILURE;                                      Line 155
}                                                                               Block 4
                                                                                
/* If FILE contains a component with a leading "-", report an error             
   and return false; otherwise, return true.  */                                
                                                                                
static bool                                                                     Line 161
no_leading_hyphen (char const *file)                                            Line 162
{                                                                               
  char const *p;                                                                Line 164
                                                                                
  for (p = file;  (p = strchr (p, '-'));  p++)                                  Line 166
    if (p == file || p[-1] == '/')                                              Line 167
      {                                                                         
        error (0, 0, _("leading '-' in a component of file name %s"),           Line 169
               quoteaf (file));                                                 Line 170
        return false;                                                           Line 171
      }                                                                         
                                                                                
  return true;                                                                  Line 174
}                                                                               Block 5
                                                                                
/* If FILE (of length FILELEN) contains only portable characters,               
   return true, else report an error and return false.  */                      
                                                                                
static bool                                                                     Line 180
portable_chars_only (char const *file, size_t filelen)                          Line 181
{                                                                               
  size_t validlen = strspn (file,                                               Line 183
                            ("/"                                                Line 184
                             "ABCDEFGHIJKLMNOPQRSTUVWXYZ"                       Line 185
                             "abcdefghijklmnopqrstuvwxyz"                       Line 186
                             "0123456789._-"));                                 Line 187
  char const *invalid = file + validlen;                                        Line 188
                                                                                
  if (*invalid)                                                                 Line 190
    {                                                                           
      mbstate_t mbstate = { 0, };                                               Line 192
      size_t charlen = mbrlen (invalid, filelen - validlen, &mbstate);          Line 193
      error (0, 0,                                                              Line 194
             _("nonportable character %s in file name %s"),                     Line 195
             quotearg_n_style_mem (1, locale_quoting_style, invalid,            Line 196
                                   (charlen <= MB_LEN_MAX ? charlen : 1)),      Line 197
             quoteaf_n (0, file));                                              Line 198
      return false;                                                             Line 199
    }                                                                           
                                                                                
  return true;                                                                  Line 202
}                                                                               Block 6
                                                                                
/* Return the address of the start of the next file name component in F.  */    
                                                                                
static char * _GL_ATTRIBUTE_PURE                                                Line 207
component_start (char *f)                                                       Line 208
{                                                                               
  while (*f == '/')                                                             Line 210
    f++;                                                                        Line 211
  return f;                                                                     Line 212
}                                                                               Block 7
                                                                                
/* Return the size of the file name component F.  F must be nonempty.  */       
                                                                                
static size_t _GL_ATTRIBUTE_PURE                                                Line 217
component_len (char const *f)                                                   Line 218
{                                                                               
  size_t len;                                                                   Line 220
  for (len = 1; f[len] != '/' && f[len]; len++)                                 Line 221
    continue;                                                                   Line 222
  return len;                                                                   Line 223
}                                                                               Block 8
                                                                                
/* Make sure that                                                               
   strlen (FILE) <= PATH_MAX                                                    
   && strlen (each-existing-directory-in-FILE) <= NAME_MAX                      
                                                                                
   If CHECK_BASIC_PORTABILITY is true, compare against _POSIX_PATH_MAX and      
   _POSIX_NAME_MAX instead, and make sure that FILE contains no                 
   characters not in the POSIX portable filename character set, which           
   consists of A-Z, a-z, 0-9, ., _, - (plus / for separators).                  
                                                                                
   If CHECK_BASIC_PORTABILITY is false, make sure that all leading directories  
   along FILE that exist are searchable.                                        
                                                                                
   If CHECK_EXTRA_PORTABILITY is true, check that file name components do not   
   begin with "-".                                                              
                                                                                
   If either CHECK_BASIC_PORTABILITY or CHECK_EXTRA_PORTABILITY is true,        
   check that the file name is not empty.                                       
                                                                                
   Return true if all of these tests are successful, false if any fail.  */     
                                                                                
static bool                                                                     Line 246
validate_file_name (char *file, bool check_basic_portability,                   Line 247
                    bool check_extra_portability)                               Line 248
{                                                                               
  size_t filelen = strlen (file);                                               Line 250
                                                                                
  /* Start of file name component being checked.  */                            
  char *start;                                                                  Line 253
                                                                                
  /* True if component lengths need to be checked.  */                          
  bool check_component_lengths;                                                 Line 256
                                                                                
  /* True if the file is known to exist.  */                                    
  bool file_exists = false;                                                     Line 259
                                                                                
  if (check_extra_portability && ! no_leading_hyphen (file))                    Line 261
    return false;                                                               Line 262
                                                                                
  if ((check_basic_portability || check_extra_portability)                      Line 264
      && filelen == 0)                                                          Line 265
    {                                                                           
      /* Fail, since empty names are not portable.  As of                       
         2005-01-06 POSIX does not address whether "pathchk -p ''"              
         should (or is allowed to) fail, so this is not a                       
         conformance violation.  */                                             
      error (0, 0, _("empty file name"));                                       Line 271
      return false;                                                             Line 272
    }                                                                           
                                                                                
  if (check_basic_portability)                                                  Line 275
    {                                                                           
      if (! portable_chars_only (file, filelen))                                Line 277
        return false;                                                           Line 278
    }                                                                           
  else                                                                          Line 280
    {                                                                           
      /* Check whether a file name component is in a directory that             
         is not searchable, or has some other serious problem.                  
         POSIX does not allow "" as a file name, but some non-POSIX             
         hosts do (as an alias for "."), so allow "" if lstat does.  */         
                                                                                
      struct stat st;                                                           Line 287
      if (lstat (file, &st) == 0)                                               Line 288...!syscalls auto-comment...
        file_exists = true;                                                     Line 289
      else if (errno != ENOENT || filelen == 0)                                 Line 290
        {                                                                       
          error (0, errno, "%s", quotef (file));                                Line 292
          return false;                                                         Line 293
        }                                                                       
    }                                                                           
                                                                                
  if (check_basic_portability                                                   Line 297
      || (! file_exists && PATH_MAX_MINIMUM <= filelen))                        Line 298
    {                                                                           
      size_t maxsize;                                                           Line 300
                                                                                
      if (check_basic_portability)                                              Line 302
        maxsize = _POSIX_PATH_MAX;                                              Line 303
      else                                                                      Line 304
        {                                                                       
          long int size;                                                        Line 306
          char const *dir = (*file == '/' ? "/" : ".");                         Line 307
          errno = 0;                                                            Line 308
          size = pathconf (dir, _PC_PATH_MAX);                                  Line 309
          if (size < 0 && errno != 0)                                           Line 310
            {                                                                   
              error (0, errno,                                                  Line 312
                     _("%s: unable to determine maximum file name length"),     Line 313
                     dir);                                                      Line 314
              return false;                                                     Line 315
            }                                                                   
          maxsize = MIN (size, SSIZE_MAX);                                      Line 317
        }                                                                       
                                                                                
      if (maxsize <= filelen)                                                   Line 320
        {                                                                       
          unsigned long int len = filelen;                                      Line 322
          unsigned long int maxlen = maxsize - 1;                               Line 323
          error (0, 0, _("limit %lu exceeded by length %lu of file name %s"),   Line 324
                 maxlen, len, quoteaf (file));                                  Line 325
          return false;                                                         Line 326
        }                                                                       
    }                                                                           
                                                                                
  /* Check whether pathconf (..., _PC_NAME_MAX) can be avoided, i.e.,           
     whether all file name components are so short that they are valid          
     in any file system on this platform.  If CHECK_BASIC_PORTABILITY, though,  
     it's more convenient to check component lengths below.  */                 
                                                                                
  check_component_lengths = check_basic_portability;                            Line 335
  if (! check_component_lengths && ! file_exists)                               Line 336
    {                                                                           
      for (start = file; *(start = component_start (start)); )                  Line 338
        {                                                                       
          size_t length = component_len (start);                                Line 340
                                                                                
          if (NAME_MAX_MINIMUM < length)                                        Line 342
            {                                                                   
              check_component_lengths = true;                                   Line 344
              break;                                                            Line 345
            }                                                                   
                                                                                
          start += length;                                                      Line 348
        }                                                                       
    }                                                                           
                                                                                
  if (check_component_lengths)                                                  Line 352
    {                                                                           
      /* The limit on file name components for the current component.           
         This defaults to NAME_MAX_MINIMUM, for the sake of non-POSIX           
         systems (NFS, say?) where pathconf fails on "." or "/" with            
         errno == ENOENT.  */                                                   
      size_t name_max = NAME_MAX_MINIMUM;                                       Line 358
                                                                                
      /* If nonzero, the known limit on file name components.  */               
      size_t known_name_max = (check_basic_portability ? _POSIX_NAME_MAX : 0);  Line 361
                                                                                
      for (start = file; *(start = component_start (start)); )                  Line 363
        {                                                                       
          size_t length;                                                        Line 365
                                                                                
          if (known_name_max)                                                   Line 367
            name_max = known_name_max;                                          Line 368
          else                                                                  Line 369
            {                                                                   
              long int len;                                                     Line 371
              char const *dir = (start == file ? "." : file);                   Line 372
              char c = *start;                                                  Line 373
              errno = 0;                                                        Line 374
              *start = '\0';                                                    Line 375
              len = pathconf (dir, _PC_NAME_MAX);                               Line 376
              *start = c;                                                       Line 377
              if (0 <= len)                                                     Line 378
                name_max = MIN (len, SSIZE_MAX);                                Line 379
              else                                                              Line 380
                switch (errno)                                                  Line 381
                  {                                                             
                  case 0:                                                       Line 383
                    /* There is no limit.  */                                   
                    name_max = SIZE_MAX;                                        Line 385
                    break;                                                      Line 386
                                                                                
                  case ENOENT:                                                  Line 388
                    /* DIR does not exist; use its parent's maximum.  */        
                    known_name_max = name_max;                                  Line 390
                    break;                                                      Line 391
                                                                                
                  default:                                                      Line 393
                    *start = '\0';                                              Line 394
                    error (0, errno, "%s", quotef (dir));                       Line 395
                    *start = c;                                                 Line 396
                    return false;                                               Line 397
                  }                                                             
            }                                                                   
                                                                                
          length = component_len (start);                                       Line 401
                                                                                
          if (name_max < length)                                                Line 403
            {                                                                   
              unsigned long int len = length;                                   Line 405
              unsigned long int maxlen = name_max;                              Line 406
              char c = start[len];                                              Line 407
              start[len] = '\0';                                                Line 408
              error (0, 0,                                                      Line 409
                     _("limit %lu exceeded by length %lu "                      Line 410
                       "of file name component %s"),                            Line 411
                     maxlen, len, quote (start));                               Line 412
              start[len] = c;                                                   Line 413
              return false;                                                     Line 414
            }                                                                   
                                                                                
          start += length;                                                      Line 417
        }                                                                       
    }                                                                           
                                                                                
  return true;                                                                  Line 421
}                                                                               Block 9