/*
 * @(#) Config.cpp
 *
 * This file is part of webCDwriter - Network CD Writing.
 *
 * Copyright (C) 1999-2002 Jörg P. M. Haeger
 *
 * webCDwriter is free software. See CDWserver.cpp for details.
 *
 * Jörg Haeger, 25.05.1999
 */

#include <sys/types.h>
#include <grp.h>
#include <pwd.h>
#include "Config.h"
#include "PrintWriter.h"
#include "String.h"
#include "Version.h"

Config::Config() {
	cdrecordInfo = "cdrecord";
	fileName = getenv("CDWserverConfig");
	if (fileName == NULL)
		fileName = strCat(prefix, "/etc/CDWserver/config");
	char *configDir = strDup(fileName);
	int i;
	for (i = strlen(configDir);
		i > 0 && configDir[i - 1] != '/'; i--);
	configDir[i] = 0;
	this->configDir = configDir;

	firstError = NULL;
	mkisofsInfo = "mkisofs";
	numOfReaders = 0;
	numOfWriters = 0;

	// values
	accounts = 1;
	accountsFile = strCat(configDir, "accounts");
	anonymousUse = 1;
	burnProof = 0;
	cdrdao = 0;
	cdrdaoArgs = "--source-driver generic-mmc-raw --driver generic-mmc";
	cdrdaoPath = "cdrdao";
	cdrecordArgs = "";
	cdrecordPath = "cdrecord";
	copySupport = 1;
	debug = "";
	defaultFilesDir = strCat(prefix, "/etc/CDWserver/default_files");
	defaultSpeed = 4;
	ejectOnDone = 1;
	ejectOnReservation = 1;
	eMailOnError = NULL;
	exportArchive = 1;
	ftpPubDir = strCat(prefix, "/etc/CDWserver/export");
	group = defaultGroup;
	homeDir = strCat(prefix, "/home/CDWserver");
	imageOnTheFly = 1;
	infoFile = strCat(configDir, "greeting");
	laserOn = 1;
	logDir = strCat(prefix, "/var/log/CDWserver");
	logoURL = NULL;
	logLevel = 1;
	maxCopies = 10;
	maxMBytesInSpoolDir = 1600;
	maxMBytesPerSession = 800;
	maxOpenConnections = 10;
	maxOpenSessions = 0;
	maxSpeed = 8;
	mkisofsArgs = "";
	mkisofsPath = "mkisofs";
	MP3decoding = 1;
	mpg123Info = "";
	mpg123Path = "mpg123";
	organization = "unknown";
	portNo = 12411;
	removeSessions = 0;
	reservedMBytes = 10;
	setSchedulerPath = strCat(prefix, "/usr/bin/setScheduler");
	showUsers = 1;
	spoolDir = strCat(prefix, "/var/spool/CDWserver");
	statusPortNo = 0;
	user = defaultUser;
	verify = 1;
	verifyPath = strCat(prefix, "/usr/bin/CDWverify");
	waitForCD = 10;
	waitForCDMsgFile = strCat(configDir, "waitForCD");
	webCDcreatorDir = "webCDcreator";

	try {
		gid = toGid(group);
	}
	catch (Error *error)
	{
		gid = 0;
		group = strDup(toGroup(gid));
	}

	try {
		uid = toUid(user);
	}
	catch (Error *error)
	{
		uid = 0;
		user = strDup(toUser(uid));
	}

	try {
		readConfigFile();
	}
	catch (Error *error)
	{
		firstError = error;
	}
}

void Config::checkExecAsRoot(const char *execName)
{
	struct stat statBuf;
	if (stat(execName, &statBuf) != 0)
		throw new Error("Cannot find %s.", execName);

	if (uid == 0)
		return;

	if (statBuf.st_uid != 0
		|| (statBuf.st_mode & S_ISUID) == 0)
		throw (new Error("Cannot run %s as root.", execName))
			->addHelp("Try one of\n<ul>\n"
				"<li>run<br>[chown root.%s %s<br>\n"
				"chmod 4750 %s]</li>\n"
				"<li>set <b>user=root</b> in %s</li></ul>",
				getGroup(), execName,
				execName,
				fileName);
	else
	if (((statBuf.st_mode & S_IXGRP) == 0
			|| statBuf.st_gid != gid)
		&& (statBuf.st_mode & S_IXOTH) == 0)
		throw (new Error("Cannot run %s as root.", execName))
			->addHelp("Try one of\n<ul>\n"
				"<li>set <b>group=%s</b> in %s</li>\n"
				"<li>set <b>user=root</b> in %s</li></ul>",
				toGroup(statBuf.st_gid), fileName,
				fileName);
}

void Config::evaluate(const char *str1, const char *str2)
{
/*	if (strcasecmp(str1, "accounts") == 0)
		accounts = toBoolean(str2);
	else
*/	if (strcasecmp(str1, "anonymousUse") == 0)
		anonymousUse = toBoolean(str2);
	else
	if (strcasecmp(str1, "burnProof") == 0)
		burnProof = toBoolean(str2);
	else
	if (strcasecmp(str1, "cdrdao") == 0)
		cdrdao = toBoolean(str2);
	else
	if (strcasecmp(str1, "cdrdaoArgs") == 0)
		cdrdaoArgs = strDup(str2);
	else
	if (strcasecmp(str1, "cdrdaoPath") == 0)
		cdrdaoPath = strDup(str2);
	else
	if (strcasecmp(str1, "cdrecordArgs") == 0)
		cdrecordArgs = strDup(str2);
	else
	if (strcasecmp(str1, "cdrecordDevArg") == 0
		|| strcasecmp(str1, "CDwriter") == 0
		|| strcasecmp(str1, "CDwriters") == 0)
		parseCDROMs(str2, writers, numOfWriters);
	else
	if (strcasecmp(str1, "cdrecordPath") == 0)
		cdrecordPath = strDup(str2);
	else
	if (strcasecmp(str1, "CDROM") == 0
		|| strcasecmp(str1, "CDROMs") == 0)
		parseCDROMs(str2, CDROMs, numOfReaders);
	else
	if (strcasecmp(str1, "copySupport") == 0)
		copySupport = toBoolean(str2);
	else
	if (strcasecmp(str1, "debug") == 0)
		debug = strDup(str2);
	else
	if (strcasecmp(str1, "defaultFilesDir") == 0)
		defaultFilesDir = strDup(str2);
	else
	if (strcasecmp(str1, "defaultSpeed") == 0)
		defaultSpeed = toInteger(str2);
	else
	if (strcasecmp(str1, "ejectOnDone") == 0)
		ejectOnDone = toBoolean(str2);
	else
	if (strcasecmp(str1, "ejectOnReservation") == 0)
		ejectOnReservation = toBoolean(str2);
	else
	if (strcasecmp(str1, "eMailOnError") == 0)
		eMailOnError = strDup(str2);
	else
	if (strcasecmp(str1, "exportArchive") == 0)
		exportArchive = toBoolean(str2);
	else
	if (strcasecmp(str1, "exportDir") == 0
		|| strcasecmp(str1, "ftpPubDir") == 0)
		ftpPubDir = strDup(str2);
	else
	if (strcasecmp(str1, "group") == 0)
	{
		gid = toGid(str2);
		group = strDup(str2);
	}
	else
	if (strcasecmp(str1, "homeDir") == 0) {
		homeDir = strDup(str2);
		setenv("HOME", homeDir, 1);
	}
	else
	if (strcasecmp(str1, "imageFile") == 0)
		throw new Error("now internal, remove line");
	else
	if (strcasecmp(str1, "imageOnTheFly") == 0)
		imageOnTheFly = toBoolean(str2);
	else
	if (strcasecmp(str1, "laser") == 0)
		laserOn = toBoolean(str2);
	else
	if (strcasecmp(str1, "logDir") == 0)
		logDir = strDup(str2);
	else
	if (strcasecmp(str1, "logLevel") == 0)
		logLevel = toInteger(str2);
	else
	if (strcasecmp(str1, "logoURL") == 0)
		logoURL = strDup(str2);
	else
	if (strcasecmp(str1, "logRequests") == 0) {
		if (toBoolean(str2) && logLevel < 3)
			logLevel = 3;
	}
	else
	if (strcasecmp(str1, "logSubProcess") == 0) {
		if (toBoolean(str2) && logLevel < 5)
			logLevel = 5;
	}
	else
	if (strcasecmp(str1, "logVerbose") == 0) {
		if (toBoolean(str2) && logLevel < 4)
			logLevel = 4;
	}
	else
	if (strcasecmp(str1, "maxCopies") == 0)
		maxCopies = toInteger(str2);
	else
	if (strcasecmp(str1, "maxMBytesInSpoolDir") == 0)
		maxMBytesInSpoolDir = toInteger(str2);
	else
	if (strcasecmp(str1, "maxMBytesPerSession") == 0)
		maxMBytesPerSession = toInteger(str2);
	else
	if (strcasecmp(str1, "maxOpenConnections") == 0)
		maxOpenConnections = toInteger(str2);
	else
	if (strcasecmp(str1, "maxOpenSessions") == 0)
	{
		if (strcasecmp(str2, "auto") == 0)
			maxOpenSessions = 0;
		else
			maxOpenSessions = toInteger(str2);
	}
	else
	if (strcasecmp(str1, "maxSpeed") == 0)
		maxSpeed = toInteger(str2);
	else
	if (strcasecmp(str1, "maxStoreMBs") == 0)
		throw new Error(
			": use maxMBytesInSpoolDir (see CDWserver/config.eg)");
	else
	if (strcasecmp(str1, "mkisofsArgs") == 0)
		mkisofsArgs = strDup(str2);
	else
	if (strcasecmp(str1, "mkisofsMArg") == 0)
		throw new Error("now internal, remove line");
	else
	if (strcasecmp(str1, "mkisofsPath") == 0)
		mkisofsPath = strDup(str2);
	else
	if (strcasecmp(str1, "MP3decoding") == 0)
		MP3decoding = toBoolean(str2);
	else
	if (strcasecmp(str1, "mpg123Path") == 0)
		mpg123Path = strDup(str2);
	else
	if (strcasecmp(str1, "organization") == 0)
		organization = strDup(str2);
	else
	if (strcasecmp(str1, "portNo") == 0)
		portNo = toInteger(str2);
	else
	if (strcasecmp(str1, "removeSessions") == 0)
		removeSessions = toBoolean(str2);
	else
	if (strcasecmp(str1, "reservedMBytes") == 0)
	{
		reservedMBytes = toInteger(str2);
		if (reservedMBytes < 10)
			reservedMBytes = 10;
	}
	else
	if (strcasecmp(str1, "showUsers") == 0)
		showUsers = toBoolean(str2);
	else
	if (strcasecmp(str1, "speed") == 0)
		throw new Error("use defaultSpeed and maxSpeed");
	else
	if (strcasecmp(str1, "spoolDir") == 0)
		spoolDir = strDup(str2);
	else
	if (strcasecmp(str1, "statusPortNo") == 0)
		statusPortNo = toInteger(str2);
	else
	if (strcasecmp(str1, "user") == 0)
	{
		uid = toUid(str2);
		user = strDup(str2);
	}
	else
	if (strcasecmp(str1, "verify") == 0)
		verify = toBoolean(str2);
	else
	if (strcasecmp(str1, "waitForCD") == 0)
		waitForCD = toInteger(str2);
	else
	if (strcasecmp(str1, "webCDcreatorDir") == 0) {
		const char *str0 = str2;
		while (*str0 == '/')
			str0++;
		webCDcreatorDir = strDup(str0);
	}
	else
		throw new Error("unknown symbol");
}

const char *Config::getRawDevice(int bus, int ID, int LUN, int no)
{
	char str[1024];
#ifdef FreeBSD
	sprintf(str, "/dev/su%d-%d-%d", bus, ID, LUN);
#else
	sprintf(str, "/dev/scd%d", no);
#endif
	return strDup(str);
}

void Config::parseCDROMs(const char *str, const char **list, int &count)
{
	if (strcasecmp(str, "auto") == 0)
		return;

	char str2[strlen(str) + 1];
	strcpy(str2, str);
	char *ptr = str2;
	while (ptr != NULL)
	{
		int bus, id, lun;
		if (sscanf(ptr, "%d,%d,%d",
			&bus, &id, &lun) != 3)
			throw new Error("invalid value");

		char str3[1024];
		sprintf(str3, "%d,%d,%d", bus, id, lun);
		list[count++] = strDup(str3);
		ptr = strchr(ptr, ';');
		if (ptr != NULL)
			ptr++;
	}
}

void Config::parseLine(
	const char *lineStr, char *str1, char *str2)
{
	str1[0] = str2[0] = 0;

	if (lineStr[0] == '#')
		return;

	int i = 0;
	while (lineStr[i] == '\t'
		|| lineStr[i] == ' ')
		i++;

	int j = 0;
	for (; lineStr[i] != 0
		&& lineStr[i] != '\n'
		&& lineStr[i] != '\t'
		&& lineStr[i] != ' '
		&& lineStr[i] != '='; i++)
		str1[j++] = lineStr[i];
	str1[j] = 0;

	while (lineStr[i] == '\t'
		|| lineStr[i] == ' '
		|| lineStr[i] == '=')
		i++;

	j = 0;
	while (lineStr[i] != 0
		&& lineStr[i] != '\n')
		str2[j++] = lineStr[i++];

	// truncate trailing whitespace
	while (j > 0
		&& (str2[j - 1] == '\t'
			|| str2[j - 1] == ' '))
		j--;
	str2[j] = 0;
}

void Config::prepareBinaries() {
	prepareBinary(cdrdaoPath);
}

void Config::prepareBinary(const char *&path) {
	File orig = new File(path);
	orig.complete();
	if (!orig.exists() || orig.isDirectory())
		return;

	String str = new String(orig.getPath());
	StringBuffer nameBuf;
	for (int i = str.length() - 1; i >= 0 && str.charAt(i) != '/'; i--)
		nameBuf.insert(0, (char)str.charAt(i));
	String name = new String(nameBuf);
	if (name.length() == 0)
		return;

	File binDir = new File(homeDir, "bin");
	File copy = new File(binDir, name.getBytes());
	copy.remove();
	copy.mkdirs();
	String cmd;
	cmd = cmd + "cp -p " + orig.getPath() + " " + copy.getPath();
	log.put(4, "%s", cmd.getBytes());
	system(cmd.getBytes());
	String cmd2;
	cmd2 = cmd2 + "chown root." + group + " " + copy.getPath();
	system(cmd2.getBytes());
	String cmd3;
	cmd3 = cmd3 + "chmod 4710" + " " + copy.getPath();
	system(cmd3.getBytes());
	String cmd4;
	cmd4 = cmd4 + "chmod 755" + " " + binDir.getPath();
	system(cmd4.getBytes());
	path = strDup(copy.getPath().getBytes());
}

void Config::readConfigFile()
{
	if (!File(fileName).exists()
		|| File(fileName).getSize() == 0)
		writeConfigFile();

	if (!File(infoFile).exists()
		|| File(infoFile).getSize() == 0)
		writeInfoFile();

	if (!File(waitForCDMsgFile).exists()
		|| File(waitForCDMsgFile).getSize() == 0)
		writeWaitForCDMsgFile();

	FILE *inStream = fopen(fileName, "r");
	if (inStream == NULL)
		throw new Error("can't open configFile %s", fileName);

	numOfReaders = 0;
	numOfWriters = 0;

	int lineNo = 0;
	char lineStr[1024];
	while (fgets(lineStr, sizeof lineStr, inStream) != NULL)
	{
		lineNo++;
		char str1[1024], str2[1024];
		parseLine(lineStr, str1, str2);

		try {
			if (strlen(str1) == 0 && strlen(str2) == 0)
				continue;
			else
				evaluate(str1, str2);
		}
		catch (Error *error)
		{
			if (firstError == NULL)
			{
				firstError = new Error(
					"error in %s: line %d: \"%s=%s\": %s",
					fileName, lineNo, str1, str2,
					error->toString());
				log.put(firstError->toString());
			}
			delete error;
		}
	}

	fclose(inStream);

	if (firstError != NULL)
		throw firstError;
}

void Config::reload()
{
	log.put("Config::reload() started");

	if (firstError != NULL)
	{
		delete firstError;
		firstError = NULL;
	}

	try {
		readConfigFile();

		if (uid != geteuid() || gid != getegid())
			throw (new Error("Restart %s as root "
				"to set user %s and group %s!",
				product, user, group));

		if (accounts && !File(homeDir).exists())
			throw new Error("homeDir %s does not exist", homeDir);

		if (!File(logDir).exists())
			throw new Error("logDir %s does not exist", logDir);

		if (!File(spoolDir).exists())
			throw new Error("spoolDir %s does not exist", spoolDir);

		cdrecordPath = strDup(File(cdrecordPath).complete());
		checkExecAsRoot(cdrecordPath);
		log.putv("cdrecordPath = %s", cdrecordPath);

		mkisofsPath = strDup(File(mkisofsPath).complete());
		checkExecAsRoot(mkisofsPath);
		log.putv("mkisofsPath = %s", mkisofsPath);

		setSchedulerPath = strDup(File(setSchedulerPath).complete());
		checkExecAsRoot(setSchedulerPath);
		log.putv("setSchedulerPath = %s", setSchedulerPath);

		if (verify)
		{
			verifyPath = strDup(File(verifyPath).complete());
			checkExecAsRoot(verifyPath);
			log.putv("verifyPath = %s", verifyPath);
		}

		if (uid != 0)
		if (File(configDir).isReadAndWritable(uid, gid, 0) != 0)
			throw (new Error("configDir %s "
			"is not read and writable by user %s / group %s.",
				configDir, user, group))
			->addHelp("Try\n<ul>\n"
				"<li>run<br>[chown %s %s]</li></ul>",
				user, configDir);

		if (uid != 0)
		if (File(accountsFile).isReadAndWritable(uid, gid) != 0)
			throw (new Error("accountsFile %s "
			"is not read and writable by user %s / group %s.",
				configDir, user, group))
			->addHelp("Try\n<ul>\n"
				"<li>run<br>[chown %s %s]</li></ul>",
				user, accountsFile);

/*		if (uid != 0)
		if (accounts && File(homeDir).isReadAndWritable(uid, gid) != 0)
			throw (new Error("homeDir %s or a file below it "
			"is not read and writable by user %s / group %s.",
				homeDir, user, group))
			->addHelp("Try one of\n<ul>\n"
				"<li>change <b>homeDir</b> in %s</li>\n"
				"<li>run<br>[chown %s %s -R]</li></ul>",
				fileName, user, homeDir);
*/
		if (uid != 0)
		if (File(logDir).isReadAndWritable(uid, gid) != 0)
			throw (new Error("logDir %s or a file below it "
			"is not read and writable by user %s / group %s.",
				logDir, user, group))
			->addHelp("Try one of\n<ul>\n"
				"<li>change <b>logDir</b> in %s</li>\n"
				"<li>run<br>[chown %s %s -R]</li></ul>",
				fileName, user, logDir);

		if (uid != 0)
		if (File(spoolDir).checkOwner(uid) != 0)
			throw (new Error("spoolDir %s or a file below it "
				"is not owned by user %s.",
				spoolDir, user))
			->addHelp("Try one of\n<ul>\n"
				"<li>change <b>spoolDir</b> in %s</li>\n"
				"<li>run<br>[chown %s %s -R]</li></ul>",
				fileName, user, spoolDir);

/*		if (!config.getImageOnTheFly())
		{
			FILE *outStream = fopen(imageFile, "w");
			if (outStream == NULL)
				throw new Error("can't create imageFile %s",
					imageFile);
			else
				fclose(outStream);
		}
*/
		testMkisofs();
		testCdrecord();
		testCdrecord2();
		for (int i = 0; i < numOfWriters; i++)
			testCdrecord3(i);
		if (MP3decoding)
			testMpg123();
	}
	catch (Error *error)
	{
		firstError = error;
		return;
	}

	if (maxOpenSessions == 0)
	{
		// calculate number of possible parallel sessions
		int n = maxMBytesInSpoolDir;
		if (imageOnTheFly)
		{
			if (n < maxMBytesPerSession)
				maxMBytesPerSession = n;
		}
		else
		{
			if (n < 2 * maxMBytesPerSession)
				maxMBytesPerSession = n / 2;
			n -= maxMBytesPerSession;
		}
		maxOpenSessions = n / maxMBytesPerSession;
	}
}

void Config::setPermissions() {
	char fileName[4*1024];
	sprintf(fileName, "%s/connects",
		config.getLogDir());
	chown(fileName, uid, gid);
	sprintf(fileName, "%s/log",
		config.getLogDir(), version);
	chown(fileName, uid, gid);
//	chown(imageFile, uid, gid);
}

void Config::testCdrecord()
{
	Process p(getCdrecordPath());
	p.addArg("-version");
	p.start();
	const char *lineStr = p.readLine();
	if (lineStr == NULL)
		throw new Error("can't run cdrecord");
	if (p.getResult() != 0)
		throw new Error("error running cdrecord -version");
	char str1[strlen(lineStr) + 1], str2[strlen(lineStr) + 1];
	if (sscanf(lineStr, "%s %s", str1, str2) != 2)
		throw new Error("can't get version of cdrecord");
	char *str3 = new char[strlen(str1) + strlen(str2) + 2];
	sprintf(str3, "%s %s", str1, str2);
	cdrecordInfo = str3;
	log.put(lineStr);
}

void Config::testCdrecord2() {
	Process p(getCdrecordPath());
	p.addArg("-scanbus");
	p.start();

	for (int i = 0; i < maxNumOfReaders; i++)
		readerRawDevices[i] = NULL;
	for (int i = 0; i < maxNumOfWriters; i++)
		writerRawDevices[i] = NULL;

	int readersAuto = numOfReaders == 0;
	int writersAuto = numOfWriters == 0;
	int numOfCDROMs = 0;
	while (1) {
		const char *lineStr = p.readLine();
		if (lineStr == NULL)
			break;
		if (strstr(lineStr, "CD-ROM") == NULL
			&& strstr(lineStr, "WORM") == NULL)
			continue;

		log.put(lineStr);
		int bus, ID, LUN;
		if (sscanf(lineStr, "%d,%d,%d", &bus, &ID, &LUN) != 3)
			continue;

		char CDROM[20];
		sprintf(CDROM, "%d,%d,%d", bus, ID, LUN);

		const char *str = lineStr;
		for (int i = 0; i < 3; i++) {
			while (*str != '\'' && *str != 0)
				str++;
			if (*str != 0)
				str++;
		}
		char model[17];
		strncpy(model, str, 16);
		model[16] = 0;

		int isWriter = 0;
		const char *patterns[] = {
			"CD-R", "CDR", "CR", "CW", "RW",
			"LTR", "MP-7", "PX-R", "PX-W",
			"CD-Writer", NULL };
		if (strstr(model, "CDROM") == NULL
			&& strstr(model, "CD-ROM") == NULL) {
			const char **p = patterns;
			while (*p != NULL && strstr(model, *p) == NULL)
				p++;
			if (*p != NULL)
				isWriter = 1;
		}

		int used = 0;
		if (!writersAuto) {
			int i;
			for (i = 0;
				i < numOfWriters
				&& strcmp(writers[i], CDROM) != 0; i++);
			if (i < numOfWriters) {
				writerRawDevices[i] = getRawDevice(
					bus, ID, LUN, numOfCDROMs);
				used = 1;
			}
		}

		if (!used && !readersAuto) {
			int i;
			for (i = 0;
				i < numOfReaders
				&& strcmp(CDROMs[i], CDROM) != 0; i++);
			if (i < numOfReaders) {
				readerInfos[i] = strDup(model);
				readerRawDevices[i] = getRawDevice(
					bus, ID, LUN, numOfCDROMs);
				used = 1;
			}
		}

		if (isWriter && writersAuto && !used) {
			writers[numOfWriters] = strDup(CDROM);
			writerRawDevices[numOfWriters++] = getRawDevice(
				bus, ID, LUN, numOfCDROMs);
		}
		else
		if (readersAuto && !used) {
			CDROMs[numOfReaders] = strDup(CDROM);
			readerInfos[numOfReaders] = strDup(model);
			readerRawDevices[numOfReaders++] = getRawDevice(
				bus, ID, LUN, numOfCDROMs);
		}

		numOfCDROMs++;
	}

	log.put("CDROMs = %d, writers = %d", numOfReaders, numOfWriters);

	if (p.getResult() != 0)
		throw new Error("error running cdrecord -scanbus");

	if (numOfWriters == 0)
		throw new Error("Cannot autodetect the CDwriter.");

	for (int i = 0; i < numOfReaders; i++) {
		if (readerRawDevices[i] == NULL)
			throw new Error("Cannot find reader %s",
				CDROMs[i]);
		log.put("reader = %s, readerRawDevice = %s",
			CDROMs[i], readerRawDevices[i]);
	}

	for (int i = 0; i < numOfWriters; i++) {
		if (writerRawDevices[i] == NULL)
			throw new Error("Cannot find writer %s",
				writers[i]);

		char str[1024];
		sprintf(str, "%s/cdimage%d.iso", spoolDir, i);
		imageFile[i] = strDup(str);

		writerInfos[i] = "unknown";

		log.put("writer = %s, writerRawDevice = %s",
			writers[i], writerRawDevices[i]);
	}
}

void Config::testCdrecord3(int writerNo) {
	Process p(getCdrecordPath());
	char str[4*1024];
	sprintf(str, "dev=%s", getWriter(writerNo));
	p.addArg(str);
	p.addArg("-inq");
	p.start();

	int errorCode = 0;
	str[0] = 0;
	while (1) {
		const char *lineStr = p.readLine();
		if (lineStr == NULL)
			break;

		char a[strlen(lineStr) + 1], b[strlen(lineStr) + 1];
		if (sscanf(lineStr, "%s : '%s", a, b) == 2)
		if (strcmp(a, "Vendor_info") == 0
			|| strcmp(a, "Identifikation") == 0
			|| strcmp(a, "Revision") == 0) {
			strcat(str, b);
			strcat(str, " ");
		}
	}

	if (p.getResult() != 0)
		throw new Error("error running cdrecord dev=%s -inq",
			getWriter(writerNo));

	if (strlen(str) >= 2 && str[strlen(str) - 2] == '\'')
		str[strlen(str) - 2] = 0;
	writerInfos[writerNo] = strDup(str);
	DVDplusRWSupport[writerNo] =
		strstr(str, "DVD") != NULL
			&& strstr(str, "+RW") != NULL;
}

void Config::testMkisofs()
{
	Process p(getMkisofsPath());
	p.addArg("-version");
	p.start();
	const char *lineStr = p.readLine();
	if (lineStr == NULL)
		throw new Error("can't run mkisofs");
	if (p.getResult() != 0)
		throw new Error("error running mkisofs -version");
	char str1[strlen(lineStr) + 1], str2[strlen(lineStr) + 1];
	if (sscanf(lineStr, "%s %s", str1, str2) != 2)
		throw new Error("can't get version of mkisofs");
	char *str3 = new char[strlen(str1) + strlen(str2) + 2];
	sprintf(str3, "%s %s", str1, str2);
	mkisofsInfo = str3;
	log.put(lineStr);
}

void Config::testMpg123()
{
	Process mpg123(getMpg123Path());
	mpg123.addArg("--longhelp");
	mpg123.start();
	const char *lineStr = mpg123.readLine();
	if (lineStr == NULL)
		throw new Error("cannot run mpg123 (%s)", getMpg123Path());
	do
	{
		char str[strlen(lineStr) + 1];
		if (sscanf(lineStr, "Version %s", str) == 1)
			mpg123Info = strDup(str);
		lineStr = mpg123.readLine();
	} while (lineStr != NULL);
}

gid_t Config::toGid(const char *groupName)
{
	struct group *group = getgrnam(groupName);
	if (group == NULL)
		throw new Error("unknown group: %s", groupName);
	return group->gr_gid;
}

const char *Config::toGroup(gid_t gid)
{
	struct group *group = getgrgid(gid);
	if (group == NULL)
	{
		static char str[20];
		sprintf(str, "%d", gid);
		return str;
	}
	else
		return group->gr_name;
}

String &Config::toHTML() {
	text = "";
	HTML = 1;

	text = text
		+ "<html>\n"
		+ "<head><title>Configuration of CDWserver</title></head>"
		+ "<body>\n\n"
		+ "<h1># Configuration of CDWserver</h1>\n\n";

	write();

	text = text
		+ "</body>\n"
		+ "</html>\n";

	return text;
}

const char *Config::toString(boolean b)
{
	if (b)
		return "on";
	else
		return "off";
}

uid_t Config::toUid(const char *userName)
{
	struct passwd *passwd = getpwnam(userName);
	if (passwd == NULL)
		throw new Error("unknown user: %s", userName);
	return passwd->pw_uid;
}

const char *Config::toUser(uid_t uid)
{
	struct passwd *passwd = getpwuid(uid);
	if (passwd == NULL)
	{
		static char str[20];
		sprintf(str, "%d", uid);
		return str;
	}
	else
		return passwd->pw_name;
}

void Config::write() {
	write("# 1. Features");
//	write("accounts", toString(accounts));
	write("burnProof", toString(burnProof));
	write("cdrdao", toString(cdrdao));
	write("copySupport", toString(copySupport));
	write("imageOnTheFly", toString(imageOnTheFly));
	write("laser", toString(laserOn));
	write("MP3decoding", toString(MP3decoding));
	write("verify", toString(verify));

	write("# 2. Server");
	if (!HTML) {
		write("group", group);
		write("user", user);
	}
	write("portNo", portNo);
	write("statusPortNo", statusPortNo);
	write("maxOpenConnections", maxOpenConnections);

	write("# 3. Directories");
	write("exportDir", ftpPubDir);
	write("homeDir", homeDir);
	write("logDir", logDir);
	write("spoolDir", spoolDir);

	write("# 4. Cache");
	write("maxMBytesInSpoolDir", maxMBytesInSpoolDir);
	write("maxMBytesPerSession", maxMBytesPerSession);
	write("maxOpenSessions", "auto");
	write("reservedMBytes", reservedMBytes);

	write("# 5. CD-ROM(s) and CD-writer(s)");
	if (numOfReaders == 0)
		write("CDROMs", "auto");
	else
	{
		char str[1024];
		strcpy(str, CDROMs[0]);
		for (int i = 1; i < numOfReaders; i++)
		{
			strcat(str, ";");
			strcat(str, CDROMs[i]);
		}
		write("CDROMs", str);
	}
	if (numOfWriters == 0)
		write("CDwriters", "auto");
	else
	{
		char str[1024];
		strcpy(str, writers[0]);
		for (int i = 1; i < numOfWriters; i++)
		{
			strcat(str, ";");
			strcat(str, writers[i]);
		}
		write("CDwriters", str);
	}
	write("defaultSpeed", defaultSpeed);
	write("maxSpeed", maxSpeed);

	write("# 6. Programs");
	write("cdrdaoPath", cdrdaoPath);
	write("cdrecordPath", cdrecordPath);
	write("mkisofsPath", mkisofsPath);
	write("mpg123Path", mpg123Path);

	write("# 7. Options");
	write("anonymousUse", toString(anonymousUse));
	write("cdrecordArgs", cdrecordArgs);
	write("#debug", "Process,Server::noDisc()");
	write("#defaultFilesDir", "/etc/CDWserver/default_files");
	write("ejectOnDone", toString(ejectOnDone));
	write("ejectOnReservation", toString(ejectOnReservation));
	write("#eMailOnError", "unknown@unknown.org");
	write("logLevel", toString(logLevel));
	write("#logoURL", "http://unknown.org/logo.jpg");
	write("maxCopies", maxCopies);
	write("organization", organization);
	write("removeSessions", toString(removeSessions));
	write("showUsers", toString(showUsers));
	write("waitForCD", waitForCD);
}

void Config::write(const char *str) {
	if (HTML)
		text = text + "<h2>" + str + "</h2>\n";
	else
		text = text + "\n" + str + "\n\n";
}

void Config::write(const char *str1, const char *str2) {
	if (HTML)
		text = text + str1 + " = " + str2 + "<br>\n";
	else
		text = text + str1 + "=" + str2 + "\n";
}

void Config::write(const char *str, int number) {
	if (HTML)
		text = text + str + " = " + number + "<br>\n";
	else
		text = text + str + "=" + number + "\n";
}

void Config::writeConfigFile() {
	int out = open(fileName, O_CREAT | O_WRONLY,
		S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
	if (out < 0)
		throw new Error("can't create configFile %s", fileName);

	PrintWriter writer = new PrintWriter(out);
	text = "";
	HTML = 0;

	text = text
		+ "# Configuration of CDWserver\n"
		+ "# See config.html for details\n\n";

	write();

	writer.write(text);
	writer.flush();
	close(out);
}

void Config::writeInfoFile()
{
	FILE *out = fopen(infoFile, "w");
	if (out == NULL)
		throw new Error("can't create %s", infoFile);

	fprintf(out, "Welcome to CDWserver.\n");
	fprintf(out, "\n");
	fprintf(out, "This is the contents of %s.\n", infoFile);
	fclose(out);
}

void Config::writeWaitForCDMsgFile()
{
	FILE *out = fopen(waitForCDMsgFile, "w");
	if (out == NULL)
		throw new Error("can't create %s", waitForCDMsgFile);

	fprintf(out, "The CD-writer No. $writerNo ($writerType) "
		"was reserved for $reservedMinutes minutes.\n");
	fprintf(out, "Please insert a CD-R(W) "
		"and wait until it is ejected!\n");
	fprintf(out, "\n");
	fprintf(out, "This is the contents of %s.\n", waitForCDMsgFile);
	fclose(out);
}
