/* preload_cache: Efficiently read one or more files into the operating
   system's cache.  Non-mappable files are silently skipped.  A mutex
   file may be used to make separate invocations run sequentially, which
   is useful for speeding up a parallel `make'.

   Copyright (C) 1997, 1999 Jamie Lokier.

   This file 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, or (at your option) any
   later version.

   This file 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 file; see the file COPYING.  If not, write to the
   Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <string.h>
#include <getopt.h>
#include <setjmp.h>

static sigjmp_buf segv_jmpbuf;

static void
segv_handler ()
{
  siglongjmp (segv_jmpbuf, 1);
}

int
main (int argc, char ** argv)
{
  size_t page_size = getpagesize ();

  static const char * usage_string = "\
Usage: %s [OPTION]... FILE...\n\
Efficiently read FILE(s) into the operating system's file cache.\n\
Non-mappable files are silently skipped.  A mutex file may be used\n\
to make separate instances of this program run sequentially, which\n\
is useful for speeding up a parallel `make'.\n\
\n\
  -b, --bytes=N           read at most N bytes into the cache\n\
  -m, --mutex             lock `/tmp/preload_cache_mutex' so that\n\
                          separate instances of this program will\n\
                          run sequentially\n\
  -M, --mutex-file=FILE   lock FILE so that separate instances of\n\
                          this program that also lock FILE will\n\
                          run sequentially\n\
      --help              display this help and exit\n\
      --version           output version information and exit\n\
\n\
At most one mutex file will be used.\n";

  static struct option long_options [] =
  {
    {"bytes",      required_argument, 0, 'b'},
    {"mutex",      no_argument,       0, 'm'},
    {"mutex-file", required_argument, 0, 'M'},
    {"help",       no_argument,       0, 'h'},
    {"version",    no_argument,       0, 'v'},
    {0, 0, 0, 0}
  };

  char help_flag = 0;
  char version_flag = 0;
  char error_flag = 0;

  char          bytes_flag = 0;
  unsigned long bytes_value = 0;

  const char * mutex_file = "/tmp/preload_cache_mutex";
  int          mutex_fd = -1;
  char         mutex_flag = 0;

  while (1)
    {
      char c = getopt_long (argc, argv, "b:mM:h", long_options, 0);

      if (c == -1)
	break;

      switch (c)
	{
	case 'b':
	  {
	    char * endptr;
	    errno = 0;
	    bytes_value = strtoul (optarg, &endptr, 0);
	    if (*endptr != '\0' || errno == ERANGE)
	      {
		fprintf (stderr, "%s: invalid number of bytes: %s\n",
			 argv [0], optarg);
		return EXIT_FAILURE;
	      }
	    bytes_flag = 1;
	    break;
	  }

	case 'm':
	  mutex_flag = 1;
	  break;

	case 'M':
	  mutex_flag = 1;
	  mutex_file = optarg;
	  break;

	case 'h':
	  help_flag = 1;
	  break;

	case 'v':
	  version_flag = 1;
	  break;

	case '?':
	  error_flag = 1;
	}
    }

  if (error_flag)
    {
      fprintf (stderr, "Try `%s --help' for more information.\n", argv [0]);
      return EXIT_FAILURE;
    }

  if (version_flag)
    {
      printf ("preload_cache - version 0.2, "
	      "Copyright (C) 1997, 1999 Jamie Lokier; licensed under GNU GPL terms\n");
      return EXIT_SUCCESS;
    }

  if (help_flag)
    {
      printf (usage_string, argv [0]);
      return EXIT_SUCCESS;
    }

  if (mutex_flag)
    {
      mutex_fd = open (mutex_file, O_RDONLY | O_CREAT, 0444);

      if (mutex_fd == -1)
	{
	  perror (mutex_file);
	  return EXIT_FAILURE;
	}

      if (flock (mutex_fd, LOCK_EX) == -1)
	{
	  perror (mutex_file);
	  return EXIT_FAILURE;
	}
    }

  while (optind < argc)
    {
      int fd = open (argv [optind], O_RDONLY);
      int status;
      struct stat st;

      if (fd == -1)
	{
	  perror (argv [optind]);
	  return EXIT_FAILURE;
	}

      status = fstat (fd, &st);

      if (status == -1)
	{
	  perror (argv [optind]);
	  return EXIT_FAILURE;
	}

      if ((S_ISREG (st.st_mode) || S_ISBLK (st.st_mode))
	  && st.st_size > 0)
	{
	  size_t size = st.st_size, count;
	  void * mapped_area;

	  if (bytes_flag && size > bytes_value)
	    size = bytes_value;

	  mapped_area = mmap (0, size, PROT_READ, MAP_SHARED, fd, 0);

	  count = (size + (page_size-1)) & ~(size_t) (page_size-1);

#ifndef MAP_FAILED
#define MAP_FAILED ((void *) -1)
#endif

	  if (mapped_area == MAP_FAILED)
	    perror (argv [optind]);	/* Only warn; don't terminate. */
	  else
	    {
	      char * ptr = (char *) mapped_area;

	      /* Page in the file by referencing its mapped pages.  Trap
		 SIGSEGV signals.  We can receive them due to some kinds
		 of file read error (such as permission denied over
		 NFS).  If the program just terminated it would look
		 like it had a bug. */

	      if (sigsetjmp (segv_jmpbuf, 1) == 0)
		{
		  signal (SIGSEGV, segv_handler);
		  while (count != 0)
		    {
		      * (volatile *) ptr;
		      ptr += page_size;
		      count -= page_size;
		    }
		}
	      else
		{
		  signal (SIGSEGV, SIG_DFL);
		  /* Only warn; don't terminate. */
		  fprintf (stderr, "%s: while referencing %s: "
			   "received SIGSEGV signal\n",
			   argv [0], argv [optind]);
		}
	      signal (SIGSEGV, SIG_DFL);
	      munmap (mapped_area, size);

	      if (bytes_flag)
		{
		  if (bytes_value <= size)
		    break;

		  bytes_value -= size;
		}
	    }
	}

      close (fd);
      optind++;
    }

  if (mutex_flag && flock (mutex_fd, LOCK_UN) == -1)
    {
      perror (mutex_file);
      return EXIT_FAILURE;
    }

  return EXIT_SUCCESS;
}

