/*
 * Guillaume Cottenceau (gc)
 *
 * Copyright 2000 Mandriva
 *
 * This software may be freely redistributed under the terms of the GNU
 * public license.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/* This code comes from util-linux-2.10n (mount/lomount.c)
 * (this is a simplified version of this code)
 */

#include <sys/types.h>
#include <sys/mount.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "stage1.h"
#include "frontend.h"
#include "log.h"
#include "mount.h"
#include "modules.h"

#include "lomount.h"


#define LO_NAME_SIZE	64
#define LO_KEY_SIZE	32

struct loop_info
{
	int		lo_number;	/* ioctl r/o */
	dev_t		lo_device; 	/* ioctl r/o */
	unsigned long	lo_inode; 	/* ioctl r/o */
	dev_t		lo_rdevice; 	/* ioctl r/o */
	int		lo_offset;
	int		lo_encrypt_type;
	int		lo_encrypt_key_size; 	/* ioctl w/o */
	int		lo_flags;	/* ioctl r/o */
	char		lo_name[LO_NAME_SIZE];
	unsigned char	lo_encrypt_key[LO_KEY_SIZE]; /* ioctl w/o */
	unsigned long	lo_init[2];
	char		reserved[4];
};

#define LOOP_SET_FD	0x4C00
#define LOOP_CLR_FD	0x4C01
#define LOOP_SET_STATUS	0x4C02
#define LOOP_GET_STATUS	0x4C03

int
set_loop (const char *device, const char *file)
{
	struct loop_info loopinfo;
	int fd, ffd, mode;

	mode = O_RDONLY;

	if ((ffd = open (file, mode)) < 0)
		return 1;
  
	if ((fd = open (device, mode)) < 0) {
		close(ffd);
		return 1;
	}

	memset(&loopinfo, 0, sizeof (loopinfo));
	strncpy(loopinfo.lo_name, file, LO_NAME_SIZE);
	loopinfo.lo_name[LO_NAME_SIZE - 1] = 0;
	loopinfo.lo_offset = 0;

#ifdef MCL_FUTURE  
	/*
	 * Oh-oh, sensitive data coming up. Better lock into memory to prevent
	 * passwd etc being swapped out and left somewhere on disk.
	 */
  
	  if(mlockall(MCL_CURRENT|MCL_FUTURE)) {
		  log_message("CRITICAL Couldn't lock into memory! %s (memlock)", strerror(errno));
		  return 1;
	  }
#endif

	if (ioctl(fd, LOOP_SET_FD, ffd) < 0) {
		close(fd);
		close(ffd);
		return 1;
	}

	if (ioctl(fd, LOOP_SET_STATUS, &loopinfo) < 0) {
		(void) ioctl (fd, LOOP_CLR_FD, 0);
		close(fd);
		close(ffd);
		return 1;
	}

	close(fd);
	close(ffd);
	return 0;
}


char* find_free_loop()
{
	struct loop_info loopinfo;
        int i;
        for (i=0; i<256; i++) {
                int fd;
		char ldev[100];
		sprintf(ldev, "/dev/loop%d", i);
                ensure_dev_exists(ldev);
                fd = open(ldev, O_RDONLY);
                if (!ioctl(fd, LOOP_GET_STATUS, &loopinfo)) {
                        close(fd);
                        continue;
                }
                if (errno == ENXIO) {
                        log_message("%s is available", ldev);
                        close(fd);
                        return strdup(ldev);
                } else {
                        log_perror("LOOP_GET_STATUS(unexpected error)");
                        close(fd);
                        continue;
                }
        }
        return NULL;
}

void
del_loop(char * loopdev)
{
	int fd;

        if (!loopdev)
                return;

	if ((fd = open (loopdev, O_RDONLY)) < 0)
		return;

	if (ioctl (fd, LOOP_CLR_FD, 0) < 0)
		return;
  
	close (fd);
}

int
lomount(char *loopfile, char *where, char **dev, int compressed)
{
  
	long int flag;
        char * loopdev;

	flag = MS_MGC_VAL;
	flag |= MS_RDONLY;

	my_modprobe("loop", ANY_DRIVER_TYPE, "max_loop=256");
	if (compressed) {
	    my_modprobe("squashfs", ANY_DRIVER_TYPE, NULL);
	}

        if (!(loopdev = find_free_loop())) {
		log_message("could not find a free loop");
		return 1;
        }
        if (dev)
                *dev = loopdev;

	if (set_loop(loopdev, loopfile)) {
		log_message("set_loop failed on %s (%s)", loopdev, strerror(errno));
		return 1;
	}
  
	if (my_mount(loopdev, where, compressed ? "squashfs" : "iso9660", 0)) {
		del_loop(loopdev);
		return 1;
	}

	log_message("lomount succeeded for %s on %s", loopfile, where);
	return 0;
}