/*
 * fs2dt: creates a flattened device-tree
 *
 * Copyright (C) 2004,2005  Milton D Miller II, IBM Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation (version 2 of the License).
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * 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.
 */

#include <sys/types.h>
#include <sys/stat.h>

#include <fcntl.h>
#include <dirent.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>

#define MAXPATH 1024		/* max path name length */
#define NAMESPACE 16384		/* max bytes for property names */
#define TREEWORDS 65536		/* max 32 bit words for property values */
#define MEMRESERVE 256		/* max number of reserved memory blocks */

enum {
	ERR_NONE,
	ERR_USAGE,
	ERR_OPENDIR,
	ERR_READDIR,
	ERR_STAT,
	ERR_OPEN,
	ERR_READ,
	ERR_RESERVE,
};

void err(const char *str, int rc)
{
	if (errno)
		perror(str);
	else
		fprintf(stderr, "%s: unrecoverable error\n", str);
	exit(rc);
}

typedef unsigned dvt;
struct stat statbuf[1];
char pathname[MAXPATH], *pathstart;
char propnames[NAMESPACE];
dvt dtstruct[TREEWORDS], *dt;
unsigned long long mem_rsrv[2*MEMRESERVE];

void reserve(unsigned long long where, unsigned long long length)
{
	unsigned long long *mr;

	mr = mem_rsrv;

	while(mr[1])
		mr += 2;

	mr[0] = where;
	mr[1] = length;
}

/* look for properties we need to reserve memory space for */
void checkprop(char *name, dvt *data)
{
	static unsigned long long base, size, end;

	if ((data == NULL) && (base || size || end))
			err((void *)data, ERR_RESERVE);
	else if (!strcmp(name, "linux,rtas-base"))
		base = *data;
	else if (!strcmp(name, "linux,initrd-start") ||
			!strcmp(name, "linux,tce-base"))
		base = *(unsigned long long *) data;
	else if (!strcmp(name, "rtas-size") ||
			!strcmp(name, "linux,tce-size"))
		size = *data;
	else if (!strcmp(name, "linux,initrd-end"))
		end = *(unsigned long long *) data;

	if (size && end) 
		err(name, ERR_RESERVE);
	if (base && size) {
		reserve(base, size);
		base = size = 0;
	}
	if (base && end) {
		reserve(base, end-base);
		base = end = 0;
	}
}

/*
 * return the property index for a property name, creating a new one
 * if needed.
 */
dvt propnum(const char *name)
{
	dvt offset = 0;

	while(propnames[offset])
		if (strcmp(name, propnames+offset))
			offset += strlen(propnames+offset)+1;
		else
			return offset;

	strcpy(propnames+offset, name);

	return offset;
}

/* put all properties (files) in the property structure */
void putprops(char *fn, DIR *dir)
{
	struct dirent *dp;

	while ((dp = readdir(dir)) != NULL) {
		strcpy(fn, dp->d_name);

		if (lstat(pathname, statbuf))
			err(pathname, ERR_STAT);

		if (S_ISREG(statbuf[0].st_mode)) {
			int fd, len = statbuf[0].st_size;

			*dt++ = 3;
			*dt++ = len;
			*dt++ = propnum(fn);

			if ((len >= 8) && ((unsigned long)dt & 0x4))
				dt++;

			fd = open(pathname, O_RDONLY);
			if (fd == -1)
				err(pathname, ERR_OPEN);
			if (read(fd, dt, len) != len)
				err(pathname, ERR_READ);
			close(fd);

			checkprop(fn, dt);

			dt += (len + 3)/4;
		}
	}
	fn[0] = '\0';
	if (errno)
		err(pathname, ERR_READDIR);
	checkprop(pathname, NULL);
}

/*
 * put a node (directory) in the property structure.  first properties
 * then children.
 */
void putnode(void)
{
	DIR *dir;
	char *dn;
	struct dirent *dp;

	*dt++ = 1;
	strcpy((void *)dt, *pathstart ? pathstart : "/");
	while(*dt)
		dt++;
	if (dt[-1] & 0xff)
		dt++;
	
	dir = opendir(pathname);

	if (!dir)
		err(pathname, ERR_OPENDIR);

	strcat(pathname, "/");
	dn = pathname + strlen(pathname);

	putprops(dn, dir);

	rewinddir(dir);

	while ((dp = readdir(dir)) != NULL) {
		strcpy(dn, dp->d_name);

		if (!strcmp(dn, ".") || !strcmp(dn, ".."))
			continue;

		if (lstat(pathname, statbuf))
			err(pathname, ERR_STAT);

		if (S_ISDIR(statbuf[0].st_mode))
			putnode();
	}
	if (errno)
		err(pathname, ERR_READDIR);

	*dt++ = 2;
	closedir(dir);
	dn[-1] = '\0';
}

/* boot block version 2 as defined by the linux kernel */
struct bootblock {
	unsigned magic,
		totalsize,
		off_dt_struct,
		off_dt_strings,
		off_mem_rsvmap,
		version,
		last_comp_version,
		boot_physid;
} bb[1];

main(int argc, char *argv[], char *envp[])
{
	unsigned len;
	unsigned long long me;

	me = 0x01ff8000;


	while (1)
		switch(getopt(argc, argv, "c:b:")) {
			case -1:
				goto opt;
			case 'c':
				bb->boot_physid = strtoul(optarg, NULL, 0);
				break;
			case 'b':
				me = strtoull(optarg, NULL, 0);
				break;
			default:
				fprintf(stderr, 
				"usage: fs2dt [-c cpu ] [-b base] dir\n"); 
				err(argv[0], ERR_USAGE);
		}

opt:
	if (optind < argc)
		strcpy(pathname, argv[optind]);
	else
		strcpy(pathname, ".");

	pathstart = pathname + strlen(pathname);
	dt = dtstruct;

	putnode();
	*dt++ = 9;

	len = sizeof(bb[0]);
	len += 7; len &= ~7;

	bb->off_mem_rsvmap = len;

	for (len = 1; mem_rsrv[len]; len += 2)
		;
	len+= 3;
	len *= sizeof(mem_rsrv[0]);

	bb->off_dt_struct = bb->off_mem_rsvmap + len;

	len = dt - dtstruct;
	len *= sizeof(dvt);
	bb->off_dt_strings = bb->off_dt_struct + len;

	len = propnum("");
	len +=  3; len &= ~3;
	bb->totalsize = bb->off_dt_strings + len;

	bb->magic = 0xd00dfeed;
	bb->version = 2;
	bb->last_comp_version = 2;

	reserve(me, bb->totalsize);

	write(1, bb, bb->off_mem_rsvmap);
	write(1, mem_rsrv, bb->off_dt_struct - bb->off_mem_rsvmap);
	write(1, dtstruct,  bb->off_dt_strings - bb->off_dt_struct);
	write(1, propnames,  bb->totalsize - bb->off_dt_strings);

	exit(ERR_NONE);
}
