/* Taken from Ted's losetup.c - Mitch <m.dsouza@mrc-apu.cam.ac.uk> */
/* Added vfs mount options - aeb - 960223 */
/* Removed lomount - aeb - 960224 */

#define PROC_DEVICES	"/proc/devices"

/*
 * losetup.c - setup and control loop devices
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/stat.h>

#include "loop.h"
#include "lomount.h"

char *xstrdup (const char *s);		/* not: #include "sundries.h" */
void error (const char *fmt, ...);	/* idem */

#ifdef LOOP_SET_FD
struct crypt_type_struct {
  int id;
  char *name;
} crypt_type_tbl[] = {
  { LO_CRYPT_NONE, "no" },
  { LO_CRYPT_NONE, "none" },
  { LO_CRYPT_XOR, "xor" },
  { LO_CRYPT_DES, "DES" },
  { -1, NULL   }
};

static int 
crypt_type (const char *name)
{
  int i;

  if (name)
    for (i = 0; crypt_type_tbl[i].id != -1; i++)
      if (!strcasecmp (name, crypt_type_tbl[i].name))
	return crypt_type_tbl[i].id;
  return -1;
}

#if 0
static char *
crypt_name (int id)
{
  int i;

  for (i = 0; crypt_type_tbl[i].id != -1; i++)
    if (id == crypt_type_tbl[i].id)
      return crypt_type_tbl[i].name;
  return "undefined";
}

static void 
show_loop (char *device)
{
  struct loop_info loopinfo;
  int fd;

  if ((fd = open (device, O_RDONLY)) < 0) {
    fprintf(stderr, "loop: can't open device %s: %s\n",
	    device, strerror (errno));
    return;
  }
  if (ioctl (fd, LOOP_GET_STATUS, &loopinfo) < 0) {
    fprintf(stderr, "loop: can't get info on device %s: %s\n",
	    device, strerror (errno));
    close (fd);
    return;
  }
  printf ("%s: [%04x]:%ld (%s) offset %d, %s encryption\n",
	  device, loopinfo.lo_device, loopinfo.lo_inode,
	  loopinfo.lo_name, loopinfo.lo_offset,
	  crypt_name (loopinfo.lo_encrypt_type));
  close (fd);
}
#endif

char *
find_unused_loop_device (void)
{
    /* Just creating a device, say in /tmp, is probably a bad idea -
       people might have problems with backup or so.
       So, we just try /dev/loop[0-7]. */
    char dev[20];
    int i, fd, somedev = 0, someloop = 0, loop_known = 0;
    struct stat statbuf;
    struct loop_info loopinfo;
    FILE *procdev;

    for(i = 0; i < 256; i++) {
      sprintf(dev, "/dev/loop%d", i);
      if (stat (dev, &statbuf) == 0 && S_ISBLK(statbuf.st_mode)) {
	somedev++;
	fd = open (dev, O_RDONLY);
	if (fd >= 0) {
	  if(ioctl (fd, LOOP_GET_STATUS, &loopinfo) == 0)
	    someloop++;			/* in use */
	  else if (errno == ENXIO) {
	    close (fd);
	    return xstrdup(dev);	/* probably free */
	  }
	  close (fd);
        }
	continue;		/* continue trying as long as devices exist */
      }
      if (i >= 7)
	break;
    }

    /* Nothing found. Why not? */
    if ((procdev = fopen(PROC_DEVICES, "r")) != NULL) {
      char line[100];
      while (fgets (line, sizeof(line), procdev))
	if (strstr (line, " loop\n")) {
	  loop_known = 1;
	  break;
	}
      fclose(procdev);
      if (!loop_known)
	loop_known = -1;
    }

    if (!somedev)
      error("mount: could not find any device /dev/loop#");
    else if(!someloop) {
      if (loop_known == 1)
	error(
"mount: Could not find any loop device.\n"
"       Maybe /dev/loop# has a wrong major number?");
      else if (loop_known == -1)
	error(
"mount: Could not find any loop device, and, according to %s,\n"
"       this kernel does not know about the loop device.\n"
"       (If so, then recompile or `insmod loop.o'.)", PROC_DEVICES);
      else
	error(
"mount: Could not find any loop device. Maybe this kernel does not know\n"
"       about the loop device (then recompile or `insmod loop.o'), or\n"
"       maybe /dev/loop# has the wrong major number?");
    } else
      error("mount: could not find any free loop device");
    return 0;
}

int
set_loop (const char *device, const char *file, int offset,
	  const char *encryption, int *loopro)
{
  struct loop_info loopinfo;
  int fd, ffd, mode, i;
  char *pass;

  mode = (*loopro ? O_RDONLY : O_RDWR);
  if ((ffd = open (file, mode)) < 0) {
       if (!*loopro && errno == EROFS)
	    ffd = open (file, mode = O_RDONLY);
       if (ffd < 0) {
	    perror (file);
	    return 1;
       }
  }
  if ((fd = open (device, mode)) < 0) {
    perror (device);
    return 1;
  }
  *loopro = (mode == O_RDONLY);
  memset (&loopinfo, 0, sizeof (loopinfo));
  strncpy (loopinfo.lo_name, file, LO_NAME_SIZE);
  loopinfo.lo_name[LO_NAME_SIZE - 1] = 0;
  if (encryption && (loopinfo.lo_encrypt_type = crypt_type (encryption))
      < 0) {
    fprintf (stderr, "Unsupported encryption type %s\n", encryption);
    return 1;
  }
  loopinfo.lo_offset = offset;
  switch (loopinfo.lo_encrypt_type) {
  case LO_CRYPT_NONE:
    loopinfo.lo_encrypt_key_size = 0;
    break;
  case LO_CRYPT_XOR:
    pass = getpass ("Password: ");
    strncpy (loopinfo.lo_encrypt_key, pass, LO_KEY_SIZE);
    loopinfo.lo_encrypt_key[LO_KEY_SIZE - 1] = 0;
    loopinfo.lo_encrypt_key_size = strlen (loopinfo.lo_encrypt_key);
    break;
  case LO_CRYPT_DES:
    pass = getpass ("Password: ");
    strncpy (loopinfo.lo_encrypt_key, pass, 8);
    loopinfo.lo_encrypt_key[8] = 0;
    loopinfo.lo_encrypt_key_size = 8;
    pass = getpass ("Init (up to 16 hex digits): ");
    for (i = 0; i < 16 && pass[i]; i++)
      if (isxdigit (pass[i]))
	loopinfo.lo_init[i >> 3] |= (pass[i] > '9' ?
				     (islower (pass[i]) ? toupper (pass[i]) :
			pass[i]) - 'A' + 10 : pass[i] - '0') << (i & 7) * 4;
      else {
	fprintf (stderr, "Non-hex digit '%c'.\n", pass[i]);
	return 1;
      }
    break;
  default:
    fprintf (stderr,
	     "Don't know how to get key for encryption system %d\n",
	     loopinfo.lo_encrypt_type);
    return 1;
  }
  if (ioctl (fd, LOOP_SET_FD, ffd) < 0) {
    perror ("ioctl: LOOP_SET_FD");
    return 1;
  }
  if (ioctl (fd, LOOP_SET_STATUS, &loopinfo) < 0) {
    (void) ioctl (fd, LOOP_CLR_FD, 0);
    perror ("ioctl: LOOP_SET_STATUS");
    return 1;
  }
  close (fd);
  close (ffd);
  if (mount_verbose > 1)
    printf("set_loop(%s,%s,%d): success\n", device, file, offset);
  return 0;
}

int 
del_loop (const char *device)
{
  int fd;

  if ((fd = open (device, O_RDONLY)) < 0) {
    fprintf(stderr, "loop: can't delete device %s: %s\n",
	    device, strerror (errno));
    return 1;
  }
  if (ioctl (fd, LOOP_CLR_FD, 0) < 0) {
    perror ("ioctl: LOOP_CLR_FD");
    return 1;
  }
  close (fd);
  if (mount_verbose > 1)
    printf("del_loop(%s): success\n", device);
  return 0;
}

#else /* no LOOP_SET_FD defined */
static void
mutter(void) {
  fprintf(stderr,
	  "This mount was compiled without loop support. Please recompile.\n");
}  

int
set_loop (const char *device, const char *file, int offset,
	  const char *encryption, int *loopro) {
  mutter();
  return 1;
}

int
del_loop (const char *device) {
  mutter();
  return 1;
}

char *
find_unused_loop_device (void) {
  mutter();
  return 0;
}

#endif
