/* device.c  -  Device access */
/*
Copyright 1992-1997 Werner Almesberger.
Copyright 1999-2003 John Coffman.
All rights reserved.

Licensed under the terms contained in the file 'COPYING' in the 
source directory.

*/


#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <dirent.h>
#include <limits.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include "config.h"
#include "lilo.h"
#include "common.h"
#include "temp.h"
#include "device.h"
#include "geometry.h"


typedef struct _cache_entry {
    const char *name;
    int number;
    struct _cache_entry *next;
} CACHE_ENTRY;

typedef struct _st_buf {
    struct _st_buf *next;
    struct stat st;
} ST_BUF;


static CACHE_ENTRY *cache = NULL;


static int scan_dir(ST_BUF *next,DEVICE *dev,char *parent,int number)
{
    DIR *dp;
    struct dirent *dir;
    ST_BUF st,*walk;
    char *start;

    st.next = next;
    if ((dp = opendir(parent)) == NULL)
	die("opendir %s: %s",parent,strerror(errno));
    *(start = strchr(parent,0)) = '/';
    while ((dir = readdir(dp))) {
	strcpy(start+1,dir->d_name);
	if (stat(parent,&st.st) >= 0) {
	    dev->st = st.st;
	    if (S_ISBLK(dev->st.st_mode) && dev->st.st_rdev == number) {
		(void) closedir(dp);
		return 1;
	    }
	    if (S_ISDIR(dev->st.st_mode) && strcmp(dir->d_name,".") &&
	      strcmp(dir->d_name,"..")) {
		for (walk = next; walk; walk = walk->next)
		    if (stat_equal(&walk->st,&st.st)) break;
		if (!walk && scan_dir(&st,dev,parent,number)) {
		    (void) closedir(dp);
		    return 1;
		}
	    }
	}
    }
    (void) closedir(dp);
    *start = 0;
    return 0;
}


static int lookup_dev(char *name,DEVICE *dev,int number)
{
    CACHE_ENTRY **walk,*here;

    for (walk = &cache; *walk; walk = &(*walk)->next)
	if ((*walk)->number == number) {
	    if (stat((*walk)->name,&dev->st) >= 0)
		if (S_ISBLK(dev->st.st_mode) && dev->st.st_rdev == number) {
		    strcpy(name,(*walk)->name);
		    return 1;
		}
	    here = *walk; /* remove entry from cache */
	    if (verbose >= 2)
		printf("Invalidating cache entry for %s (0x%04X)\n",here->name,
		  here->number);
	    *walk = here->next;
	    free((char *) here->name);
	    free(here);
	    return 0;
	}
    return 0;
}


static void cache_add(const char *name,int number)
{
    CACHE_ENTRY *entry;

    if (verbose >= 4) printf("Caching device %s (0x%04X)\n",name,number);
    entry = alloc_t(CACHE_ENTRY);
    entry->name = stralloc(name);
    entry->number = number;
    entry->next = cache;
    cache = entry;
}


int dev_open(DEVICE *dev,int number,int flags)
{
    char name[PATH_MAX+1];
    ST_BUF st;
    int count;

    if (lookup_dev(name,dev,number)) dev->delete = 0;
    else {
	if (stat(DEV_DIR,&st.st) < 0)
	    die("stat " DEV_DIR ": %s",strerror(errno));
	st.next = NULL;
	dev->delete = !scan_dir(&st,dev,strcpy(name,DEV_DIR),number);
	if (dev->delete) {
	    for (count = 0; count <= MAX_TMP_DEV; count++) {
#ifdef LCF_USE_TMPDIR
		if (!strncmp(TMP_DEV,"/tmp/",5) && getenv("TMPDIR")) {
		    strcpy(name,getenv("TMPDIR"));
		    sprintf(name+strlen(name),TMP_DEV+4,count);
		}
		else
#endif
		    sprintf(name,TMP_DEV,count);
		if (stat(name,&dev->st) < 0) break;
	    }
	    if (count > MAX_TMP_DEV)
		die("Failed to create a temporary device");
	    if (mknod(name,0600 | S_IFBLK,number) < 0)
		die("mknod %s: %s",name,strerror(errno));
	    if (stat(name,&dev->st) < 0)
		die("stat %s: %s",name,strerror(errno));
	    if (verbose > 1)
		printf("Created temporary device %s (0x%04X)\n",name,number);
	    temp_register(name);
	}
	else cache_add(name,number);
    }
    if (flags == -1) dev->fd = -1;
    else if ((dev->fd = open(name,flags)) < 0)
	    die("open %s: %s",name,strerror(errno));
    dev->name = stralloc(name);
    return dev->fd;
}


void dev_close(DEVICE *dev)
{
    if (dev->fd != -1)
	if (close(dev->fd) < 0) die("close %s: %s",dev->name,strerror(errno));
    if (dev->delete) {
	if (verbose > 1)
	    printf("Removed temporary device %s (0x%04X)\n",dev->name,
	      (unsigned int) dev->st.st_rdev);
	(void) remove(dev->name);
	temp_unregister(dev->name);
    }
    free(dev->name);
}

#define MAX 15

void cache_scsi (char *name, int major)
{
    char tem[12], format[12];
    int i, j, dev;
    
    for (i=15; i>=0; i--) {
	dev = (major<<8) | (i<<4);
	strcpy(format, name);
	format[7] += i;
	cache_add(format, dev);
	strcat(format, "%d");
	for (j = MAX; j; j--) {
	    sprintf(tem, format, j);
	    cache_add(tem, dev | j);
	}
    }
}


void cache_ide (char *name, int major)
{
    char tem[12];
    char format[12];
    int i, dev, minor;
    
    minor = name[7]&1 ? 0 : 64;
    strcat(strcpy(format, name), "%d");
    dev = (major<<8) | minor;
    for (i = MAX; i; i--) {
	sprintf(tem, format, i);
	cache_add(tem, dev | i);
    }
    cache_add(name, dev);
}


void preload_dev_cache(void)
{
    char tmp[12];
    int i, vsave;

#define QUIET (1+(VERSION_MINOR>=50))

#if QUIET>0
    vsave = verbose;
    verbose -= QUIET;	/* quiet useless output */
#endif

    cache_add("/dev/fd0", 0x0200);
    cache_add("/dev/fd1", 0x0201);
    
#if 0
    cache_ide("/dev/hdt", MAJOR_IDE10);
    cache_ide("/dev/hds", MAJOR_IDE10);
    cache_ide("/dev/hdr", MAJOR_IDE9);
    cache_ide("/dev/hdq", MAJOR_IDE9);

    cache_ide("/dev/hdp", MAJOR_IDE8);
    cache_ide("/dev/hdo", MAJOR_IDE8);
    cache_ide("/dev/hdn", MAJOR_IDE7);
    cache_ide("/dev/hdm", MAJOR_IDE7);
    
    cache_ide("/dev/hdl", MAJOR_IDE6);
    cache_ide("/dev/hdk", MAJOR_IDE6);
    cache_ide("/dev/hdj", MAJOR_IDE5);
    cache_ide("/dev/hdi", MAJOR_IDE5);
#endif
    
    cache_scsi("/dev/sda", MAJOR_SD);
    
    cache_ide("/dev/hdh", MAJOR_IDE4);
    cache_ide("/dev/hdg", MAJOR_IDE4);
    cache_ide("/dev/hdf", MAJOR_IDE3);
    cache_ide("/dev/hde", MAJOR_IDE3);

    for (i = 0; i <= 7; i++) {
	sprintf(tmp,"/dev/loop%d",i);
	cache_add(tmp,0x700+i);
    }

    cache_ide("/dev/hdd", MAJOR_IDE2);
    cache_ide("/dev/hdc", MAJOR_IDE2);
    cache_ide("/dev/hdb", MAJOR_HD);
    cache_ide("/dev/hda", MAJOR_HD);
#if QUIET>0
    verbose = vsave;
#endif
}

#if 1
/* make a backup, returning the timestamp of the backup file */
/* 0 if no timestamp returned, and no backup file created */

long make_backup(char *backup_file, int force_backup, BOOT_SECTOR *bsect,
	int device, char *id)
{
    struct stat st;
    char temp_name[PATH_MAX+1];
    int bck_file;
    long timestamp=0;
    char *filename = "boot";

    if (backup_file && stat(backup_file, &st) >= 0) {
/* file or directory exists */
    	if (S_ISDIR(st.st_mode)) {
	    sprintf(temp_name, "%s/%s.%04X", backup_file, filename, device);
	    backup_file = temp_name;
	}
    /* test for /dev/null */
	else if (S_ISCHR(st.st_mode) && st.st_rdev==0x0103) return 0;
	else if (!S_ISREG(st.st_mode))
	    die("make_backup: %s not a directory or regular file", backup_file);
    }

/* could not stat it, or it was a directory, or it was a regular file */

    if (backup_file) {
	char *name, *dir, suffix[16];
	
	backup_file = strcpy(temp_name, backup_file);
	sprintf(suffix, "%04X", device);
	dir = strrchr(backup_file, '/');
	if (!dir) dir = backup_file;
	name = strrchr(dir, '.');
	if (name) {  /* there is a '.' in the name */
	    if (strcmp(name+1, suffix)==0) ; /* do nothing */
	    else if (strlen(name+1)==4) {  /* && the suffix doesn't match */
	    	strcpy(name+1,suffix);
	    }
	    else if (name[1]==0) strcat(name,suffix);	/* ended with '.' */
	    else {
		strcat(name+1,".");
		strcat(backup_file,suffix);
	    }
	  /* we now have the filename with the correct suffix */
	}
	else {
    /* no '.' in the name, take it as a template */
	    strcat(backup_file,".");
	    strcat(backup_file,suffix);
	}
    }
    else
  /*  if (!backup_file) */ {
	sprintf(temp_name, BACKUP_DIR "/%s.%04X", filename, device);
	backup_file = temp_name;
    }
    
    bck_file = open(backup_file, O_RDONLY);
    if (bck_file >= 0 && force_backup) {
	(void) close(bck_file);
	bck_file = -1;
    }
    if (bck_file >= 0) {
	if (verbose)
	    printf("%s exists - no %s backup copy made.\n", backup_file, id);
    }
    else {
	if ((bck_file = creat(backup_file, 0644)) < 0)
	    die("creat %s: %s",backup_file, strerror(errno));
	if (write(bck_file, (char *)bsect, SECTOR_SIZE) != SECTOR_SIZE)
	    die("write %s: %s", backup_file, strerror(errno));
	if (verbose)
	    printf("Backup copy of %s in %s\n", id, backup_file);
	if (fstat(bck_file, &st) < 0)
	    die("fstat %s: %s",backup_file,strerror(errno));
	timestamp = st.st_mtime;
    }
    if (close(bck_file) < 0) die("close %s: %s",backup_file,strerror(errno));
    
    return timestamp;
}



static int inited=0;
unsigned long serial_no[MAX_BIOS_DEVICES];
static int device_code[MAX_BIOS_DEVICES];

/*  register references to various bios devices
 *  compiles the list of volume serial numbers
 *    returns volume serial number on success
 *	0 = no volume serial number
 *	-1 = error
 */
unsigned long register_bios(int bios, int device)
{
    int i, fd;
    DEVICE dev;
    BOOT_SECTOR buff;
    unsigned long serial = -1;
    
    if (!inited) {
	for (i=0; i<MAX_BIOS_DEVICES; i++) {
	    device_code[i] = -1;
	    serial_no[i] = -1;
	}
	inited = 1;
	srand(time(NULL));
    }
    
    if (verbose>=4) {
	printf("registering bios=0x%02X  device=0x%04X\n", bios, device);
    }
    
    if (bios>=0x80 && bios<0x80+MAX_BIOS_DEVICES &&
				(i=has_partitions(device))) {
	bios &= 0x7F;	/* mask to index */
	device &= i;	/* mask to master device */
	if (device_code[bios]==-1 && serial_no[bios]==-1) {  /* slot empty */
	    fd = dev_open(&dev, device, O_RDWR);
	    if (lseek(fd, 0L, SEEK_SET) < 0)
		die("master boot record seek %04X: %s", device, strerror(errno));
	    if (read(fd, (char*)&buff, SECTOR_SIZE)!=SECTOR_SIZE)
		die("read master boot record %04X: %s", device, strerror(errno));
	    serial = *(long*)&buff.sector[PART_TABLE_OFFSET-6];
	    if (!serial) {
		if (!test)
		    make_backup(NULL, 0, &buff, device,
					"master disk volume ID record");
		i = device % 271 + 31;
		while(i--) serial = rand();
		if (serial==0 || serial==-1) die("serial number generation error");

		*(long*)&buff.sector[PART_TABLE_OFFSET-6] = serial;
		if (*(short*)&buff.sector[PART_TABLE_OFFSET - 2] == 0)
		    *(short*)&buff.sector[PART_TABLE_OFFSET - 2] = MAGIC_SERIAL;
		if (verbose)
		    printf("Assigning new serial number to (%04X) '%s'  S/N %08X\n",
		    			device, dev.name, (int)serial);
		if (!test) {
		    i = lseek(fd, 0L, SEEK_SET);
		    if (i<0) die("master boot record2 seek %04X: %s", device, strerror(errno));
		    if (write(fd, (char*)&buff, SECTOR_SIZE)!=SECTOR_SIZE)
			die("write master boot record %04X: %s", device, strerror(errno));
		}
	    }
	    dev_close(&dev);
	    for (i=0; i<MAX_BIOS_DEVICES; i++) {
		if (device_code[i]==device)
		    die("register_bios: device code duplicated: %04X", device);
		if (serial_no[i]==serial)
		    die("register_bios: volume ID serial no. duplicated: %08lX", serial);
	    }
	    device_code[bios] = device;
	    serial_no[bios] = serial;
	}
	if (device_code[bios]==device) serial = serial_no[bios];
	else {
	    DEVICE dev, deva;
	    
	    dev_open(&dev, device_code[bios], -1);
	    dev_open(&deva, device, -1);

	    fprintf(errstd,
	    	"\nLILO is unable to correctly determine the device codes assigned to two\n"
	    	"of your disks by the System BIOS.  Consequently, it seems to have guessed\n"
	    	"wrong.  The following conflict needs to be resolved with explicit\n"
		"    \"disk=/dev/...  bios=0x\?\?\" lines in '%s'.\n\n", DFL_CONFIG);	    

	    die("Bios device code 0x%02X is being used by two disks\n\t%s (0x%04X)  and  %s (0x%04X)",
	    	bios|0x80, dev.name, device_code[bios], deva.name, device);
	}
	if (verbose>=3) {
	    printf("Using serial number %08X on bios %02X\n", (int)serial, bios+0x80);
	}
    }
    else if (bios>=0 && bios <=3) serial = 0;
    else serial = -1;
    
    return serial;
}


void dump_serial_nos(void)
{
    int i,j;
    
    printf(" BIOS  Volume S/N  Device\n");
    if (!inited) return;
    for (j=nelem(serial_no); serial_no[--j]==-1; )   ;
    for (i=0; i<=j; i++)
	printf("  %02X    %08X    %04X\n",
		i+0x80,
		(int)serial_no[i],
		(int)device_code[i]
	);
}
#endif


