text: // SPDX-License-Identifier: GPL-3.0-or-later
  /*
   * License: GPLv3+
   * Copyright (c) 2013-2015 Davide Madrisan <davide.madrisan@gmail.com>
   *
   * A Nagios plugin to check for readonly filesystems.
   *
   * 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/>.
   */
  
  #include <getopt.h>
  #include <mntent.h>
  #include <fcntl.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <sys/types.h>
  #include <sys/stat.h>
  #include <unistd.h>
  
  #include "common.h"
  #include "string-macros.h"
  #include "messages.h"
  #include "mountlist.h"
  #include "xalloc.h"
  #include "progname.h"
  #include "progversion.h"
  
  static const char *program_copyright =
    "Copyright (C) 2013-2015 Davide Madrisan <" PACKAGE_BUGREPORT ">\n";
  
  /* A file system type to display. */
  
  struct fs_type_list
  {
    char *fs_name;
    struct fs_type_list *fs_next;
  };
  
  /* Linked list of file system types to display.
   * If 'fs_select_list' is NULL, list all types.
   * This table is generated dynamically from command-line options,
   * rather than hardcoding into the program what it thinks are the
   * valid file system types; let the user specify any file system type
   * they want to, and if there are any file systems of that type, they
   * will be shown.
   *
   * Some file system types:
   * 4.2 4.3 ufs nfs swap ignore io vm efs dbg */
  
  static struct fs_type_list *fs_select_list;
  
  /* Linked list of file system types to omit.
   *    If the list is empty, don't exclude any types.  */
  static struct fs_type_list *fs_exclude_list;
  
  /* Linked list of mounted file systems. */
  static struct mount_entry *mount_list;
  
  /* If true, show even file systems with zero size or
     uninteresting types. */
  static bool show_all_fs;
  
  /* If true, show only local file systems.  */
  static bool show_local_fs;
  
  /* If true, show each file system corresponding to the
     command line arguments.  */
  static bool verbose = false;
  
  static struct option const longopts[] = {
    {(char *) "all", no_argument, NULL, 'a'},
    {(char *) "local", no_argument, NULL, 'l'},
    {(char *) "type", required_argument, NULL, 'T'},
    {(char *) "exclude-type", required_argument, NULL, 'X'},
    {(char *) "verbose", no_argument, NULL, 'v'},
    {(char *) "help", no_argument, NULL, GETOPT_HELP_CHAR},
    {(char *) "version", no_argument, NULL, GETOPT_VERSION_CHAR},
    {NULL, 0, NULL, 0}
  };
  
  static _Noreturn void
  usage (FILE * out)
  {
    fprintf (out, "%s (" PACKAGE_NAME ") v%s\n", program_name, program_version);
    fputs ("This plugin checks for readonly filesystems.\n", out);
    fputs (program_copyright, out);
    fputs (USAGE_HEADER, out);
    fprintf (out, "  %s [OPTION]... [FILESYSTEM]...\n", program_name);
    fputs (USAGE_OPTIONS, out);
    fputs ("  -a, --all       include dummy file systems\n", out);
    fputs ("  -l, --local     limit listing to local file systems\n",
  	 out);
    fputs ("  -T, --type=TYPE   limit listing to file systems of type TYPE\n",
  	 out);
    fputs ("  -X, --exclude-type=TYPE   "
  	 "limit listing to file systems not of type TYPE\n", out);
    fputs ("  -v, --verbose   show details for command-line debugging "
           "(Nagios may truncate output)\n", out);
    fputs (USAGE_HELP, out);
    fputs (USAGE_VERSION, out);
    fputs (USAGE_EXAMPLES, out);
    fprintf (out, "  %s -l -T ext3 -T ext4\n", program_name);
    fprintf (out, "  %s -l -X vfat\n", program_name);
  
    exit (out == stderr ? STATE_UNKNOWN : STATE_OK);
  }
  
  static _Noreturn void
  print_version (void)
  {
    printf ("%s (" PACKAGE_NAME ") v%s\n", program_name, program_version);
    fputs (program_copyright, stdout);
    fputs (GPLv3_DISCLAIMER, stdout);
  
    exit (STATE_OK);
  }
  
  /* Add FSTYPE to the list of file system types to display. */
  
  static void
  add_fs_type (const char *fstype)
  {
    struct fs_type_list *fsp;
  
    fsp = xmalloc (sizeof *fsp);
    fsp->fs_name = (char *) fstype;
    fsp->fs_next = fs_select_list;
    fs_select_list = fsp;
  }
  
  /* Is FSTYPE a type of file system that should be listed?  */
  
  static bool
  selected_fstype (const char *fstype)
  {
    const struct fs_type_list *fsp;
  
    if (fs_select_list == NULL || fstype == NULL)
      return true;
    for (fsp = fs_select_list; fsp; fsp = fsp->fs_next)
      if (STREQ (fstype, fsp->fs_name))
        return true;
    return false;
  }
  
  /* Add FSTYPE to the list of file system types to be omitted. */
  
  static void
  add_excluded_fs_type (const char *fstype)
  {
    struct fs_type_list *fsp;
  
    fsp = xmalloc (sizeof *fsp);
    fsp->fs_name = (char *) fstype;
    fsp->fs_next = fs_exclude_list;
    fs_exclude_list = fsp;
  }
  
  /* Is FSTYPE a type of file system that should be omitted?  */
  
  static bool
  excluded_fstype (const char *fstype)
  {
    const struct fs_type_list *fsp;
  
    if (fs_exclude_list == NULL || fstype == NULL)
      return false;
    for (fsp = fs_exclude_list; fsp; fsp = fsp->fs_next)
      if (STREQ (fstype, fsp->fs_name))
        return true;
    return false;
  }
  
  static bool
  skip_mount_entry (struct mount_entry *me)
  {
    if (me->me_remote && show_local_fs)
      return true;
  
    if (me->me_dummy && !show_all_fs)
      return true;
  
    if (!selected_fstype (me->me_type) || excluded_fstype (me->me_type))
      return true;
  
    return false;
  }
  
  static int
  check_all_entries (char **ro_filesystems)
  {
    struct mount_entry *me;
    int status = STATE_OK;
    char *p;
    size_t len;
  
    for (me = mount_list; me; me = me->me_next)
      {
        if (skip_mount_entry (me))
  	continue;
  
        if (verbose)
  	printf ("%-10s %s type %s (%s) %s\n",
  		me->me_devname, me->me_mountdir, me->me_type, me->me_opts,
  		(me->me_readonly) ? "<< read-only" : "");
        if (me->me_readonly)
  	{
  	  if (*ro_filesystems == NULL)
  	    *ro_filesystems = xstrdup (me->me_mountdir);
  	  else
  	    {
  	      len = strlen (*ro_filesystems) + strlen (me->me_mountdir) + 2;
  	      p = xrealloc (*ro_filesystems, len);
  	      sprintf (p, "%s %s", *ro_filesystems, me->me_mountdir);
  	      *ro_filesystems = p;
  	    }
  	  status = STATE_CRITICAL;
  	}
      }
  
    return status;
  }
  
  static int
  check_entry (char const *name)
  {
    struct mount_entry *me;
  
    if (name == NULL)
      return STATE_OK;
  
    for (me = mount_list; me; me = me->me_next)
      if (STREQ (me->me_mountdir, name))
        {
  	if (skip_mount_entry (me))
  	  return STATE_OK;
  
  	if (verbose)
  	  printf ("%-10s %s type %s (%s) %s\n",
  		  me->me_devname, me->me_mountdir, me->me_type, me->me_opts,
  		  (me->me_readonly) ? "<< read-only" : "");
  
  	if (me->me_readonly)
  	  return STATE_CRITICAL;
        }
  
    return STATE_OK;
  }
  
  int
  main (int argc, char **argv)
  {
    int c, i;
    int status = STATE_OK;
    char *ro_filesystems = NULL;
  
    fs_select_list = NULL;
    fs_exclude_list = NULL;
  
    show_local_fs = false;
    show_all_fs = false;
  
    set_program_name (argv[0]);
  
    while ((c = getopt_long (argc, argv,
            "alT:X:v" GETOPT_HELP_VERSION_STRING, longopts, NULL)) != -1)
      {
        switch (c)
  	{
  	default:
  	  usage (stderr);
  	case 'a':
  	  show_all_fs = true;
  	  break;
  	case 'l':
  	  show_local_fs = true;
  	  break;
  	case 'T':
  	  add_fs_type (optarg);
  	  break;
  	case 'X':
  	  add_excluded_fs_type (optarg);
  	  break;
  	case 'v':
  	  verbose = true;
  	  break;
  
  	case_GETOPT_HELP_CHAR
          case_GETOPT_VERSION_CHAR
  
  	}
      }
  
    /* Fail if the same file system type was both selected and excluded.  */
    {
      struct fs_type_list *fs_incl;
      for (fs_incl = fs_select_list; fs_incl; fs_incl = fs_incl->fs_next)
        {
  	struct fs_type_list *fs_excl;
  	for (fs_excl = fs_exclude_list; fs_excl; fs_excl = fs_excl->fs_next)
  	  {
  	    if (STREQ (fs_incl->fs_name, fs_excl->fs_name))
  	      plugin_error (STATE_UNKNOWN, 0,
  		            "file system type `%s' both selected and "
                              "excluded",
  		            fs_incl->fs_name);
  	  }
        }
    }
  
    if (optind < argc)
      {
        /* Open each of the given entries to make sure any corresponding
         * partition is automounted.  This must be done before reading the
         * file system table.  */
        for (i = optind; i < argc; ++i)
  	{
  	  int fd = open (argv[i], O_RDONLY | O_NOCTTY);
  	  if (fd < 0)
  	    plugin_error (STATE_UNKNOWN, 0, "cannot open `%s'", argv[i]);
  	  close (fd);
  	}
      }
  
    mount_list =
      read_file_system_list ((fs_select_list != NULL
  			    || fs_exclude_list != NULL || show_local_fs));
  
    if (NULL == mount_list)
      /* Couldn't read the table of mounted file systems. */
      plugin_error (STATE_UNKNOWN, 0,
                    "cannot read table of mounted file systems");
  
    if (optind < argc)
      {
        for (i = optind; i < argc; ++i)
  	{
  	  if (STATE_CRITICAL == check_entry (argv[i]))
  	    status = STATE_CRITICAL;
  	  else
  	    argv[i] = NULL;
  	}
  
        printf ("%s %s", program_name_short, state_text (status));
        if (STATE_CRITICAL == status)
  	{
  	  for (i = optind; i < argc; ++i)
  	    if (argv[i])
  	      printf (" %s", argv[i]);
  	  printf (" readonly!");
  	}
  
        putchar ('\n');
        return status;
      }
  
    status = check_all_entries (&ro_filesystems);
  
    if (STATE_CRITICAL == status)
      {
        printf ("%s %s: %s readonly!\n", program_name_short, state_text (status),
  	      ro_filesystems);
        free (ro_filesystems);
      }
    else
      printf ("%s %s\n", program_name_short, state_text (status));
  
    return status;
  }
  