#!/usr/bin/env slsh
% A fast /proc filesystem checker for Linux.  It looks for hidden processes.
% Copyright (C) 2008 John E. Davis <jed@jedsoft.org>
% 
% The chkproc2.sl script 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 2 of the
% License, or (at your option) any later version.
%
% This script 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.  It is available online at
% <http://www.gnu.org/copyleft/gpl.html>.
%
require ("cmdopt");
require ("glob");

private variable FIRST_PID = 1;
private variable LAST_PID = 99999;
private variable FIRST_GID = 1;	       %  does not include 0
private variable LAST_GID = 0xFFFF;

private variable Script_Version_String = "0.1.0";

define get_info (dir)
{
   variable exe = readlink (path_concat (dir, "exe"));
   if (exe == NULL)
     {
	% Could be an internal pgm, or process has no permission to
	% read the link.
	variable fp = fopen (path_concat (dir, "status"), "r");
	if ((fp == NULL) 
	    || (-1 == fgets (&exe, fp)))
	  return NULL;
	variable len = strlen (exe);
	if (strncmp (exe, "Name:\t", 6))
	  exe = "Unknown";
	else
	  exe = strcat ("<", substr (exe, 7, len-7), ">");
     }

   return struct
     {
	exe = exe,
     };
}

define print_info (pid, dir)
{
   variable s = get_info (dir);
   if (s == NULL)
     return;
   
   () = fprintf (stdout, "%d:%s\n", pid, s.exe);
}

private define exit_version ()
{
   () = fprintf (stdout, "Version: %S\n", Script_Version_String);
   exit (0);
}

private define exit_usage ()
{
   variable fp = stderr;
   () = fprintf (fp, "Usage: %s [options]\n", __argv[0]);
   variable opts = 
     [
      "Options:\n",
      " -g, --gids=RANGE            Use specified GID range\n",
      " -q, --quiet                 Run quietly, showing only warnings\n",
      " -v, --version               Print version\n",
      " -h, --help                  This message\n",
     ];
   foreach (opts)
     {
	variable opt = ();
	() = fputs (opt, fp);
     }
   exit (1);
}

define slsh_main ()
{
   variable grange = "${FIRST_GID}:${LAST_GID}"$;
   variable quiet = 0;
   variable c = cmdopt_new ();
   c.add ("q|quiet", &quiet; default=1);
   c.add ("g|gid", &grange; type="str");
   c.add("h|help", &exit_usage);
   c.add("v|version", &exit_version);
   variable i = c.process (__argv, 1);
   if (i != __argc)
     exit_usage (1);
   variable gmin, gmax, gdir;
   
   if (grange != NULL)
     {
	grange = strtrans (grange, "-:", " ");
	if (2 != sscanf (grange, "%d %d", &gmin, &gmax))
	  {
	     () = fprintf (stderr, "Error parsing group range\n");
	     exit (1);
	  }
	gdir = 1;
	if (gmin > gmax)
	  gdir = -1;
     }

   variable pid, gid;
   variable suspect_pid_list = {};
   variable hidden_pid_list = {};
   variable dir;
   variable is_thread = Char_Type[LAST_PID+1];

   () = setgid (0);
   
   variable dirs = glob ("/proc/*");
   _for pid (FIRST_PID, LAST_PID, 1)
     {
	dir = sprintf ("/proc/%d", pid);
	if (0 == chdir (dir))
	  {
	     foreach (listdir (strcat (dir, "/task")))
	       {
		  variable tpid = ();
		  tpid = atoi (tpid);
		  if (tpid != pid)
		    is_thread[tpid] = 1;
	       }
	     ifnot (quiet) print_info (pid, dir);

	     if (NULL == wherefirst (dirs == dir))
	       {
		  % check for race condition
		  dirs = glob("/proc/*");
		  if ((NULL == wherefirst (dirs == dir))
		      && (0 == chdir (dir)))
		    list_append (hidden_pid_list, pid);
	       }
	     continue;
	  }

	if (-1 != kill (pid, 0))
	  {
	     % pid present -- could be the result of a race condition
	     if (0 == chdir (dir))
	       {
		  ifnot (quiet) print_info (pid, dir);
		  continue;
	       }

	     if (-1 != kill (pid, 0))
	       {
		  () = fprintf (stderr, "WARNING: pid %d exists, but chdir %s fails\n", pid, dir);
		  list_append (suspect_pid_list, pid);
	       }
	  }
     }

   foreach pid (hidden_pid_list)
     {
	if (is_thread[pid])
	  {
	     ifnot (quiet)
	       vmessage ("pid %d appears to be a thread", pid);
	     continue;
	  }
	dir = sprintf ("/proc/%d", pid);
	if (0 == chdir (dir))
	  {
	     () = fprintf (stderr, "WARNING: %s is hidden and does not appear to be a thread\n", dir);
	     print_info (pid, dir);
	  }
     }

   ifnot (length (suspect_pid_list))
     exit (0);

   ifnot (quiet)
     vmessage ("Checking suspect pids using gid range g=%d:%d", gmin, gmax);

   foreach pid (suspect_pid_list)
     {
	ifnot (quiet)
	  vmessage ("Processing suspect pid %d", pid);
	
	if (-1 == kill (pid, 0))
	  {
	     () = fprintf (stderr, "Suspect pid %d appears to have exited\n", pid);
	     continue;
	  }

	dir = sprintf ("/proc/%d", pid);

	_for gid (gmin, gmax, gdir)
	  {
	     if (0 != setgid (gid))
	       continue;

	     if (0 == chdir (dir))
	       {
		  () = fprintf (stdout, "WARNING: %s needs gid=%d for access\n", dir, gid);
		  print_info (pid, dir);
		  break;
	       }
	  }
	then
	  {
	     () = fprintf (stderr, "Could not access %s using gids in the range %d-%d\n",
			   dir, gmin, gmax);
	  }
     }
   exit (0);
}
