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

#include <stdlib.h>
#include <sys/types.h>
#include "Accounts.h"
#include "Config.h"
#include "PrintWriter.h"
#include "String.h"
#include "Version.h"

Config::Config() {
	cdrdaoInfo = "";
	cdrecordInfo = "cdrecord";
	growisofsInfo = "";
	mkisofsInfo = "mkisofs";
	mpg123Info = "";
	ogg123Info = "";
	soxInfo = "";

	pthread_mutex_init(&mutex, NULL);

	fileName = getenv("CDWserverConfig");
	if (fileName == NULL)
		fileName = strCat(prefix, "/etc/CDWserver/config");

	configDir = fileName;
	int p = configDir.lastIndexOf("/");
	if (p >= 0)
		configDir = configDir.substring(0, p);
	configDir = configDir + "/";

	firstError = "";
	firstErrorPriority = 0;
	hasReader = false;
	numOfWriters = 0;
	debugCache = strDup("");

	// values
	accounts = 1;
	accountsFile = strCat(configDir.getBytes(), "accounts");
	cdrecordArgs = "";
	defaultFilesDir = strCat(prefix, "/etc/CDWserver/default_files");
	defaultSpeed = 4;
	eMailOnError = NULL;
	group = defaultGroup;
	homeDir = strCat(prefix, "/home/CDWserver");
	imageOnTheFly = 1;
	infoFile = strCat(configDir.getBytes(), "greeting");
	laserOn = 1;
	maxCopies = 10;
	maxSpeed = 8;
	mkisofsArgs = "";
	organization = "unknown";
	removeSessions = 0;
	statusPortNo = 0;
	user = defaultUser;
	userSave = true;
	waitForCD = 10;
	waitForCDMsgFile = strCat(configDir.getBytes(), "waitForCD");

	numOfItems = 0;
	add("anonymousUse", true);
	add("audioCDSupport", true);
	add("burnProof", false);
	add("cdrdao", true);
	add("cdrdaoArgs",
		"--source-driver generic-mmc-raw --driver generic-mmc");
	add("cdrdaoPath", makeHomeBinPath("cdrdao"));
	add("cdrdaoSimulationPath", makeHomeBinPath("cdrdao-dummy"));
	add("cdrecordPath", makeHomeBinPath("cdrecord"));
	add("cdrecordSimulationPath", makeHomeBinPath("cdrecord-dummy"));
	add("copySupport", true);
	add("debug", debugCache);
	add("DVDplusRW_formatPath", makeUsrBinPath("dvd+rw-format"));
	add("DVDplusRW_formatSimulationPath",
		makeHomeBinPath("dvd+rw-format-dummy"));
	add(DVDSUPPORT, false);
	add("ejectOnDone", true);
	add("ejectOnReservation", true);
	add("exportArchive", true);
	String str;
	str = str + prefix + "/etc/CDWserver/export";
	add("exportDir", str.getBytes());
	add("gracetime", 9);
	add("growisofsPath", makeUsrBinPath("growisofs"));
	add("growisofsSimulationPath", makeHomeBinPath("growisofs-dummy"));
	str = "";
	str = str + configDir + "http";
	add("httpDir", str.getBytes());
	str = "";
	str = str + prefix + "/var/log/CDWserver";
	add("logDir", str.getBytes());
	add("logLevel", 1);
	logLevelCache = getActiveInt("logLevel");
	add("logoURL", "http://@SERVER@:@PORT@/penguin.png");
	add("maxMBytesInSpoolDir", 4000);
	maxMBytesInSpoolDirCache = getActiveInt("maxMBytesInSpoolDir");
	add("maxMBytesPerSession", 800);
	maxMBytesPerSessionCache = getActiveInt("maxMBytesPerSession");
	add("maxOpenConnections", 10);
	add("maxOpenSessions", 3);
	add("mkisofsPath", makeHomeBinPath("mkisofs"));
	add("MP3decoding", true);
	add("mpg123Path", "/usr/bin/mpg123");
	add("ogg123Path", "/usr/bin/ogg123");
	add("oggDecoding", true);
	add("overburnSupport", false);
	add("overburnSupportArgs", "-dao -overburn");
	add(PASSWORD, "");
	add("portNo", 12411);
	add("reservedMBytes", 10);
	reservedMBytesCache = getActiveInt("reservedMBytes");
	str = "";
	str = str + prefix + "/usr/bin/setScheduler";
	add("setSchedulerPath", str.getBytes());
	add("showUsers", true);
	add(SIMULATION, false);
//	add("SMTPserver", "localhost");
	add("soxPath", "/usr/bin/sox");
	str = "";
	str = str + prefix + "/var/spool/CDWserver";
	add("spoolDir", str.getBytes());
	add("useUTF8", false);
	add("verify", true);
	add("verifyPath", makeUsrBinPath("CDWverify"));
	add("verifySimulationPath", makeUsrBinPath("CDWverify-dummy"));
	add("webCDcreatorDir", "webCDcreator");
	add("writeProtection", true);

	try {
		gid = toGid(group);
	}
	catch (Exception *error) {
		gid = 0;
		String str = toGroup(gid);
		group = strDup(str.getBytes());
		delete error;
	}

	try {
		uid = toUid(user);
	}
	catch (Exception *error) {
		uid = 0;
		String str = toUser(uid);
		user = strDup(str.getBytes());
		delete error;
	}

	try {
		readConfigFile();
	}
	catch (Exception *error) {
		if (firstErrorPriority < 100) {
			firstError = error->toString();
			firstErrorPriority = 100;
		}
		delete error;
	}

	String password = readPassword();
	if (password.equals("changeit")) {
		char passwd[8 + 1];
		time_t t;
		time(&t);
		srandom(t);
		for (int i = 0; i < sizeof passwd - 1; i++)
			passwd[i] = 'A' + random() % ('Z' - 'A' + 1);
		passwd[sizeof passwd - 1] = 0;
		password = passwd;
		writePassword(password);
	}
}

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

	String info = "";

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

		String line = lineStr;
		if (line.startsWith("Vendor_info")
				|| line.startsWith("Identifikation")
				|| line.startsWith("Revision")) {
			int p = line.indexOf("'");
			if (p >= 0 && p + 1 < line.length()) {
				line = line.substring(p + 1);
				p = line.indexOf("'");
				while (p > 0 && line.charAt(p - 1) <= 32)
					p--;
				if (p > 0) {
					if (info.length() > 0)
						info = info + " ";
					info = info + line.substring(0, p);
				}
			}
		}
	}

	if (p.getResult() != 0)
		throw new Exception(S.e + "Error running cdrecord dev="
			+ getWriter(writerNo) + " -inq");

	if (info.length() > 0)
		writers[writerNo].info = info;
}

void Config::cdrecordScanbus() {
	hasReader = false;
	numOfWriters = 0;
	for (int i = 0; i < maxNumOfWriters; i++)
		writers[i].rawDevice = "";
	maxSpeed = 1;

	Process p(getCdrecordPath());
	p.addArg("-scanbus");
	p.start();
	cdrecordScanbus2(p);

	Process p2(getCdrecordPath());
	p2.addArg(S.e + "dev=" + ATAPI);
	p2.addArg("-scanbus");
	p2.start();
	cdrecordScanbus2(p2, ATAPI);

	log.put(2, S.e + "writers = " + numOfWriters);

	if (p.getResult() != 0 && p2.getResult() != 0)
		throw (new Exception("Error running cdrecord -scanbus"))
			->addHelp(S.e + "Do you use SuSE Linux 8.2?\n<br>\n"
				+ "-> Use a cdrecord without the <b>resmgr</b> patch!\n"
				+ "<a href=\"" + Version.projectHome
					+ "help/SuSE82.html\">Help</a>\n");

	if (numOfWriters == 0)
		throw new Exception("Cannot autodetect a writer.");

	for (int i = 0; i < numOfWriters; i++) {
		if (writers[i].rawDevice.length() == 0)
			throw new Exception(S.e + "Cannot find writer "
				+ writers[i].dev);

		String name = "cdimage";
		name = name + i + ".iso";
		File f = new File(getSpoolDir(), name);
		writers[i].imageFile = f.getPath();

//		writers[i].info = "unknown";

		log.put(2, S.e + "writer = " + writers[i].dev
			+ ", writerRawDevice = " + writers[i].rawDevice);
	}
}

void Config::cdrecordScanbus2(Process &p, const char *driver) {
	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;

		String dev = driver;
		if (dev.length() > 0)
			dev = dev + ":";
		dev = dev + bus + "," + ID + "," + LUN;

		String line = lineStr;
		int p0 = line.indexOf("'");
		if (p0 < 0)
			continue;

		// create the device name
		StringBuffer id = "device.";
		StringBuffer modelBuf = "";
		for (int i = p0 + 1, j = 0; i < line.length() && j < 3; i++)
			if (line.charAt(i) == '\'')
				j++;
			else {
				if (line.charAt(i) > 32 && line.charAt(i) != '=')
					id.append((char)line.charAt(i));
				modelBuf.append((char)line.charAt(i));
			}
		String device = new String(id);
		log.put(5, S.e + device);
		String model = modelBuf;

		// avoid duplicates by adding a number
		String deviceTmp = device;
		deviceTmp = deviceTmp + "-1";
		int count = 1;
		for (int i = 0; i < numOfWriters; i++)
			if (writers[i].id.equals(deviceTmp)) {
				if (dev.startsWith(ATAPI)
						&& !writers[i].dev.startsWith(ATAPI)) {
					// probably already a SCSI device -> skip
					count = -1;
					break;
				}
				count++;
				deviceTmp = device;
				deviceTmp = deviceTmp + "-" + count;
				i = 0;
			}
		if (count < 0)
			// skip SCSI device
			continue;
		device = deviceTmp;

		String str = device;
		str = str + ".CD";
		if (search(str.getBytes()) < 0)
			add(str.getBytes(), 16);
		if (getActiveInt(str.getBytes()) > 0)
			hasReader = true;

		// CD writer autodetection
		const char *patterns[] = {
			"CD-R", "CDR", "CR", "CW", "RW",
			"LTR", "MP-7", "PX-R", "PX-W",
			"CD-Writer", NULL };
		if (model.indexOf("CDROM") < 0
				&& model.indexOf("CD-ROM") < 0) {
			const char **p = patterns;
			while (*p != NULL && model.indexOf(*p) < 0)
				p++;
			if (*p != NULL) {
				String str = device;
				str = str + ".CD-R";
				if (search(str.getBytes()) < 0)
					add(str.getBytes(), 8);

				str = device;
				str = str + ".CD-RW";
				if (search(str.getBytes()) < 0)
					add(str.getBytes(), 4);
			}
		}

		if (getActiveBoolean(DVDSUPPORT)) {
			if (model.indexOf("DVD-RW") >= 0) {
				String str = device;
				str = str + ".DVD-R";
				if (search(str.getBytes()) < 0)
					add(str.getBytes(), 2);

				str = device;
				str = str + ".DVD-RW";
				if (search(str.getBytes()) < 0)
					add(str.getBytes(), 2);
			}
			if (model.indexOf("DVD+") >= 0) {
				String str = device;
				str = str + ".DVD+R";
				if (search(str.getBytes()) < 0)
					add(str.getBytes(), 2);

				str = device;
				str = str + ".DVD+RW";
				if (search(str.getBytes()) < 0)
					add(str.getBytes(), 2);
			}
		}

		writers[numOfWriters].dev = dev;
		writers[numOfWriters].id = device;
		writers[numOfWriters].info = model;
		writers[numOfWriters].rawDevice = getRawDevice(
			bus, ID, LUN, numOfCDROMs, driver);
		writers[numOfWriters].CD = getMaxSpeed(numOfWriters, "CD");
		writers[numOfWriters].CDR = getMaxSpeed(numOfWriters, "CD-R");
		writers[numOfWriters].CDRW = getMaxSpeed(numOfWriters, "CD-RW");
		writers[numOfWriters].DVDMinusR
			= getMaxSpeed(numOfWriters, "DVD");
		writers[numOfWriters].DVDMinusR
			= getMaxSpeed(numOfWriters, "DVD-R");
		writers[numOfWriters].DVDMinusRW
			= getMaxSpeed(numOfWriters, "DVD-RW");
		writers[numOfWriters].DVDPlusR
			= getMaxSpeed(numOfWriters, "DVD+R");
		writers[numOfWriters].DVDPlusRW
			= getMaxSpeed(numOfWriters, "DVD+RW");

		String supports = "";
		if (writers[numOfWriters].CDR > 0)
			supports = supports + "CD-R, ";
		if (writers[numOfWriters].CDRW > 0)
			supports = supports + "CD-RW, ";
		if (writers[numOfWriters].DVDMinusR > 0)
			supports = supports + "DVD-R, ";
		if (writers[numOfWriters].DVDMinusRW > 0)
			supports = supports + "DVD-RW, ";
		if (writers[numOfWriters].DVDPlusR > 0)
			supports = supports + "DVD+R, ";
		if (writers[numOfWriters].DVDPlusRW > 0)
			supports = supports + "DVD+RW, ";

		if (supports.length() < 2)
			writers[numOfWriters].supports = "-";
		else
			writers[numOfWriters].supports
				= supports.substring(0, supports.length() - 2);
		numOfWriters++;

		try {
			String str = device;
			str = str + ".CD-R";
			int speed = getActiveInt(str.getBytes());
			if (speed > maxSpeed)
				maxSpeed = speed;
		}
		catch (Exception *e) {
			delete e;
		}

		numOfCDROMs++;
	}
}

void Config::checkExecAsRoot(const char *execName) {
	struct stat statBuf;
	if (stat(execName, &statBuf) != 0)
		throw new Exception(S.e + "Cannot find " + execName);

	if (uid == 0)
		return;

	if (statBuf.st_uid != 0
		|| (statBuf.st_mode & S_ISUID) == 0)
		throw (new Exception("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-root</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 Exception("Cannot run %s as root.", execName))
			->addHelp("Try one of\n<ul>\n"
				"<li>set <b>group=%s</b> in %s-root</li>\n"
				"<li>set <b>user=root</b> in %s-root</li></ul>",
				toGroup(statBuf.st_gid), fileName,
				fileName);
}

	String *Config::createCheckbox(Device &device, const char *medium) {
		String str = device.id;
		str = str + "." + medium;
		String checked = "";
		try {
			if (getEditorBoolean(str))
				checked = " checked=\"checked\"";
		}
		catch (Exception *e) {
			delete e;
		}
		String input = "";
		input = input
			+ "<input type=\"hidden\""
				+ " name=\"" + str + "\""
				+ " value=\"off\">"
			+ "<input type=\"checkbox\""
				+ " name=\"" + str + "\""
				+ " value=\"on\""
				+ checked + ">\n";
		return new String(input);
	}

	String *Config::createSpeedbox(Device &device, const char *medium) {
		String str = device.id;
		str = str + "." + medium;
		String checked = "";
		int speed = 0;
		try {
			int index = indexOf(str.getBytes());
			Object &object = items[index]->config;
			if (!object.instanceof("Integer"))
				throw new Exception("Integer expected");
			Integer *i = (Integer *)&object;
			speed = i->intValue();
		}
		catch (Exception *e) {
			delete e;
		}
		String select = "";
		select = select
			+ "<select name=\"" + str + "\" size=\"1\">";
		select = select
			+ "<option value=\"0\">-</option>";
		for (int i = 1; i <= 64; i++) {
			if ((i > 2 && i % 2 > 0)
					|| (i > 16 && i % 4 > 0)
					|| (i > 32 && str.endsWith(".DVD"))
					|| (i > 8 && str.indexOf(".DVD-") >= 0)
					|| (i > 8 && str.indexOf(".DVD+") >= 0))
				continue;
			select = select
				+ "<option";
			if (i == speed)
				select = select
					+ " selected";
			select = select
				+ " value=\"" + i + "\">" + i + "x</option>";
		}
		select = select
			+ "</select>\n";
		return new String(select);
	}

void Config::evaluate(
		int lineNo, const char *str1, const char *str2) {
	if (strcasecmp(str1, "cdrdaoPath") == 0
			|| strcasecmp(str1, "cdrecordPath") == 0
			|| strcasecmp(str1, "maxSpeed") == 0
			|| strcasecmp(str1, "maxVerifySpeed") == 0
			|| strcasecmp(str1, "mkisofsPath") == 0)
		return;

	if (strcasecmp(str1, "maxOpenSessions") == 0
			&& strcasecmp(str2, "auto") == 0)
		str2 = "3";

	int index = search(str1);
	if (index >= 0) {
		if (items[index]->lineInConfig >= 0)
			throw new Exception(S.e
				+ str1 + " already set in line "
				+ items[index]->lineInConfig);
		items[index]->setConfig(str2);
		items[index]->lineInConfig = lineNo;
	}
	else
	if (strstr(str1, "device.") == str1)
		add(str1, Integer::getInt(str2));
	else
/*	if (strcasecmp(str1, "accounts") == 0)
		accounts = toBoolean(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);
//		throw new Exception(S.e + "CDwriters");
		log.put(2, "CDwriters line found");
	else
	if (strcasecmp(str1, "CDROM") == 0
		|| strcasecmp(str1, "CDROMs") == 0)
//		parseCDROMs(str2, CDROMs, numOfReaders);
		log.put(2, "CDROMs line found");
	else
	if (strcasecmp(str1, "defaultFilesDir") == 0)
		defaultFilesDir = strDup(str2);
	else
	if (strcasecmp(str1, "defaultSpeed") == 0)
		defaultSpeed = toInteger(str2);
	else
	if (strcasecmp(str1, "eMailOnError") == 0)
		eMailOnError = strDup(str2);
	else
	if (strcasecmp(str1, "group") == 0) {
		if (!userSave) {
			gid = toGid(str2);
			group = strDup(str2);
			log.put(2, S.e + "group = " + group + ", gid = " + gid);
		}
	}
	else
	if (strcasecmp(str1, "homeDir") == 0) {
		homeDir = strDup(str2);
		setenv("HOME", homeDir, 1);
	}
	else
	if (strcasecmp(str1, "imageFile") == 0)
		throw new Exception("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, "logRequests") == 0) {
		if (toBoolean(str2) && getLogLevel() < 3)
			setConfig("logLevel", "3");
	}
	else
	if (strcasecmp(str1, "logSubProcess") == 0) {
		if (toBoolean(str2) && getLogLevel() < 5)
			setConfig("logLevel", "5");
	}
	else
	if (strcasecmp(str1, "logVerbose") == 0) {
		if (toBoolean(str2) && getLogLevel() < 4)
			setConfig("logLevel", "4");
	}
	else
	if (strcasecmp(str1, "maxCopies") == 0)
		maxCopies = toInteger(str2);
	else
	if (strcasecmp(str1, "maxStoreMBs") == 0)
		throw new Exception(
			": use maxMBytesInSpoolDir (see CDWserver/config.eg)");
	else
	if (strcasecmp(str1, "mkisofsArgs") == 0)
		mkisofsArgs = strDup(str2);
	else
	if (strcasecmp(str1, "organization") == 0)
		organization = strDup(str2);
	else
	if (strcasecmp(str1, "removeSessions") == 0)
		removeSessions = toBoolean(str2);
	else
	if (strcasecmp(str1, "statusPortNo") == 0)
		statusPortNo = toInteger(str2);
	else
	if (strcasecmp(str1, "user") == 0) {
		if (!userSave) {
			uid = toUid(str2);
			user = strDup(str2);
			log.put(2, S.e + "user = " + user + ", uid = " + uid);
		}
	}
	else
	if (strcasecmp(str1, "waitForCD") == 0)
		waitForCD = toInteger(str2);
	else
		throw new Exception("unknown symbol");
}

	String *Config::getRawDevice(
			int bus, int ID, int LUN, int no, const char *driver) {
		String path = "";
#ifdef FreeBSD
		path = path + "/dev/su" + bus + "-" + ID + "-" + LUN;
#else
		String str = driver;
		if (str.equals("ATAPI:")) {
			int count = 0;
			for (char ch = 'a'; ch <= 'z'; ch++) {
				String path2 = "";
				path2 = path2 + "/proc/ide/hd" + ch + "/media";
				FILE *in = fopen(path2.getBytes(), "r");
				if (in == NULL)
					continue;
				char buf[1024];
				int n = fread(buf, 1, sizeof buf - 1, in);
				buf[n] = 0;
				fclose(in);
				String str = buf;
				if (str.startsWith("cdrom"))
					count++;
				if (count == no + 1) {
					path = path + "/dev/hd" + ch;
					return new String(path);
				}
			}
		}
		path = path + "/dev/scd" + no;
		File f = new File(path);
		if (!f.exists()) {
			String path2 = "";
			path2 = path2 + "/dev/sr" + no;
			f = new File(path2);
			if (f.exists())
				path = path2;
		}
#endif
		return new String(path);
	}

	String *Config::getWritersForm() {
		String td = "<td bgcolor=\"<i18n>colorTableCellBackground</i18n>\">";
		String TD = "</td>";
		String str = "";

		for (int i = 0; i < getNumOfWriters(); i++) {
			str = str
				+ "<tr>"
				+ td + (i + 1) + TD
//				+ td + "<input type=\"text\" value=\""
//					+ writers[i].dev + "\">" + TD
//				+ td + writers[i].rawDevice + TD
				+ td + getWriterInfo(i) + TD
				+ td + createSpeedbox(writers[i], "CD") + TD
				+ td + createSpeedbox(writers[i], "CD-R") + TD
				+ td + createSpeedbox(writers[i], "CD-RW") + TD
				+ td + createSpeedbox(writers[i], "DVD") + TD
				+ td + createSpeedbox(writers[i], "DVD-R") + TD
				+ td + createSpeedbox(writers[i], "DVD-RW") + TD
				+ td + createSpeedbox(writers[i], "DVD+R") + TD
				+ td + createSpeedbox(writers[i], "DVD+RW") + TD
				+ "</tr>";
		}

		return new String(str);
	}

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::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 Exception(S.e
			+ "Cannot open config file " + fileName);

	log.put(2, S.e + "Reading config file " + fileName + "...");
	resetItems();
	numOfWriters = 0;
	readConfigFile(inStream);
	fclose(inStream);

	String name = fileName;
	name = name + "-root";
	inStream = fopen(name.getBytes(), "r");
	if (inStream != NULL) {
		log.put(2, S.e + "Reading root config file " + name + "...");
		userSave = false;
		try {
			readConfigFile(inStream);
		}
		catch (Exception *e) {
			userSave = true;
			throw e;
		}
		userSave = true;
		fclose(inStream);
	}

	activateConfig();
}

void Config::readConfigFile(FILE *inStream) {
	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(lineNo, str1, str2);
		}
		catch (Exception *error) {
			if (firstErrorPriority < 100) {
				firstError = firstError
					+ "Error in " + fileName
					+ ": line " + lineNo
					+ ": \"" + str1 + "=" + str2 + "\": "
					+ error->toString();
				firstErrorPriority = 100;
				log.put(1, S.e + firstError);
			}
			delete error;
		}
	}
}

	String *Config::readPassword() {
		File file = new File(getConfigDir(), PASSWORD);
		FILE *in = fopen(file.getPath().getBytes(), "r");
		if (in == NULL)
			return new String("changeit");

		StringBuffer sb = new StringBuffer(20);
		while (true) {
			int ch = fgetc(in);
			if (ch == EOF || ch < 32)
				break;
			sb.append((char)ch);
		}

		fclose(in);
		return new String(sb);
	}

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

	try {
		for (int i = 0; i < numOfItems; i++)
			log.put(8, S.e + "<" + items[i]->key
				+ "> <" + items[i]->active.toString() + ">");
	}
	catch (Exception *e) {
		log.put(2, S.e + e->getMessage());
		delete e;
	}

	if (firstErrorPriority >= 100
			|| firstError.indexOf("pending") >= 0) {
		firstError = "";
		firstErrorHelp = "";
		firstErrorPriority = 0;
	}

	try {
		readConfigFile();

		if (uid != geteuid() || gid != getegid())
			throw new Exception(S.e + "Cannot set user \""
				+ user + "\" and group \"" + group + "\"",
				S.e + "Restart " + product + " as root by"
				+ " [/etc/init.d/CDWserver restart]");

		if (accounts && !File(homeDir).exists())
			throw new Exception(S.e
				+ "homeDir " + homeDir + " does not exist");

		String logDir = getActiveValue("logDir");
		if (!File(logDir).exists())
			throw new Exception(S.e
				+ "logDir " + logDir + " does not exist");

		String spoolDir = getActiveValue("spoolDir");
		if (!File(spoolDir).exists())
			throw new Exception(S.e
				+ "spoolDir " + spoolDir + " does not exist");

		if (getActiveBoolean("cdrdao"))
			preparePath("cdrdao", PATH_COPY | PATH_DUMMY);

		preparePath("cdrecord", PATH_COPY | PATH_DUMMY);

		if (config.getActiveBoolean(DVDSUPPORT)) {
			preparePath("DVDplusRW_format", PATH_DUMMY);
			preparePath("growisofs", PATH_DUMMY);
			testGrowisofs();
		}

		preparePath("mkisofs", PATH_COPY);

		preparePath("mpg123");
		preparePath("ogg123");
		preparePath("setScheduler", PATH_ROOT);
		preparePath("sox");

		if (getActiveBoolean("verify"))
			preparePath("verify", PATH_DUMMY | PATH_ROOT);

		activateConfig();

/*		if (uid != 0)
		if (File(configDir).isReadAndWritable(uid, gid, 0) != 0)
			throw (new Exception("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 Exception("accountsFile %s "
			"is not read and writable by user %s / group %s.",
				accountsFile, 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 Exception("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 Exception("logDir %s or a file below it "
			"is not read and writable by user %s / group %s.",
				logDir.getBytes(), 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.getBytes());

		if (uid != 0)
		if (File(spoolDir).checkOwner(uid) != 0)
			throw (new Exception("spoolDir %s or a file below it "
				"is not owned by user %s.",
				spoolDir.getBytes(), 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.getBytes());

/*		if (!config.getImageOnTheFly()) {
			FILE *outStream = fopen(imageFile, "w");
			if (outStream == NULL)
				throw new Exception("can't create imageFile %s",
					imageFile);
			else
				fclose(outStream);
		}
*/
		testMkisofs();
		cdrecordVersion();
		cdrecordHelp();
		cdrecordScanbus();
		for (int i = 0; i < numOfWriters; i++)
			cdrecordInq(i);
		checkRawDevices();

		if (getActiveBoolean("audioCDSupport")) {
			if (getActiveBoolean("MP3decoding"))
				testMpg123();
			if (getActiveBoolean("oggDecoding")) {
				testOgg123();
				if (ogg123Info.indexOf("1.0rc") >= 0)
					throw new Exception(S.e + "ogg123 " + ogg123Info
						+ " does not work with webCDwriter. Get 1.0!",
						S.e + "/config/error/ogg123.html");
			}
			testSox();
		}

		activateConfig();
		resetEditor();
	}
	catch (Exception *error) {
		if (firstErrorPriority < 100) {
			firstError = error->toString();
			firstErrorHelp = error->getHelp();
			firstErrorPriority = 100;
		}
		delete error;
	}
}

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::testGrowisofs() {
		String helpURL = "/config/error/dvd+rw-tools.html";

		Process growisofs(getActiveBinPath("growisofs"));
		growisofs.addArg("-version");
		growisofs.start();
		const char *lineStr = growisofs.readLine();
		if (lineStr == NULL)
			throw new Exception(S.e + "Cannot run growisofs ("
				+ getActiveValue("growisofsPath") + ")",
				S.e + helpURL);

		String line = lineStr;
		int a = 0, b = -1;
		if (line.startsWith("growisofs "))
			growisofsInfo = line.substring(10);
		else
		if (line.indexOf("growisofs by") >= 0) {
			int p = line.indexOf("version ");
			if (p >= 0)
				growisofsInfo = line.substring(p + 8);
		}

		sscanf(growisofsInfo.getBytes(), "%d.%d", &a, &b);

		if (b < 0)
			throw new Exception(S.e
				+ "Unexpected output of \"growisofs -version\"",
				S.e + helpURL);

		if (a < 5 || b < 2)
			throw new Exception(S.e + "growisofs " + a + "." + b
				+ " is to old",
				S.e + helpURL);

		int comma = growisofsInfo.indexOf(",");
		if (comma > 0)
			growisofsInfo = growisofsInfo.substring(0, comma);
	}

void Config::testMkisofs() {
	Process p(getActiveValue("mkisofsPath").getBytes());
	p.addArg("-version");
	p.start();
	const char *lineStr = p.readLine();
	if (lineStr == NULL)
		throw new Exception("Cannot run mkisofs");

	do {
		if (strstr(lineStr, ".mkisofsrc") == NULL)
			break;
		lineStr = p.readLine();
	} while (lineStr != NULL);

	if (p.getResult() != 0)
		throw new Exception("Error running mkisofs -version");
	char str1[strlen(lineStr) + 1], str2[strlen(lineStr) + 1];
	if (sscanf(lineStr, "%s %s", str1, str2) != 2)
		throw new Exception("Cannot get the 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(config.getActiveValue("mpg123Path"));
	mpg123.addArg("--longhelp");
	mpg123.start();
	const char *lineStr = mpg123.readLine();
	if (lineStr == NULL)
		throw new Exception(S.e + "Cannot run mpg123 ("
			+ config.getActiveValue("mpg123Path") + ")",
				S.e + "/config/error/mpg123.html");
	do {
		char str[strlen(lineStr) + 1];
		if (sscanf(lineStr, "Version %s", str) == 1)
			mpg123Info = str;
		lineStr = mpg123.readLine();
	} while (lineStr != NULL);
}

	void Config::testOgg123() {
		Process ogg123(getActiveValue("ogg123Path"));
		ogg123.addArg("--version");
		ogg123.start();
		const char *lineStr = ogg123.readLine();
		if (lineStr == NULL)
			throw new Exception(S.e + "Cannot run ogg123 ("
				+ getActiveValue("ogg123Path") + ")",
				S.e + "/config/error/ogg123.html");
		do {
			char str1[strlen(lineStr) + 1], str2[strlen(lineStr) + 1];
			if (sscanf(lineStr, "%s %[^:]", str1, str2) == 2)
				ogg123Info = str2;
			lineStr = ogg123.readLine();
		} while (lineStr != NULL);
	}

	void Config::testSox() {
		Process sox(getActiveValue("soxPath"));
		sox.addArg("-h");
		sox.start();
		const char *lineStr = sox.readLine();
		if (lineStr == NULL)
			throw new Exception(S.e + "Cannot run sox ("
				+ getActiveValue("soxPath") + ")",
				S.e + "/config/error/sox.html");
		do {
			char str1[strlen(lineStr) + 1], str2[strlen(lineStr) + 1];
			if (sscanf(lineStr, "%[^:]: Version %s", str1, str2) == 2)
				soxInfo = str2;
			lineStr = sox.readLine();
		} while (lineStr != NULL);
	}

String *getTabItem(const char *file,
		String &key, int inCol,
		int getCol) {
	FILE *in = fopen(file, "r");
	if (in == NULL)
		return new String();

	String value = "";
	while (true) {
		char line[1024];
		if (fgets(line, sizeof line, in) == NULL)
			break;
		String str = line;

		int p0 = 0;
		for (int i = 0; i < inCol && p0 < str.length(); p0++)
			if (str.charAt(p0) == ':')
				i++;

		int p1 = p0 + 1;
		for (; p1 < str.length(); p1++)
			if (str.charAt(p1) == ':')
				break;

		String item = str.substring(p0, p1);
		if (!item.equals(key))
			continue;

		p0 = 0;
		for (int i = 0; i < getCol && p0 < str.length(); p0++)
			if (str.charAt(p0) == ':')
				i++;

		p1 = p0 + 1;
		for (; p1 < str.length(); p1++)
			if (str.charAt(p1) == ':')
				break;

		value = str.substring(p0, p1);
//		printf("<%s><%s>\n", item.getBytes(), value.getBytes());
		break;
	}

	fclose(in);
	return new String(value);
}

int Config::toGid(const char *groupName) {
	String name = groupName;
	String id = getTabItem("/etc/group", name, 0, 2);
	if (id.length() == 0)
		throw new Exception(S.e + "Unknown group: " + name);
	return Integer::getInt(id.getBytes());
}

String *Config::toGroup(int gid) {
	String id = String::valueOf(gid);
	String name = getTabItem("/etc/group", id, 2, 0);
	if (name.length() == 0)
		return new String(id);
	else
		return new String(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";
}

int Config::toUid(const char *userName) {
	String name = userName;
	String id = getTabItem("/etc/passwd", name, 0, 2);
	if (id.length() == 0)
		throw new Exception(S.e + "Unknown user: " + name);
	return Integer::getInt(id.getBytes());
}

String *Config::toUser(int uid) {
	String id = String::valueOf(uid);
	String name = getTabItem("/etc/passwd", id, 2, 0);
	if (name.length() == 0)
		return new String(id);
	else
		return new String(name);
}

	void Config::updateConfigFile() {
		FILE *in = fopen(fileName, "r");
		if (in == NULL)
			throw new Exception(S.e + "Cannot open " + fileName);
		StringBuffer sb = new StringBuffer(10000);
		while (true) {
			int ch = fgetc(in);
			if (ch == EOF)
				break;
			sb.append((char)ch);
		}
		fclose(in);

		if (sb.length() > 0 && sb.charAt(sb.length() - 1) != '\n')
			sb.append('\n');

		for (int i = 0; i < numOfItems; i++) {
			if (items[i]->key.equals("gracetime")
					|| items[i]->key.equals(PASSWORD))
				continue;
			int done = false;
			for (int p0 = 0; ; p0++) {
				p0 = sb.indexOf(items[i]->key.getBytes(), p0);
				if (p0 < 0)
					break;
				if (p0 > 0 && sb.charAt(p0 - 1) != '\n')
					continue;
				int p1 = sb.indexOf("\n", p0);
				if (p1 < 0)
					p1 = sb.length();
				sb.delete_(p0, p1);
				String str = createConfigLine(i);
				if (str.startsWith("device.")) {
					if (sb.length() > p0 && sb.charAt(p0) == '\n')
						sb.delete_(p0, p0 + 1);
					if (!str.endsWith("=0") || str.endsWith(".CD=0")) {
						str = str + "\n";
						sb.append(str.getBytes());
					}
				}
				else
					sb.insert(p0, str.getBytes());
				done = true;
				break;
			}
			if (done || items[i]->isDefault())
				continue;
			String key = "#";
			key = key + items[i]->key;
			for (int p0 = 0; ; p0++) {
				p0 = sb.indexOf(key.getBytes(), p0);
				if (p0 < 0)
					break;
				if (p0 > 0 && sb.charAt(p0 - 1) != '\n')
					continue;
				int p1 = sb.indexOf("\n", p0);
				if (p1 < 0)
					p1 = sb.length();
				sb.delete_(p0, p1);
				String str = createConfigLine(i);
				sb.insert(p0, str.getBytes());
				done = true;
				break;
			}
			if (done)
				continue;
			String str = createConfigLine(i);
			str = str + "\n";
			sb.append(str.getBytes());
		}

		FILE *out = fopen(fileName, "w");
		if (out == NULL)
			throw new Exception(S.e + "Cannot write " + fileName);
		String str = new String(sb);
		fwrite(str.getBytes(), 1, str.length(), out);
		fclose(out);
	}

	void Config::updateEditor(String &str) {
		log.put(5, S.e + str);

		bool newPasswordMode = false;
		String newPassword1 = "", newPassword2 = "";

		int i = 0;
		while (true) {
			while (i < str.length()
					&& (str.charAt(i) == '\n'
						|| str.charAt(i) == ' '))
				i++;
			if (i == str.length())
				break;

			int p0 = i;
			while (i < str.length()
					&& str.charAt(i) != '\n'
					&& str.charAt(i) != ' '
					&& str.charAt(i) != '=')
				i++;
			if (i == p0)
				continue;
			String key = str.substring(p0, i);

			while (i < str.length()
					&& str.charAt(i) == ' ')
				i++;
			if (i == str.length()
					|| str.charAt(i) != '=')
				continue;
			i++;

			p0 = i;
			while (i < str.length()
					&& str.charAt(i) != '\n')
				i++;
			String value = str.substring(p0, i);

			if (key.equals("newPassword")) {
				newPasswordMode = true;
				newPassword1 = value;
				continue;
			}

			if (key.equals("newPassword2")) {
				newPasswordMode = true;
				newPassword2 = value;
				continue;
			}

			int index = search(key.getBytes());
			if (index < 0)
				continue;
			items[index]->setEditor(value);
		}

		String passwdKey = PASSWORD;
		String password = getEditorValue(passwdKey);
		log.debug(PASSWORD, S.e + "input = <" + password + ">");
		String password0 = readPassword();
		log.debug(PASSWORD, S.e + "file = <" + password0 + ">");
		if (!password.equals(password0)) {
			setError(PASSWORD,
				"<font color=\"#FF0000\">Invalid password</font>");
			throw new Exception(S.e + "Invalid password");
		}

		if (newPasswordMode)
			if (newPassword1.equals(newPassword2)) {
				writePassword(newPassword1);
				setEditor(PASSWORD, newPassword1);
			}
			else {
				setError(PASSWORD,
					"<font color=\"#FF0000\">Do not match</font>");
				throw new Exception(S.e + "Do not match");
			}

		updateConfig();
		updateConfigFile();
		setWarning(95, S.e + "Reload config pending",
			"Close all connections to CDWserver!");
	}

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

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

	write("# 3. Directories");
	write("exportDir", getActiveValue("exportDir").getBytes());
	write("homeDir", homeDir);
	write("logDir", getLogDir());
	write("spoolDir", getSpoolDir());

	write("# 4. Cache");
	write("maxMBytesInSpoolDir", getMaxMBytesInSpoolDir());
	write("maxMBytesPerSession", getMaxMBytesPerSession());
	write("maxOpenSessions", getMaxOpenSessions());
	write("reservedMBytes", getReservedMBytes());

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

	write("# 6. Programs");
	write("cdrdaoPath", getCdrdaoPath());
	write("cdrecordPath", getCdrecordPath());
	if (config.getActiveBoolean(DVDSUPPORT)) {
		write("DVDplusRW_formatPath",
			getActiveBinPath("DVDplusRW_format"));
		write("growisofsPath",
			getActiveBinPath("growisofs"));
	}
	write("mkisofsPath", getActiveValue("mkisofsPath").getBytes());
	write("mpg123Path", getActiveValue("mpg123Path").getBytes());
	write("ogg123Path", getActiveValue("ogg123Path").getBytes());
	write("soxPath", getActiveValue("soxPath").getBytes());

	write("# 7. Options");
	write("anonymousUse", toString(getAnonymousUse()));
	write("cdrecordArgs", cdrecordArgs);
	write("#debug", "Process,Server::noDisc()");
	write("#defaultFilesDir", "/etc/CDWserver/default_files");
	write("ejectOnDone", toString(getEjectOnDone()));
	write("ejectOnReservation", toString(getEjectOnReservation()));
	if (eMailOnError == NULL)
		write("#eMailOnError", "unknown@unknown.org");
	else
		write("eMailOnError", eMailOnError);
	write("logLevel", getLogLevel());
	write("logoURL", getLogoURL().getBytes());
	write("maxCopies", maxCopies);
	write("organization", organization);
	write("removeSessions", toString(removeSessions));
	write("showUsers", toString(getShowUsers()));
	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 Exception(S.e + "Cannot create configFile " + 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);
	chown(fileName, uid, gid);
}

void Config::writeInfoFile() {
	FILE *out = fopen(infoFile, "w");
	if (out == NULL)
		throw new Exception(S.e + "Cannot create " + infoFile);

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

	void Config::writePassword(String &password) {
		File file = new File(getConfigDir(), PASSWORD);
		int out = creat(file.getPath().getBytes(), S_IRUSR | S_IWUSR);
		int res;
		if (out < 0)
			res = -1;
		else {
			res = ::write(out, password.getBytes(), password.length());
			close(out);
			chown(file.getPath().getBytes(), uid, gid);
		}
		if (res < 0)
			throw new Exception(S.e + "Cannot write password to "
				+ file.getPath());
	}

void Config::writeWaitForCDMsgFile() {
	FILE *out = fopen(waitForCDMsgFile, "w");
	if (out == NULL)
		throw new Exception(S.e + "Cannot create " + 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);
}
