view installer/edf.c @ 480:aba2ded209f4

installer: fix EDF allocmap bug We were checking the bits of each byte one way, but returning them the other. Signed-off-by: Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
author Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
date Fri, 15 Apr 2011 21:07:04 -0400
parents 6c67834cf2b7
children 9fac53826047
line wrap: on
line source

/*
 * Copyright (c) 2011 Josef 'Jeff' Sipek
 */
#include "loader.h"
#include <string.h>
#include <ebcdic.h>
#include <list.h>

union adt_u {
	struct ADT adt;
	char buf[4096];
};

struct block_map {
	struct list_head list;

	/* key */
	u8 fn[8]; /* EBCDIC */
	u8 ft[8]; /* EBCDIC */
	u8 level; /* 0 = data */
	u32 blk_no;

	/* value */
	u32 lba;
	void *buf;

	/* dirty list */
	int dirty;
};

static union adt_u *adt;
static struct FST *directory;
static struct FST *allocmap;

static LIST_HEAD(block_map);

/* borrowed from Linux */
#define container_of(ptr, type, member) ({                      \
         const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
         (type *)( (char *)__mptr - offsetof(type,member) );})

/* borrowed from Linux */
#define offsetof(type, member) __builtin_offsetof(type,member)

#define DIRECTOR_FN ((u8*) "\x00\x00\x00\x01\x00\x00\x00\x00")
#define DIRECTOR_FT ((u8*) "\xc4\xc9\xd9\xc5\xc3\xe3\xd6\xd9")
#define ALLOCMAP_FN ((u8*) "\x00\x00\x00\x02\x00\x00\x00\x00")
#define ALLOCMAP_FT ((u8*) "\xc1\xd3\xd3\xd6\xc3\xd4\xc1\xd7")

static struct block_map *block_map_find(u8 *fn, u8 *ft, u8 level, u32 blk_no)
{
	struct block_map *cur;

	list_for_each_entry(cur, &block_map, list) {
		if (!memcmp(fn, cur->fn, 8) &&
		    !memcmp(ft, cur->ft, 8) &&
		    (level == cur->level) &&
		    (blk_no == cur->blk_no))
			return cur;
	}

	return NULL;
}

static void block_map_add(u8 *fn, u8 *ft, u8 level, u32 blk_no, u32 lba)
{
	struct block_map *map;

	map = block_map_find(fn, ft, level, blk_no);
	if (map) {
		if (map->lba != lba)
			die();

		return;
	}

	map = malloc(sizeof(struct block_map));
	if (!map)
		die();

	memcpy(map->fn, fn, 8);
	memcpy(map->ft, ft, 8);
	map->level = level;
	map->blk_no = blk_no;

	map->lba = lba;
	map->buf = NULL;
	map->dirty = 0;

	list_add(&map->list, &block_map);
}

static void *read_file_blk(u8 *fn, u8 *ft, u8 level, u32 blk)
{
	struct block_map *map;

	map = block_map_find(fn, ft, level, blk);
	if (!map)
		die();

	if (map->buf)
		return map->buf;

	map->buf = malloc(adt->adt.DBSIZ);
	if (!map->buf)
		die();

	read_blk(map->buf, map->lba);

	return map->buf;
}

static void blk_set_dirty(u8 *fn, u8 *ft, u8 level, u32 blk)
{
	struct block_map *map;

	map = block_map_find(fn, ft, level, blk);
	if (!map || !map->buf)
		die();

	map->dirty = 1;
}

void writeback_buffers()
{
	struct block_map *cur;
	char buf[100];

	list_for_each_entry(cur, &block_map, list) {
		if (!cur->dirty)
			continue;

		if (!cur->buf)
			die();

		snprintf(buf, 100, "wb: %10d %p\n", cur->lba, cur->buf);
		wto(buf);
		write_blk(cur->buf, cur->lba);
	}
}

/*
 * fills in *fst with existing file info and returns 0, or if file doesn't
 * exist, returns -1
 */
int find_file(char *fn, char *ft, struct FST *fst)
{
	struct FST *FST;
	u8 FN[8];
	u8 FT[8];
	int rec;
	int blk;

	memcpy(FN, fn, 8);
	memcpy(FT, ft, 8);
	ascii2ebcdic(FN, 8);
	ascii2ebcdic(FT, 8);

	for(blk=0; blk<directory->ADBC; blk++) {
		FST = read_file_blk(DIRECTOR_FN, DIRECTOR_FT, 0, blk);

		for(rec=0; rec<adt->adt.NFST; rec++) {
			if ((!memcmp(FST[rec].FNAME, FN, 8)) &&
			    (!memcmp(FST[rec].FTYPE, FT, 8))) {
				memcpy(fst, &FST[rec], sizeof(struct FST));
				return 0;
			}
		}
	}

	return -1;
}

static void update_directory(struct FST *fst)
{
	struct FST *FST;
	int rec;
	int blk;

	for(blk=0; blk<directory->ADBC; blk++) {
		FST = read_file_blk(DIRECTOR_FN, DIRECTOR_FT, 0, blk);

		for(rec=0; rec<adt->adt.NFST; rec++) {
			if ((!memcmp(FST[rec].FNAME, fst->FNAME, 8)) &&
			    (!memcmp(FST[rec].FTYPE, fst->FTYPE, 8))) {
				memcpy(&FST[rec], fst, sizeof(struct FST));
				blk_set_dirty(DIRECTOR_FN, DIRECTOR_FT, 0, blk);
				return;
			}
		}
	}

	die();
}

static int file_blocks_at_level(u32 ADBC, int level)
{
	const u32 ptrs_per_block = adt->adt.DBSIZ / 4;
	int blks;
	int i;

	blks = 1;
	for(i=0; i<level; i++)
		blks *= ptrs_per_block;
	blks = (ADBC + blks - 1) / blks;

	return blks;
}

static void __read_file(struct FST *fst)
{
	const u32 ptrs_per_block = adt->adt.DBSIZ / fst->PTRSZ;
	u32 *blk_ptrs;
	int level;

	int blocks;
	int i,j;

	if (!fst->NLVL) {
		block_map_add(fst->FNAME, fst->FTYPE, 0, 0, fst->FOP);
		return;
	}

	/* there are pointer blocks, let's read them in and then
	 * follow each pointer
	 */

	block_map_add(fst->FNAME, fst->FTYPE, fst->NLVL, 0, fst->FOP);

	/* for each level of pointers... */
	for(level=fst->NLVL; level>0; level--) {
		blocks = file_blocks_at_level(fst->ADBC, level);

		if (level == fst->NLVL && blocks != 1)
			die();

		/* read in each block */
		for(i=0; i<blocks; i++) {
			blk_ptrs = read_file_blk(fst->FNAME,
						 fst->FTYPE,
						 level,
						 i);

			/* and add each pointer to the next level */
			for(j=0; j<ptrs_per_block; j++) {
				block_map_add(fst->FNAME,
					      fst->FTYPE,
					      level-1,
					      (i*ptrs_per_block) + j,
					      blk_ptrs[j]);
			}
		}
	}
}

int create_file(char *fn, char *ft, int lrecl, struct FST *fst)
{
	/* first, fill in the FST */
	memset(fst, 0, sizeof(struct FST));

	memcpy(fst->FNAME, fn, 8);
	memcpy(fst->FTYPE, ft, 8);

	ascii2ebcdic(fst->FNAME, 8);
	ascii2ebcdic(fst->FTYPE, 8);

	fst->FMODE[0] = '\xc1'; // EBCDIC 'A'
	fst->FMODE[1] = '\xf1'; // EBCDIC '1'
	fst->RECFM    = FSTDFIX; // fixed record size
	fst->LRECL    = lrecl;
	fst->PTRSZ    = 4;

	append_record(directory, (u8*) fst);

	return 0;
}

static u32 __get_free_block()
{
	u32 blk;
	u8 *buf;
	u32 i;
	u32 bit;

	for(blk=0; blk<allocmap->ADBC; blk++) {
		buf = read_file_blk(allocmap->FNAME, allocmap->FTYPE, 0, blk);

		for(i=0; i<adt->adt.DBSIZ; i++) {
			if (buf[i] == 0xff)
				continue;

			if ((buf[i] & 0x80) == 0) { bit = 0; goto found; }
			if ((buf[i] & 0x40) == 0) { bit = 1; goto found; }
			if ((buf[i] & 0x20) == 0) { bit = 2; goto found; }
			if ((buf[i] & 0x10) == 0) { bit = 3; goto found; }
			if ((buf[i] & 0x08) == 0) { bit = 4; goto found; }
			if ((buf[i] & 0x04) == 0) { bit = 5; goto found; }
			if ((buf[i] & 0x02) == 0) { bit = 6; goto found; }
			if ((buf[i] & 0x01) == 0) { bit = 7; goto found; }

			continue; /* this is not necessary since we check
				     for 0xff right away, but GCC really
				     likes to complain about possibly
				     uninitialized use of 'bit' */

found:
			buf[i] |= (0x80 >> bit);

			blk_set_dirty(allocmap->FNAME, allocmap->FTYPE, 0, blk);

			return ((blk * adt->adt.DBSIZ * 8) + (i * 8) + bit) + 1;
		}
	}

	die();
	return 0;
}

static void __append_block(struct FST *fst)
{
	u32 *buf;
	u32 lba, prevlba;
	u32 blk;
	u8 lvl;

	/* no data blocks yet */
	if (!fst->ADBC) {
		fst->ADBC = 1;
		fst->NLVL = 0;
		fst->FOP  = __get_free_block();

		block_map_add(fst->FNAME, fst->FTYPE, 0, 0, fst->FOP);
		return;
	}

	/* need to add another level */
	if (fst->ADBC == (file_blocks_at_level(fst->ADBC, fst->NLVL) *
			  (adt->adt.DBSIZ / 4))) {
		for(lvl=0; lvl<=fst->NLVL; lvl++, prevlba=lba) {
			lba = __get_free_block();
			blk = file_blocks_at_level(fst->ADBC+1, lvl);

			block_map_add(fst->FNAME, fst->FTYPE, lvl, blk, lba);

			if (!lvl)
				continue;

			buf = read_file_blk(fst->FNAME, fst->FTYPE, lvl, blk);
			blk_set_dirty(fst->FNAME, fst->FTYPE, lvl, blk);

			*buf = prevlba;

		}

		lba = __get_free_block();
		block_map_add(fst->FNAME, fst->FTYPE, fst->NLVL+1, 0, lba);

		buf = read_file_blk(fst->FNAME, fst->FTYPE, fst->NLVL+1, 0);
		blk_set_dirty(fst->FNAME, fst->FTYPE, fst->NLVL+1, 0);

		buf[0] = fst->FOP;
		buf[1] = prevlba;

		fst->FOP = lba;

		fst->NLVL++;
		fst->ADBC++;
		return;
	}

	// FIXME
	die();
}

void append_record(struct FST *fst, u8 *buf)
{
	u32 foff;
	u32 blk;
	u32 off;
	u32 rem;

	u8 *dbuf;

	if (fst->RECFM != FSTDFIX)
		die();

	foff = fst->AIC * fst->LRECL;

	blk = foff / adt->adt.DBSIZ;
	off = foff % adt->adt.DBSIZ;
	rem = adt->adt.DBSIZ - off;

	/* need to add another block */
	if ((blk == fst->ADBC) || (rem < fst->LRECL))
		__append_block(fst);

	dbuf = read_file_blk(fst->FNAME, fst->FTYPE, 0, blk);

	if (rem >= fst->LRECL) {
		memcpy(dbuf + off, buf, fst->LRECL);
		blk_set_dirty(fst->FNAME, fst->FTYPE, 0, blk);
	} else {
		memcpy(dbuf + off, buf, rem);
		blk_set_dirty(fst->FNAME, fst->FTYPE, 0, blk);

		dbuf = read_file_blk(fst->FNAME, fst->FTYPE, 0, blk+1);
		memcpy(dbuf, buf + rem, fst->LRECL - rem);
		blk_set_dirty(fst->FNAME, fst->FTYPE, 0, blk+1);
	}

	fst->AIC++;

	update_directory(fst);
}

void mount_fs()
{
	struct FST *fst;

	adt = malloc(sizeof(union adt_u));

	read_blk(adt, EDF_LABEL_BLOCK_NO);

	if ((adt->adt.IDENT != __ADTIDENT) ||
	    (adt->adt.DBSIZ != EDF_SUPPORTED_BLOCK_SIZE) ||
	    (adt->adt.OFFST != 0) ||
	    (adt->adt.FSTSZ != sizeof(struct FST)))
		die();

	block_map_add(DIRECTOR_FN, DIRECTOR_FT, 0, 0, adt->adt.DOP);

	fst = read_file_blk(DIRECTOR_FN, DIRECTOR_FT, 0, 0);

	if (memcmp(fst[0].FNAME, DIRECTOR_FN, 8) ||
	    memcmp(fst[0].FTYPE, DIRECTOR_FT, 8) ||
	    (fst[0].RECFM != FSTDFIX) ||
	    memcmp(fst[1].FNAME, ALLOCMAP_FN, 8) ||
	    memcmp(fst[1].FTYPE, ALLOCMAP_FT, 8) ||
	    (fst[1].RECFM != FSTDFIX))
		die();

	directory = fst;
	allocmap  = fst+1;

	__read_file(&fst[0]); /* the directory */
	__read_file(&fst[1]); /* the alloc map */
}