/*
 *   Copyright (c) 1996-2002 David Parsons. All rights reserved.
 *   
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions
 *   are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *  3. All advertising materials mentioning features or use of this
 *     software must display the following acknowledgement:
 *     
 *   This product includes software developed by David Parsons
 *   (orc@pell.chi.il.us)
 *
 *  4. My name may not be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *     
 *  THIS SOFTWARE IS PROVIDED BY DAVID PARSONS ``AS IS'' AND ANY
 *  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 *  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DAVID
 *  PARSONS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 *  TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 *  IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 *  THE POSSIBILITY OF SUCH DAMAGE.
 */

static const char rcsid[] = "$Id: s.media.c 1.15 02/06/29 01:08:40-07:00 root@egypt.pell.portland.or.us $";

/*
 * media: media description table.
 */

#include <stdio.h>
#include <stdarg.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <linux/cdrom.h>
#include <linux/fs.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <basis/rpopen.h>

#include "util.h"
#define MEDIA_C 1
#include "media.h"
#include "packages.h"
#include "pathnames.h"

struct media {
    enum mediatypes m_type;	/* what this media is */
    char *m_name;		/* what this media is called by humans */
    char *m_dev;		/* what device it's attached to */
    int (*open)(struct media*);
    int (*extract)(struct media*, char *);
    int (*close)(struct media*);
    int (*eject)(struct media*);
    int (*load)(struct media*);			/* reload the media */
    void *m_public;
    void *m_data;
};

#define DISK(x)	((struct diskmedia*)(x->m_public))
#define ETH(x)	((struct ethmedia*)(x->m_public))

#define M_RO	(MS_RDONLY|MS_MGC_VAL)


/*
 * mOpen() attempts to mount the desired medium
 */
int
mOpen(struct media *m)
{
    if (m && m->open)
	return (*m->open)(m);
    Error("You're trying to install from something unknown");
    errno = EFAULT;
    return errno = 1;
} /* mOpen */


/*
 * mClose() detaches from the selected medium
 */
int
mClose(struct media *m)
{
    if (m && m->close)
	return (*m->close)(m);
    return 0;
} /* mClose */


/*
 * mEject() spits out the selected medium, if possible
 */
int
mEject(struct media *m)
{
    if (m && m->eject)
	(*m->eject)(m);
    return 0;
} /* mEject */


/*
 * mLoad() snarfs in the selected medium, if possible
 */
int
mLoad(struct media *m)
{
    if (m && m->load)
	(*m->load)(m);
    return 0;
} /* mLoad */


/*
 * mName() is the name of the medium
 */
char *
mName(struct media *m)
{
    char *p;

    if (!m) return 0;
    switch (m->m_type) {
    case M_FILE:
		if ( (p = DISK(m)->path) )
		    return p;
		break;
    case M_NET:
		if ( (p = ETH(m)->site) )
		    return p;
		break;
    }
    return m->m_name;
} /* mName */


/*
 * nDev() is the device associated with the medium
 */
char *
mDev(struct media *m)
{
    return m ? m->m_dev : 0;
} /* mDev */


/*
 * mData() returns the user data pointer for the medium
 */
void *
mData(struct media *m)
{
    return m ? m->m_data : 0;
} /* mData */


/*
 * setmData() sets the user data pointer for the medium
 */
void
setmData(struct media *m, void *p)
{
    if (m) m->m_data = p;
} /* setmData */


/*
 * mPublic() returns a pointer to the subclass data for a specific media
 */
void*
mPublic(struct media *m)
{
    return m ? m->m_public : 0;
} /* mPublic */


/*
 * mInstall() installs a software package
 */
int
mInstall(struct media *m, char *pkg)
{
    if (m && m->extract)
	return (*m->extract)(m,pkg);
    return 0;
} /* mInstall */


/*
 * mType() tells the world what media type we are
 */
enum mediatypes
mType(struct media *m)
{
    return m ? m->m_type : M_VOID;
} /* mType */


/* *********************************************************************** *
 * ****************** P R I V A T E  F U N C T I O N S ******************* *
 * *********************************************************************** */


/*
 * filepath() builds a pathname for a file medium
 */
static int
filepath(char *buf, int size, struct media *this, char *filename)
{
    struct diskmedia * dm = DISK(this); 

    if ( (dm->mountpoint ? strlen(dm->mountpoint) : 0)
       + (dm->path ? strlen(dm->path) : 0) + 2 + strlen(filename) >= size)
	return E2BIG;

    buf[0] = 0;
    if (dm->mountpoint)
	sprintf(buf+strlen(buf), "%s/", dm->mountpoint);
    if (dm->path)
	sprintf(buf+strlen(buf), "%s/", dm->path);
    strcat(buf, filename);
    return 0;
} /* filepath */


/*
 * filegetfile() reads a fully qualified pathname and feeds it to some
 * function for processing.
 * If everything is happy, it returns 0, otherwise it returns some errno.
 */
static int
filegetfile(struct media *this, char *file, int (*processor)(void*,int))
{
    int rc;
    int f;
    char package[200];

    if ( (rc = filepath(package, sizeof package, this, file)) != 0)
	return rc;
    
    if ( (f = open(package, O_RDONLY)) >= 0 ) {
	fprintf(stderr, "Reading %s from %s (%s)\n",
		file, mName(this), mDev(this));
	rc = (*processor)(this,f);
	close(f);
	return rc;
    }
    return errno ? errno : EPERM;
} /* filegetfile */


/*
 *  filereadtoc() reads a TOC from a file.
 */
static int
filereadtoc(struct media *this)
{
    return filegetfile(this, PACKAGES, readpackagelist);
} /* filereadtoc */


/*
 * ethgetfile() fetches a file from ethernet and feeds it to some function
 * for processing.
 * If everything is happy, it returns 0, otherwise it returns some errno.
 */
static int
ethgetfile(struct media *this, char *file, int (*processor)(void*,int))
{
    char url[400];
    int rc;
    pid_t pid;
    int pi[2];
    int status;
    char *path, *site;

    if (this == 0 || this->m_public == 0
		  || (site=ETH(this)->site) == 0)
	return EINVAL;

    path = ETH(this)->path;

    /* try to build the complete URL and die if it doesn't work
     */
    if ( (strlen(site)+(path ? strlen(path) : 0)+strlen(file)+3) > sizeof url)
	return E2BIG;

    sprintf(url, "%s/", site);
    if (path) sprintf(url+strlen(url), "%s/", path);
    strcat(url, file);

    if (pipe(pi) != -1) {
	fflush(stderr);
	if ( (pid = fork()) == -1) {
	    close(pi[0]);
	    close(pi[1]);
	}
	else if (pid == 0) {
	    close(pi[0]);
	    dup2(pi[1], 1);
	    execl("/usr/bin/wget", "wget", "-O", "-", url, 0);
	    exit(255);
	}
	else {
	    close(pi[1]);
	    rc = (*processor)(this,pi[0]);
	    close(pi[0]);
	    waitpid(pid, &status, 0);
	    return rc;
	}
    }
    return errno;
} /* ethgetfile */


/*
 * fileisthere() checks to see if a file exists
 */
static int
fileisthere(char *path)
{
    return access(path, X_OK) == 0;
} /* fileisthere */


/*
 * copy_a_file() copies a file to a local path
 */
static int
copy_a_file(void *this, int fd)
{
    char bfr[5120];
    int sz, res;

    if ( (res = creat(mData(this), 0755)) == -1)
	return errno;

    while ( (sz=read(fd, bfr, sizeof bfr)) > 0)
	write(res, bfr, sz);
    close(res);
    return 0;
} /* copy_a_file */


static int
ethopen(struct media *this)
{
    int rc = ethgetfile(this, PACKAGES, readpackagelist);

    if (rc == 0 && !fileisthere(PATH_MKFS)) {
	setmData(this, PATH_MKFS);
	rc = ethgetfile(this, ROOT_MKFS, copy_a_file);
    }
    if (rc == 0 && !fileisthere(PATH_CFDISK)) {
	setmData(this, PATH_CFDISK);
	rc = ethgetfile(this, ROOT_CFDISK, copy_a_file);
    }
    return rc;
} 


static int
ethinstall(struct media *this, char *package)
{
    return ethgetfile(this, package, install_a_package);
} /* install */


/*
 * cde() ejects a CD.
 */
static int
cde(struct media *m)
{
    if (m == 0 || m->m_public == 0) return 1;
    return ioctl(DISK(m)->fd, CDROMEJECT);
} /* cde */


/*
 * cdl() closes the CD door.
 */
static int
cdl(struct media *m)
{
    if (m == 0 || m->m_public == 0) return 1;
    return ioctl(DISK(m)->fd, CDROMCLOSETRAY);
} /* cdl */


/* 
 * fileopen() reads a toc from an already mounted filesystem
 */
static int
fileopen(struct media *this)
{
    int rc = filereadtoc(this);

    if (rc == 0 && !fileisthere(PATH_MKFS)) {
	setmData(this, PATH_MKFS);
	rc = filegetfile(this, ROOT_MKFS, copy_a_file);
    }
    if (rc == 0 && !fileisthere(PATH_CFDISK)) {
	setmData(this, PATH_CFDISK);
	rc = filegetfile(this, ROOT_CFDISK, copy_a_file);
    }
    return rc;
} /* fileopen */



/*
 * fsopen() opens and verifies that a filesystem contains a mastodon 
 *           distribution
 */
static int
fsopen(struct media *this, char *fstype)
{
    if ( (DISK(this)->fd = open(this->m_dev, O_RDONLY|O_NDELAY)) >= 0) {
	fprintf(stderr, "Got fd %d for %s\n", DISK(this)->fd, this->m_dev);
	mLoad(this);
	if (mount(this->m_dev, DISK(this)->mountpoint, fstype, M_RO, 0) == 0) {
	    fprintf(stderr, "Mounted %s (%s) on %s\n",
			     DISK(this)->mountpoint, fstype, this->m_dev);
	    if (filereadtoc(this) == 0 && fileisthere(PATH_CFDISK)
				       && fileisthere(PATH_MKFS))
		return 0;
	    perror(PACKAGES);
	    umount(DISK(this)->mountpoint);
	}
	else {
	    fprintf(stderr, "mounting %s (%s) on ", this->m_dev, fstype);
	    perror(DISK(this)->mountpoint);
	}
	mEject(this);
	close(DISK(this)->fd);
    }
    perror(this->m_dev);
    return errno;
} /* fsopen */


/*
 * fsclose() is called when we're done with a filesystem
 */
static int
fsclose(struct media *this)
{
    umount(DISK(this)->mountpoint);
    if (this->eject)
	(*this->eject)(this);
    close(DISK(this)->fd);
    return 0;
} /* fsclose */


/*
 * fileinstall() extracts a Mastodon package from a distribution
 */
static int
fileinstall(struct media *this, char *package)
{
    return filegetfile(this, package, install_a_package);
} /* fileinstall */


/*
 * cdopen() opens and verifies that a CD-rom contains a mastodon install image.
 */
static int
cdopen(struct media *this)
{
    return fsopen(this, "iso9660");
}


/*
 * dosopen() mounts a MS-dos filesystem for installation
 */
static int
dosopen(struct media *this)
{
    return fsopen(this, "msdos");
} /* dosopen */


/*
 * describe_media() returns a string describing some media
 */
char*
describe_media(struct idiskinfo *x)
{
    static char bfr[40];
    int is_scsi = x->flags & ID_SCSI;

    if (x->flags & ID_NETWORK)
	strncpy(bfr, "Network", sizeof bfr);
    else if (x->flags & ID_CDROM)
	strncpy(bfr, "SCSI CD-ROM" + (is_scsi ? 0 : 5), sizeof bfr);
    else if (x->flags & ID_REMOVABLE)
	strncpy(bfr, is_scsi ? "Removable SCSI disk"
			     : "Removable disk", sizeof bfr);
    else
	strncpy(bfr, "SCSI disk" + (is_scsi ? 0 : 5), sizeof bfr);

    return bfr;
} /* describe_media */


/*
 * newmDisk() creates a media for some disk
 */
struct media *
newmDisk(char *device, int flags)
{
    struct media *p = malloc(sizeof p[0] + sizeof (struct diskmedia));
    int is_scsi = (flags & ID_SCSI);

    if (p) {
	memset(p, 0, sizeof p[0] + sizeof (struct diskmedia));
	p->m_public = (void*)(p + 1);
	p->m_dev = strdup(device);
	DISK(p)->fd = -1;
	DISK(p)->mountpoint = strdup("/cdrom");

	if (flags & ID_CDROM) {
	    p->m_type = M_CDROM;
	    p->m_name = "SCSI CDROM" + (is_scsi ? 0 : 5);
	    p->open = cdopen;
	    p->extract = fileinstall;
	    p->close = fsclose;
	    p->eject = cde;
	    p->load = cdl;
	}
	else if (flags & ID_REMOVABLE) {
	    p->m_type = M_ZIP;
	    p->m_name = is_scsi ? "Removable SCSI disk"
				: "Removable disk";
	    p->open = dosopen;
	    p->extract = fileinstall;
	    p->close = fsclose;
#if 0
	    p->eject = zipe;
#endif
	}
	else {
	    p->m_type = M_DOS;
	    p->m_name = "DOS filesystem";
	    p->open = dosopen;
	    p->extract = fileinstall;
	    p->close = fsclose;
	}
    }
    return p;
} /* newmDisk */


/*
 * newmFile() creates a media for a file (debugging only)
 */
struct media *
newmFile()
{
    struct media *p = malloc(sizeof p[0] + sizeof (struct diskmedia));

    if (p) {
	memset(p, 0, sizeof p[0] + sizeof(struct diskmedia));
	p->m_type = M_FILE;
	p->m_public = (void*)(p+1);
	p->m_dev = "mounted filesystem";
	p->m_name = "File";
	p->open = fileopen;
	p->extract = fileinstall;
    }
    return p;
}


/*
 * newmNetwork() creates a media for a network device
 */
struct media *
newmNetwork(char *device)
{
    struct media *p = malloc(sizeof p[0] + sizeof (struct ethmedia));

    if (p) {
	memset(p, 0, sizeof p[0] + sizeof(struct ethmedia));

	p->m_type = M_NET;
	p->m_public = (void*)(p+1);
	ETH(p)->configured = 0;
	ETH(p)->dhcp = 1;
	p->m_dev = strdup(device);
	p->m_name = "Network";
	p->open = ethopen;
	p->extract = ethinstall;
    }

    return p;
} /* newmDisk */


#ifdef TEST

#include <stdarg.h>

void
Error(char *fmt, ...)
{
    va_list ptr;

    fprintf(stderr, "ERROR: ");
    va_start(ptr, fmt);
    vfprintf(stderr, fmt, ptr);
    va_end(ptr);
    fputc('\n', stderr);
}

main()
{
}
#endif
