/* findnull.c --- Find non-emtpy files consisting entirely of null bytes

   Copyright (C) 2001 Colin Walters <walters@xxxxxxxxxxxxxxxxxx>
   This program is hereby placed in the public domain. */

#define _GNU_SOURCE

#include <unistd.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#include <fstab.h>
#include <errno.h>

void fatal_error(const char *, ...);
int null_file_p(const char *);
int print_if_null_file(const char *, const struct stat *, int);
void print_null_files_in_dir(const char *);

static int n_excluded = 4;
static char *excluded[] = {"/proc", "/sys", "/dev", "/run"};

static int verbose = 0;

int main(int argc, char **argv)
{
  print_null_files_in_dir("/");

  exit(0);
}
      
void fatal_error(const char *__restrict format, ...)
{
  va_list args;
  va_start(args, format);
  vfprintf(stderr, format, args);
  exit(1);
}

int null_file_p(const char *filename)
{
  static char buf[8192];
  size_t num;
  int retval = 0;
  FILE *fp;

  if (!(fp = fopen(filename, "r")))
    {
      perror("fopen");
      return -1;
    }

  while ((num = fread(buf, sizeof(char), sizeof(buf), fp)) > 0)
    {
      size_t n;
      for (n = 0; n < num; n++)
	if (buf[n] != '\0')
	  {
	    retval = 0;
	    goto cleanup;
	  }
    }
  if (num < 0)
    {
      perror("fread");
      retval = -1;
      goto cleanup;
    }
  retval = 1;
 cleanup:
  fclose(fp);
  return retval;
}

void print_null_files_in_dir(const char *filename)
{
  struct dirent *ent;
  struct stat buf;
  int i;
  DIR *cur;
  
  if (verbose > 1)
    fprintf(stderr, "checking dir %s\n", filename);

  for (i = 0; i < n_excluded; i++)
    if (!strncmp(filename, excluded[i], strlen(excluded[i])))
      {
	if (verbose)
	  fprintf(stderr, "dir %s is excluded\n", filename);
	
	return;
      }

  if (!(cur = opendir(filename)))
    fatal_error("opendir failed on %s: %s", filename, strerror(errno));

  while ((ent = readdir(cur)) != NULL)
    {
      int ret = 0;
      char *curname;
      if (ent->d_name[0] == '\0'
	  || !strncmp(ent->d_name, ".", 2)
	  || !strncmp(ent->d_name, "..", 3))
	continue;

      if (!
	  ({
	    /* Make things look a bit prettier */
	    if (!strncmp(filename, "/", 2))
	      asprintf(&curname, "/%s", ent->d_name);
	    else
	      asprintf(&curname, "%s/%s", filename, ent->d_name);
	    curname;
	  }))
	fatal_error("out of memory");
      
      if (verbose > 1)
	  fprintf(stderr, "checking entry %s\n", curname);

      if (lstat(curname, &buf))
	perror("stat");
      else if (S_ISDIR(buf.st_mode))
	print_null_files_in_dir(curname);
      else if (S_ISREG(buf.st_mode)
	       && buf.st_size > 0
	       && ((ret = null_file_p(curname)) == 1))
	fprintf(stdout, "%s\n", curname);
      
      if (ret < 0)
	fprintf(stderr, "failed to test %s\n", curname);

      free(curname);
    }

  if (verbose > 1)
    fprintf(stderr, "exiting dir %s\n", filename);
  closedir(cur);
} 

