    /*

    Create a suitable configuration for kdm taking old xdm/kdm 
    installations into account

    Copyright (C) 2001-2003 Oswald Buddenhagen <ossi@kde.org>


    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; either version 2 of the License, or
    (at your option) any later version.

    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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

    */

#include <X11/Xlib.h>
#include <X11/Xresource.h>

#include <config.h>

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <utime.h>
#include <dirent.h>
#include <errno.h>
#include <pwd.h>
#include <time.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/param.h>
#ifdef BSD
# include <utmp.h>
#endif

#include "config.ci"

#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4)
# define ATTR_UNUSED __attribute__((unused))
#else
# define ATTR_UNUSED
#endif

#if defined(__sun) && !defined(__sun__)
# define __sun__
#endif

#define as(ar) ((int)(sizeof(ar)/sizeof(ar[0])))

#define __stringify(x) #x
#define stringify(x) __stringify(x)

#define RCVERSTR stringify(RCVERMAJOR) "." stringify(RCVERMINOR)

static int old_scripts, no_old_scripts, old_confs, no_old,
    no_backup, no_in_notice, use_destdir, mixed_scripts;
static const char *newdir = KDMCONF, *facesrc = KDMDATA "/pics/users",
    *oldxdm, *oldkde;

static int oldver;


typedef struct StrList {
    struct StrList	*next;
    const char		*str;
} StrList;


static void *
mmalloc (size_t sz)
{
    void *ptr;

    if (!(ptr = malloc (sz))) {
	fprintf (stderr, "Out of memory\n");
	exit (1);
    }
    return ptr;
}

static void *
mcalloc (size_t sz)
{
    void *ptr;

    if (!(ptr = calloc (1, sz))) {
	fprintf (stderr, "Out of memory\n");
	exit (1);
    }
    return ptr;
}

static void *
mrealloc (void *optr, size_t sz)
{
    void *ptr;

    if (!(ptr = realloc (optr, sz))) {
	fprintf (stderr, "Out of memory\n");
	exit (1);
    }
    return ptr;
}

static char *
mstrdup (const char *optr)
{
    char *ptr;

    if (!optr)
	return 0;
    if (!(ptr = strdup (optr))) {
	fprintf (stderr, "Out of memory\n");
	exit (1);
    }
    return ptr;
}


#define NO_LOGGER
#define STATIC static
#include <printf.c>

typedef struct {
    char *buf;
    int clen, blen, tlen;
} OCABuf;

static void
OutCh_OCA (void *bp, char c)
{
    OCABuf *ocabp = (OCABuf *)bp;

    ocabp->tlen++;
    if (ocabp->clen >= ocabp->blen) {
	ocabp->blen = ocabp->blen * 3 / 2 + 100;
	ocabp->buf = mrealloc (ocabp->buf, ocabp->blen);
    }
    ocabp->buf[ocabp->clen++] = c;
}

static int 
VASPrintf (char **strp, const char *fmt, va_list args)
{
    OCABuf ocab = { 0, 0, 0, -1 };

    DoPr(OutCh_OCA, &ocab, fmt, args);
    OutCh_OCA(&ocab, 0);
    *strp = realloc (ocab.buf, ocab.clen);
    if (!*strp)
	*strp = ocab.buf;
    return ocab.tlen;
}

static int 
ASPrintf (char **strp, const char *fmt, ...)
{
    va_list args;
    int len;

    va_start(args, fmt);
    len = VASPrintf (strp, fmt, args);
    va_end(args);
    return len;
}

static void
StrCat (char **strp, const char *fmt, ...)
{
    char *str, *tstr;
    va_list args;
    int el;

    va_start(args, fmt);
    el = VASPrintf (&str, fmt, args);
    va_end(args);
    if (*strp) {
	int ol = strlen (*strp);
	tstr = mmalloc (el + ol + 1);
	memcpy (tstr, *strp, ol);
	memcpy (tstr + ol, str, el + 1);
	free (*strp);
	free (str);
	*strp = tstr;
    } else
	*strp = str;
}


#define WANT_CLOSE 1

typedef struct File {
	char *buf, *eof;
#if defined(HAVE_MMAP) && defined(WANT_CLOSE)
	int ismapped;
#endif
} File;

static int
readFile (File *file, const char *fn)
{
    off_t flen;
    int fd;

    if ((fd = open (fn, O_RDONLY)) < 0)
	return 0;

    flen = lseek (fd, 0, SEEK_END);
#ifdef HAVE_MMAP
# ifdef WANT_CLOSE
    file->ismapped = 0;
# endif
    file->buf = mmap(0, flen + 1, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
# ifdef WANT_CLOSE
    if (file->buf)
	file->ismapped = 1;
    else
# else
    if (!file->buf)
# endif
#endif
    {
	file->buf = mmalloc (flen + 1);
	lseek (fd, 0, SEEK_SET);
	if (read (fd, file->buf, flen) != flen) {
	    free (file->buf);
	    close (fd);
	    fprintf (stderr, "Cannot read file\n");
	    return 0;	/* maybe better abort? */
	}
    }
    file->eof = file->buf + flen;
    close (fd);
    return 1;
}

#ifdef WANT_CLOSE
static void
freeBuf (File *file)
{
# ifdef HAVE_MMAP
    if (file->ismapped)
	munmap(file->buf, file->eof - file->buf);
    else
# endif
	free (file->buf);
}
#endif

static int
mkdirp (const char *name, int mode, const char *what, int existok)
{
    char *mfname = mstrdup (name);
    int i;
    struct stat st;

    for (i = 1; mfname[i]; i++)
	if (mfname[i] == '/') {
	    mfname[i] = 0;
	    if (stat (mfname, &st)) {
		if (mkdir (mfname, 0755)) {
		    fprintf (stderr, "Cannot create parent %s of %s directory %s: %s\n",
			     mfname, what, name, strerror (errno));
		    free (mfname);
		    return 0;
		}
		chmod (mfname, 0755);
	    }
	    mfname[i] = '/';
	}
    free (mfname);
    if (stat (name, &st)) {
	if (mkdir (name, mode)) {
	    fprintf (stderr, "Cannot create %s directory %s: %s\n",
		     what, name, strerror (errno));
	    return 0;
	}
	chmod (name, mode);
	return 1;
    }
    return existok;
}


static void
displace (const char *fn)
{
    if (!no_backup) {
	char bn[PATH_MAX + 4];
	sprintf(bn, "%s.bak", fn); /* won't overflow if only existing paths are passed */
	rename(fn, bn);
    } else
	unlink(fn);
}


/*
 * target data to be written to kdmrc
 */

typedef struct Entry {
	struct Entry	*next;
	struct Ent	*spec;
	const char	*value;
	int		active:1;
	int		written:1;
} Entry;

typedef struct Section {
	struct Section	*next;
	struct Sect	*spec;
	const char	*name;
	const char	*comment;
	Entry		*ents;
} Section;

static Section *config; /* the kdmrc data to be written */

/*
 * Specification of the (currently possible) kdmrc entries
 */

typedef struct Ent {
	const char	*key;
	int		prio;
	void (*func)(Entry *ce, Section *cs);
	const char	*comment;
} Ent;

typedef struct Sect {
	const char	*name;
	Ent		*ents;
	int		nents;
} Sect;

static Sect *findSect (const char *name);
static Ent *findEnt (Sect *sect, const char *key);

/*
 * Functions to manipulate the current kdmrc data
 */

static const char *
getfqval (const char *sect, const char *key, const char *defval)
{
    Section *cs;
    Entry *ce;

    for (cs = config; cs; cs = cs->next)
	if (!strcmp (cs->name, sect)) {
	    for (ce = cs->ents; ce; ce = ce->next)
		if (!strcmp (ce->spec->key, key)) {
		    if (ce->active && ce->written)
			return ce->value;
		    break;
		}
	    break;
	}
    return defval;
}

static void
putfqval (const char *sect, const char *key, const char *value)
{
    Section *cs, **csp;
    Entry *ce, **cep;

    if (!value)
	return;

    for (csp = &config; (cs = *csp); csp = &(cs->next))
	if (!strcmp(sect, cs->name))
	    goto havesec;
    cs = mcalloc (sizeof(*cs));
    ASPrintf ((char **)&cs->name, "%s", sect);
    cs->spec = findSect (sect);
    *csp = cs;
  havesec:

    for (cep = &(cs->ents); (ce = *cep); cep = &(ce->next))
	if (!strcmp(key, ce->spec->key))
	    goto haveent;
    ce = mcalloc (sizeof(*ce));
    ce->spec = findEnt (cs->spec, key);
    *cep = ce;
  haveent:
    ASPrintf ((char **)&ce->value, "%s", value);
    ce->written = ce->active = 1;
}

static const char *csect;

#define setsect(se) csect = se

static void
putval (const char *key, const char *value)
{
    putfqval(csect, key, value);
}


static void
wrconf (FILE *f)
{
    Section *cs;
    Entry *ce;
    StrList *sl = 0, *sp;
    const char *cmt;

    putfqval ("General", "ConfigVersion", RCVERSTR);
    for (cs = config; cs; cs = cs->next) {
	fprintf (f, "%s[%s]\n",
		 cs->comment ? cs->comment : "\n", cs->name);
	for (ce = cs->ents; ce; ce = ce->next) {
	    if (ce->spec->comment) {
		cmt = ce->spec->comment;
		for (sp = sl; sp; sp = sp->next)
		    if (sp->str == cmt) {
			cmt = "# See above\n";
			goto havit;
		    }
		if (!(sp = malloc (sizeof(*sp))))
		    fprintf (stderr, "Warning: Out of memory\n");
		else {
		    sp->str = cmt;
		    sp->next = sl; sl = sp;
		}
	    } else
		cmt = "";
	  havit:
	    fprintf (f, "%s%s%s=%s\n", 
		     cmt, ce->active ? "" : "#", ce->spec->key, ce->value);
	}
    }
}


/*
 * defaults
 */
#ifdef XDMCP
static const char def_xaccess[] = 
"# Xaccess - Access control file for XDMCP connections\n"
"#\n"
"# To control Direct and Broadcast access:\n"
"#\n"
"#	pattern\n"
"#\n"
"# To control Indirect queries:\n"
"#\n"
"# 	pattern		list of hostnames and/or macros ...\n"
"#\n"
"# To use the chooser:\n"
"#\n"
"#	pattern		CHOOSER BROADCAST\n"
"#\n"
"# or\n"
"#\n"
"#	pattern		CHOOSER list of hostnames and/or macros ...\n"
"#\n"
"# To define macros:\n"
"#\n"
"#       %name		list of hosts ...\n"
"#\n"
"# The first form tells xdm which displays to respond to itself.\n"
"# The second form tells xdm to forward indirect queries from hosts matching\n"
"# the specified pattern to the indicated list of hosts.\n"
"# The third form tells xdm to handle indirect queries using the chooser;\n"
"# the chooser is directed to send its own queries out via the broadcast\n"
"# address and display the results on the terminal.\n"
"# The fourth form is similar to the third, except instead of using the\n"
"# broadcast address, it sends DirectQuerys to each of the hosts in the list\n"
"#\n"
"# In all cases, xdm uses the first entry which matches the terminal;\n"
"# for IndirectQuery messages only entries with right hand sides can\n"
"# match, for Direct and Broadcast Query messages, only entries without\n"
"# right hand sides can match.\n"
"#\n"
"\n"
"*					#any host can get a login window\n"
"\n"
"#\n"
"# To hardwire a specific terminal to a specific host, you can\n"
"# leave the terminal sending indirect queries to this host, and\n"
"# use an entry of the form:\n"
"#\n"
"\n"
"#terminal-a	host-a\n"
"\n"
"\n"
"#\n"
"# The nicest way to run the chooser is to just ask it to broadcast\n"
"# requests to the network - that way new hosts show up automatically.\n"
"# Sometimes, however, the chooser can't figure out how to broadcast,\n"
"# so this may not work in all environments.\n"
"#\n"
"\n"
"*		CHOOSER BROADCAST	#any indirect host can get a chooser\n"
"\n"
"#\n"
"# If you'd prefer to configure the set of hosts each terminal sees,\n"
"# then just uncomment these lines (and comment the CHOOSER line above)\n"
"# and edit the %hostlist line as appropriate\n"
"#\n"
"\n"
"#%hostlist	host-a host-b\n"
"\n"
"#*		CHOOSER %hostlist	#\n";
#endif

/* XXX
#define XSERVERS_MAJOR 2
#define XSERVERS_MINOR 0
*/
#define XSERVERS_MAJOR 1
#define XSERVERS_MINOR 99

#define VERSION_WARNING "### Don't change these two lines; they are hints for genkdmconf. ###\n"
#define XSERVERS_VERSION VERSION_WARNING "### Version " stringify(XSERVERS_MAJOR) "." stringify(XSERVERS_MINOR) " ###\n"

static const char def_xservers[] = 
"# Xservers - local X-server list\n"
"#\n"
"# This file should contain an entry to start the server on the\n"
"# local display; if you have more than one display (not screen),\n"
"# you can add entries to the list (one per line).\n"
"# If you also have some X terminals connected which do not support XDMCP,\n"
"# you can add them here as well; you will want to leave those terminals\n"
"# on and connected to the network, else kdm will have a tougher time\n"
"# managing them. Each X terminal line should look like:\n"
"#       XTerminalName:0 foreign\n"
"#\n"
"\n" DEF_SERVER_LINES "\n"
"\n" XSERVERS_VERSION;

#ifdef XDMCP
static const char def_willing[] = 
"#! /bin/sh\n"
"# The output of this script is displayed in the chooser window\n"
"# (instead of \"Willing to manage\").\n"
"\n"
"load=`uptime|sed -e 's/^.*load[^0-9]*//'`\n"
"nrusers=`who|cut -c 1-8|sort -u|wc -l|sed 's/^[ \t]*//'`\n"
"s=\"\"; [ \"$nrusers\" != 1 ] && s=s\n"
"\n"
"echo \"${nrusers} user${s}, load: ${load}\"\n";
#endif

static const char def_setup[] = 
"#! /bin/sh\n"
"# Xsetup - run as root before the login dialog appears\n"
"\n"
"#xconsole -geometry 480x130-0-0 -notify -verbose -fn fixed -exitOnFail -file /dev/xconsole &\n";

static const char def_startup[] = 
"#! /bin/sh\n"
"# Xstartup - run as root before session starts\n"
"\n"
"# By convention, both xconsole and xterm -C check that the\n"
"# console is owned by the invoking user and is readable before attaching\n"
"# the console output.  This way a random user can invoke xterm -C without\n"
"# causing serious grief; still, it can cause havoc, so xconsole is started\n"
"# by Xsetup usually.\n"
"# This is not required if you use PAM with the pam_console module.\n"
"#\n"
"#chown $USER /dev/console\n"
"\n"
#ifdef _AIX
"# We create a pseudodevice for finger.  (host:0 becomes xdm/host_0)\n"
"# Without it, finger errors out with \"Can't stat /dev/host:0\".\n"
"#\n"
"if [ -f /usr/lib/X11/xdm/sessreg ]; then\n"
"  devname=`echo $DISPLAY | /usr/bin/sed -e 's/[:\\.]/_/g' | /usr/bin/cut -c1-8`\n"
"  hostname=`echo $DISPLAY | /usr/bin/cut -d':' -f1`\n"
"\n"
"  if [ -z \"$devname\" ]; then\n"
"    devname=\"unknown\"\n"
"  fi\n"
"  if [ ! -d /dev/xdm ]; then\n"
"    /usr/bin/mkdir /dev/xdm\n"
"    /usr/bin/chmod 755 /dev/xdm\n"
"  fi\n"
"  /usr/bin/touch /dev/xdm/$devname\n"
"  /usr/bin/chmod 644 /dev/xdm/$devname\n"
"\n"
"  if [ -z \"$hostname\" ]; then\n"
"    exec /usr/lib/X11/xdm/sessreg -a -l xdm/$devname $USER\n"
"  else\n"
"    exec /usr/lib/X11/xdm/sessreg -a -l xdm/$devname -h $hostname $USER\n"
"  fi\n"
"fi\n";
#else
"exec sessreg -a -l $DISPLAY"
# ifdef BSD
" -x " KDMCONF "/Xservers -u " _PATH_UTMP
# endif
" $USER\n";
#endif /* _AIX */

static const char def_reset[] = 
"#! /bin/sh\n"
"# Xreset - run as root after session exits\n"
"\n"
"# Reassign ownership of the console to root, this should disallow\n"
"# assignment of console output to any random users's xterm. See Xstartup.\n"
"#\n"
"#chown root /dev/console\n"
"#chmod 622 /dev/console\n"
"\n"
#ifdef _AIX
"if [ -f /usr/lib/X11/xdm/sessreg ]; then\n"
"  devname=`echo $DISPLAY | /usr/bin/sed -e 's/[:\\.]/_/g' | /usr/bin/cut -c1-8`\n"
"  exec /usr/lib/X11/xdm/sessreg -d -l xdm/$devname $USER\n"
"fi\n";
#else
"exec sessreg -d -l $DISPLAY"
# ifdef BSD
" -x " KDMCONF "/Xservers -u " _PATH_UTMP
# endif
" $USER\n";
#endif /* _AIX */

static const char def_session[] = 
"#! /bin/sh\n"
"# Xsession - run as user\n"
"\n"
"# Note that the respective logout scripts are not sourced.\n"
"case $SHELL in\n"
"  */bash)\n"
"    [ -z \"$BASH\" ] && exec $SHELL $0 \"$@\"\n"
"    set +o posix\n"
"    [ -f /etc/profile ] && . /etc/profile\n"
"    if [ -f $HOME/.bash_profile ]; then\n"
"      . $HOME/.bash_profile\n"
"    elif [ -f $HOME/.bash_login ]; then\n"
"      . $HOME/.bash_login\n"
"    elif [ -f $HOME/.profile ]; then\n"
"      . $HOME/.profile\n"
"    fi\n"
"    ;;\n"
"  */zsh)\n"
"    [ -z \"$ZSH_NAME\" ] && exec $SHELL $0 \"$@\"\n"
"    emulate -R zsh\n"
"    [ -d /etc/zsh ] && zdir=/etc/zsh || zdir=/etc\n"
"    zhome=${ZDOTDIR:-$HOME}\n"
"    # zshenv is always sourced automatically.\n"
"    [ -f $zdir/zprofile ] && . $zdir/zprofile\n"
"    [ -f $zhome/.zprofile ] && . $zhome/.zprofile\n"
"    [ -f $zdir/zlogin ] && . $zdir/zlogin\n"
"    [ -f $zhome/.zlogin ] && . $zhome/.zlogin\n"
"    ;;\n"
"  */csh|*/tcsh)\n"
"    # [t]cshrc is always sourced automatically.\n"
"    # Note that sourcing csh.login after .cshrc is non-standard.\n"
"    eval `$SHELL -c 'if (-f /etc/csh.login) source /etc/csh.login > /dev/null; if (-f ~/.login) source ~/.login > /dev/null; /bin/sh -c export'`\n"
"    ;;\n"
"  *) # Plain sh, ksh, and anything we don't know.\n"
"    [ -f /etc/profile ] && . /etc/profile\n"
"    [ -f $HOME/.profile ] && . $HOME/.profile\n"
"    ;;\n"
"esac\n"
"\n"
"[ -f /etc/xprofile ] && . /etc/xprofile\n"
"[ -f $HOME/.xprofile ] && . $HOME/.xprofile\n"
"\n"
"case $1 in\n"
"  \"\")\n"
"    exec xmessage -center -buttons OK:0 -default OK \"Sorry, $DESKTOP_SESSION is no valid session.\"\n"
"    ;;\n"
"  failsafe)\n"
"    exec xterm -geometry 80x24-0-0\n"
"    ;;\n"
"  custom)\n"
"    exec $HOME/.xsession\n"
"    ;;\n"
"  default)\n"
"    exec " KDE_BINDIR "/startkde\n"
"    ;;\n"
"  *)\n"
"    eval exec \"$1\"\n"
"    ;;\n"
"esac\n"
"exec xmessage -center -buttons OK:0 -default OK \"Sorry, cannot execute $1. Check $DESKTOP_SESSION.desktop.\"\n";

static const char def_background[] =
"[Desktop0]\n"
"BackgroundMode=Flat\n"
"BlendBalance=100\n"
"BlendMode=NoBlending\n"
"ChangeInterval=60\n"
"Color1=0,0,200\n"
"Color2=192,192,192\n"
"CurrentWallpaper=0\n"
"LastChange=0\n"
"MinOptimizationDepth=1\n"
"MultiWallpaperMode=NoMulti\n"
"Pattern=fish\n"
"Program=\n"
"ReverseBlending=false\n"
"UseSHM=false\n"
"Wallpaper=default_blue.jpg\n"
"WallpaperList=\n"
"WallpaperMode=Scaled\n";

static char *
prepName (const char *fn)
{
    const char *tname;
    char *nname;

    tname = strrchr (fn, '/');
    ASPrintf (&nname, "%s/%s", newdir, tname ? tname + 1 : fn);
    displace (nname);
    return nname;
}

static FILE *
Create (const char *fn, int mode)
{
    char *nname;
    FILE *f;

    nname = prepName (fn);
    if (!(f = fopen (nname, "w"))) {
	fprintf (stderr, "Cannot create %s\n", nname);
	exit (1);
    }
    chmod (nname, mode);
    free (nname);
    return f;
}

static void
WriteOut (const char *fn, int mode, time_t stamp, const char *buf, size_t len)
{
    char *nname;
    int fd;
    struct utimbuf utim;

    nname = prepName (fn);
    if ((fd = creat (nname, mode)) < 0) {
	fprintf (stderr, "Cannot create %s\n", nname);
	exit (1);
    }
    write (fd, buf, len);
    close (fd);
    if (stamp) {
	utim.actime = utim.modtime = stamp;
	utime (nname, &utim);
    }
    free (nname);
}


/* returns static array! */
static const char *
resect (const char *sec, const char *name)
{
    static char sname[64];
    char *p;

    if ((p = strrchr (sec, '-'))) {
	sprintf (sname, "%.*s-%s", p - sec, sec, name);
	return sname;
    } else
	return name;
}

static int
inNewDir (const char *name)
{
    return !memcmp (name, KDMCONF "/", sizeof(KDMCONF));
}

static int
inList (StrList *sp, const char *s)
{
    for (; sp; sp = sp->next)
	if (!strcmp (sp->str, s))
	    return 1;
    return 0;
}

static void 
addStr (StrList **sp, const char *s)
{
    for (; *sp; sp = &(*sp)->next)
	if (!strcmp ((*sp)->str, s))
	    return;
    *sp = mcalloc (sizeof(**sp));
    ASPrintf ((char **)&(*sp)->str, "%s", s);
}

StrList *aflist, *uflist, *eflist, *cflist, *lflist;

/* file is part of new config */
static void 
addedFile (const char *fn)
{
    addStr (&aflist, fn);
}

/* file from old config was parsed */
static void 
usedFile (const char *fn)
{
    addStr (&uflist, fn);
}

/* file from old config was copied with slight modifications */
static void 
editedFile (const char *fn)
{
    addStr (&eflist, fn);
}

/* file from old config was copied verbatim */
static void 
copiedFile (const char *fn)
{
    addStr (&cflist, fn);
}

/* file from old config is still being used */
static void 
linkedFile (const char *fn)
{
    addStr (&lflist, fn);
}

/*
 * XXX this stuff is highly borked. it does not handle collisions at all.
 */
static int
copyfile (Entry *ce, const char *tname, int mode, int (*proc)(File *, char **, int *))
{
    const char *tptr;
    char *nname;
    File file;
    int rt;

    if (!*ce->value)
	return 1;

    tptr = strrchr (tname, '/');
    ASPrintf (&nname, KDMCONF "/%s", tptr ? tptr + 1 : tname);
    if (inList (cflist, ce->value) ||
	inList (eflist, ce->value) || 
	inList (lflist, ce->value))
    {
	rt = 1;
	goto doret;
    }
    if (!readFile (&file, ce->value)) {
	fprintf (stderr, "Warning: cannot copy file %s\n", ce->value);
	rt = 0;
    } else {
	char *nbuf;
	int nlen;

	if (!proc || !proc (&file, &nbuf, &nlen) ||
	    (nlen == file.eof - file.buf && !memcmp (nbuf, file.buf, nlen)))
	{
	    if (!use_destdir && !strcmp (ce->value, nname))
		linkedFile (nname);
	    else {
		struct stat st;
		stat (ce->value, &st);
		WriteOut (nname, mode, st.st_mtime, file.buf, file.eof - file.buf);
		copiedFile (ce->value);
	    }
	} else {
	    WriteOut (nname, mode, 0, nbuf, nlen);
	    editedFile (ce->value);
	}
	if (strcmp (ce->value, nname) && inNewDir (ce->value) && !use_destdir)
	    displace (ce->value);
	addedFile (nname);
	rt = 1;
    }
  doret:
    ce->value = nname;
    return rt;
}

static void
dlinkfile (const char *name)
{
    File file;

    if (!readFile (&file, name)) {
	fprintf (stderr, "Warning: cannot read file %s\n", name);
	return;
    }
    if (inNewDir (name) && use_destdir) {
	struct stat st;
	stat (name, &st);
	WriteOut (name, st.st_mode, st.st_mtime, file.buf, file.eof - file.buf);
	copiedFile (name);
    } else
	linkedFile (name);
    addedFile (name);
}

static void
linkfile (Entry *ce)
{
    if (ce->written && *ce->value)
	dlinkfile (ce->value);
}

static void
writefile (const char *tname, int mode, const char *cont)
{
    WriteOut (tname, mode, 0, cont, strlen (cont));
    addedFile (tname);
}


char *background;

static void 
handBgCfg (Entry *ce, Section *cs ATTR_UNUSED)
{
    if (!ce->active)	/* can be only the X-*-Greeter one */
	writefile (def_BackgroundCfg, 0644,
		   background ? background : def_background);
#if 0 /* risk of kcontrol clobbering the original file */
    else if (old_confs)
	linkfile (ce);
#endif
    else {
	if (!copyfile (ce, ce->value, 0644, 0)) {
	    if (!strcmp (cs->name, "X-*-Greeter"))
		writefile (def_BackgroundCfg, 0644, def_background);
	    ce->active = 0;
	}
    }
}


/* TODO: handle solaris' local_uid specs */

#ifdef HAVE_VTS
# define RDPYS
#endif

#if defined(__linux__) || defined(__sun__)
# define CONS
#endif

#if defined(RDPYS) || defined(CONS)

# define dLocal		0
# define dForeign	1

static struct displayMatch {
	const char	*name;
	int		len, type;
} displayTypes[] = {
	{ "local", 5,	dLocal },
	{ "foreign", 7,	dForeign },
};

static int
parseDisplayType (const char *string
#ifdef CONS
		  , const char **atPos
#endif
	)
{
    struct displayMatch	*d;

#ifdef CONS
    *atPos = 0;
#endif
    for (d = displayTypes; d < displayTypes + as(displayTypes); d++) {
#ifdef CONS
	if (!memcmp (d->name, string, d->len) &&
	    (!string[d->len] || string[d->len] == '@')) {
	    if (string[d->len] == '@')
		*atPos = string + d->len + 1;
#else
	if (!memcmp (d->name, string, d->len + 1)) {
#endif
	    return d->type;
	}
    }
    return -1;
}

typedef struct Line {
    struct Line *next;
    StrList *words;
    char *comment;
} Line;
#endif /* RDPYS || CONS */

static int
edit_xservers(File *file, char **nbuf, int *nlen)
{
#if defined(RDPYS) || defined(CONS)
    char *cur, *nword, *wordp, *lstrt, *buf, *p, *rp;
    const char *word, *dname, *dclass, *dclassp, *atPos;
    Line *lin, *lines = 0, **lptr = &lines;
    StrList *wrd, **wptr, *xswords = 0, **xswordp;
    int ndpys = 0, nldpys = 0, nrdpys = 0, dpymask = 0;
    int ttymask = 0;
    int type, tty, dn, maj, min;
    int quoted, i;
    char c;

    /* parse it */
    *file->eof = 0;
    cur = file->buf;
  nline:
    if (cur >= file->eof)
	goto done;
    if (!memcmp (cur, VERSION_WARNING, sizeof(VERSION_WARNING) - 1)) {
	cur += sizeof(VERSION_WARNING) - 1;
	goto nline;	/* *chomp* */
    }
    i = 0;
    sscanf (cur, "### Version %d.%d ###\n%n", &maj, &min, &i);
    if (i) {
	if (maj > XSERVERS_MAJOR ||
	    (maj == XSERVERS_MAJOR && min >= XSERVERS_MINOR))
	    return 0;	/* up to date */
	cur += i;
	goto nline;	/* *chomp* */
    }
    *lptr = lin = mcalloc (sizeof(*lin));
    lptr = &(*lptr)->next;
    wptr = &lin->words;
  nword:
    lstrt = cur;
    do {
	c = *cur++;
    } while (c == ' ' || c == '\t');
    if (!c || c == '\n')
	goto nline;
    if (c == '#') {
	do {
	    c = *cur++;
	} while (c && c != '\n');
	lin->comment = nword = mmalloc (cur - lstrt);
	memcpy (nword, lstrt, cur - lstrt - 1);
	nword[cur - lstrt - 1] = 0;
	goto nline;
    }
    wordp = cur - 1;
  mloop:
    quoted = 0;
  qloop:
    switch (c) {
    case '#':
	if (quoted)
	    break;
	cur--;
	goto deol;
    case '\n':
	if (quoted)
	    break;
      deol:
    case ' ':
    case '\t':
    case 0:
	*wptr = wrd = mcalloc (sizeof(*wrd));
	wptr = &(*wptr)->next;
	wrd->str = nword = mmalloc (cur - wordp);
	cur--;
	memcpy (nword, wordp, cur - wordp);
	nword[cur - wordp] = 0;
	goto nword;
    case '\\':
	if (!quoted) {
	    quoted = 1;
	    c = *cur++;
	    goto qloop;
	}
	break;
    }
    c = *cur++;
    goto mloop;
  done:

    /* analyze it */
    for (lin = lines; lin; lin = lin->next) {
	if (!(wrd = lin->words))
	    continue;	/* no display */
	dname = wrd->str;
	if (!(wrd = wrd->next))
	    continue;	/* no type */
	type = parseDisplayType (wrd->str
#ifdef CONS
				, &atPos
#endif
		);
	if (type < 0) {
	    if (!(wrd = wrd->next))
		continue;	/* no type */
	    type = parseDisplayType (wrd->str
#ifdef CONS
				    , &atPos
#endif
		);
	    if (type < 0)	/* invalid type */
		continue;
	}
	if (!(p = strchr (dname, ':')))
	    continue;	/* invalid display name */
	dn = strtol (++p, &rp, 10);
	if (*rp)
	    continue;	/* same here ... */
#ifdef RDPYS
	ndpys++;
#endif
	if (type != dLocal)
	    continue;	/* foreign doesn't count */
#ifdef RDPYS
	nldpys++;
	dpymask |= 1 << dn;
#endif
#ifdef CONS
	if (atPos) {
#ifdef __linux__
	    if (!memcmp (atPos, "tty", 3) && (tty = atoi (atPos + 3)))
		ttymask |= 1 << (tty - 1);
#elif defined(__sun__)
	    if (!memcmp (atPos, "console", 8))
		ttymask |= 1;
#endif
	}
#endif
#ifdef RDPYS
	wrd = wrd->next;
	if (wrd && !strcmp (wrd->str, "reserve")) {
	    nrdpys++;
	    wrd = wrd->next;
	}
	xswordp = 0;
	if (!xswords)
	    xswordp = &xswords;
	while (wrd) {
	    word = wrd->str;
	    /* this will break on -display :<n>, but in such a situation
	       (Xnest) we have already lost anyway */
	    if (xswordp && word[0] != ':' &&
		(word[0] != 'v' || word[1] != 't' || !isdigit(word[2])))
	    {
		*xswordp = mcalloc (sizeof(*xswordp));
		(*xswordp)->str = word;
		xswordp = &(*xswordp)->next;
	    }
	    wrd = wrd->next;
	}
#endif
    }

    /* edit it */
    buf = 0;
    for (lin = lines; lin; lin = lin->next) {
	if (lin->words) {
	    /* NOTE: we throw away b0rked display specs */
	    wrd = lin->words;
	    dname = wrd->str;
	    if (!(wrd = wrd->next))
		continue;	/* no type */
	    type = parseDisplayType (wrd->str
#ifdef CONS
				     , &atPos
#endif
		    );
	    if (type < 0) {
		dclass = wrd->str;		
		dclassp = " ";
		if (!(wrd = wrd->next))
		    continue;	/* no type */
		type = parseDisplayType (wrd->str
#ifdef CONS
					, &atPos
#endif
			);
		if (type < 0)	/* invalid type */
		    continue;
	    } else
		dclass = dclassp = "";
	    if (!(p = strchr (dname, ':')))
		continue;	/* invalid display name */
	    dn = strtol (++p, &rp, 10);
	    if (*rp)
		continue;	/* same here ... */
	    if (type != dLocal) {
		StrCat (&buf, "%s%s%s foreign", dname, dclassp, dclass);	/* foreign doesn't count */
		goto elin;
	    }
#ifdef CONS
	    if (!atPos) {
# ifdef __linux__
		for (tty = 0; ttymask & (1 << tty); tty++);
		ttymask |= (1 << tty);
		StrCat (&buf, "%s%s%s local@tty%d", dname, dclassp, dclass, tty + 1);
# elif defined(__sun__)
		if (!(ttymask & 1)) {
		    ttymask |= 1;
		    StrCat (&buf, "%s%s%s local@console", dname, dclassp, dclass);
		} else
		    StrCat (&buf, "%s%s%s local", dname, dclassp, dclass);
# endif
	    } else
		StrCat (&buf, "%s%s%s local@%s", dname, dclassp, dclass, atPos);
#else
	    StrCat (&buf, "%s%s%s local", dname, dclassp, dclass);
#endif
	    while ((wrd = wrd->next)) {
		word = wrd->str;
		StrCat (&buf, " %s", word);
	    }
	}
      elin:
	if (lin->comment)
	    StrCat (&buf, "%s\n", lin->comment);
	else
	    StrCat (&buf, "\n");
    }

#ifdef RDPYS
    /* add reserve dpys */
/* ###
    if (nldpys < 3 && nldpys && !nrdpys) {
	for (; nldpys < 3; nldpys++) {
	    for (dn = 0; dpymask & (1 << dn); dn++);
	    dpymask |= (1 << dn);
# ifdef CONS
#  ifdef __linux__
	    for (tty = 0; ttymask & (1 << tty); tty++);
	    ttymask |= (1 << tty);
	    StrCat (&buf, ":%d local@tty%d reserve", dn, tty + 1);
#  elif defined(__sun__)
	    if (!(ttymask & 1)) {
		ttymask |= 1;
		StrCat (&buf, "%s local@console", dname);
	    } else
		StrCat (&buf, "%s local", dname);
#  endif
# else
	    StrCat (&buf, ":%d local reserve", dn);
# endif
	    for (wrd = xswords; wrd; wrd = wrd->next)
		StrCat (&buf, " %s", wrd->str);
	    StrCat (&buf, " :%d\n", dn);
	}
    }
*/
#endif

    StrCat (&buf, "\n" XSERVERS_VERSION, 0);

    *nbuf = buf;
    *nlen = strlen(buf);
    return 1;
#else /* RDPYS || CONS */
    return 0;
#endif
}

static void
mk_xservers(Entry *ce, Section *cs ATTR_UNUSED)
{
    if (!ce->active) {	/* there is only the Global one */
      mkdef:
	ce->value = KDMCONF "/Xservers";
	ce->active = ce->written = 1;
	writefile (ce->value, 0644, def_xservers);
    } else if (old_confs)
	linkfile (ce);
    else
	if (!copyfile (ce, "Xservers", 0644, edit_xservers))
	    goto mkdef;
}

#ifdef HAVE_VTS
static char *
mem_mem(char *mem, int lmem, const char *smem, int lsmem)
{
    for (; lmem >= lsmem; mem++, lmem--)
	if (!memcmp (mem, smem, lsmem))
	    return mem + lsmem;
    return 0;
}
#endif

static void
upd_servervts(Entry *ce, Section *cs ATTR_UNUSED)
{
#ifdef HAVE_VTS
    File it;
    char *p, *eol, *ep;
    int tty, maxtty;

    if (!ce->active) {	/* there is only the Global one */
#ifdef __linux__	/* XXX actually, sysvinit */
	if (readFile (&it, "/etc/inittab")) {
	    maxtty = 0;
	    for (p = it.buf; p < it.eof; p = eol + 1) {
		for (eol = p; eol < it.eof && *eol != '\n'; eol++);
		if (*p != '#') {
		    if ((ep = mem_mem(p, eol - p, " tty", 4)) &&
			ep < eol && isdigit(*ep))
		    {
			if (ep + 1 == eol || isspace(*(ep + 1)))
			    tty = *ep - '0';
			else if (isdigit(*(ep + 1)) &&
				 (ep + 2 == eol || isspace(*(ep + 2))))
			    tty = (*ep - '0') * 10 + (*(ep + 1) - '0');
			else
			    continue;
			if (tty > maxtty)
			    maxtty = tty;
		    }
		}
	    }
	    freeBuf (&it);
	    if (!maxtty)
		maxtty = 6;
	    ASPrintf((char **)&ce->value, "-%d", maxtty + 1);
	    ce->active = ce->written = 1;
	}
#endif
    }
#endif
}

#ifdef XDMCP
static void
cp_keyfile(Entry *ce, Section *cs ATTR_UNUSED)
{
    if (!ce->active)	/* there is only the Global one */
	return;
    if (old_confs)
	linkfile (ce);
    else
	if (!copyfile (ce, "kdmkeys", 0600, 0))
	    ce->active = 0;
}

static void
mk_xaccess(Entry *ce, Section *cs ATTR_UNUSED)
{
    if (!ce->active)	/* there is only the Global one */
	writefile (def_Xaccess, 0644, def_xaccess);
    else if (old_confs)
	linkfile (ce);
    else
	copyfile (ce, "Xaccess", 0644, 0);	/* don't handle error, it will disable Xdmcp automatically */
}

static void
mk_willing(Entry *ce, Section *cs ATTR_UNUSED)
{
    const char *fname;

    if (!ce->active)	/* there is only the Global one */
	goto dflt;
    else {
	if (!(fname = strchr (ce->value, '/')))
	    return;	/* obviously in-line (or empty) */
	if (old_scripts || inNewDir (fname))
	    dlinkfile (fname);
	else {
	  dflt:
	    ce->value = KDMCONF "/Xwilling";
	    ce->active = ce->written = 1;
	    writefile (ce->value, 0755, def_willing);
	}
    }
}
#endif

/*
static int
edit_resources(File *file, char **nbuf, int *nlen)
{
    // XXX remove any login*, chooser*, ... resources
    return 0;
}
*/

static void
cp_resources(Entry *ce, Section *cs ATTR_UNUSED)
{
    if (!ce->active)	/* the X-*-Greeter one */
	return;
    if (old_confs)
	linkfile (ce);
    else
	if (!copyfile (ce, ce->value, 0644, 0/*edit_resources*/))
	    ce->active = 0;
}

static int
delstr (File *fil, const char *pat)
{
    char *p, *pp, *bpp;
    const char *pap, *paap;

    *fil->eof = 0;
    for (p = fil->buf; *p; p++) {
	for (pp = p, pap = pat; ; ) {
	    if (!*pap) {
		*p = '\n';
		memcpy (p + 1, pp, fil->eof - pp);
		fil->eof -= pp - p - 1;
		return 1;
	    } else if (!memcmp (pap, "*/", 2)) {
		paap = pap += 2;
		while (!isspace (*pap))
		    pap++;
		for (;;) {
		    if (*pp++ != '/')
			goto no;
		    for (bpp = pp; *pp != '/'; pp++)
			if (isspace(*pp))
			    goto wbrk;
		}
	      wbrk:
		if (memcmp (bpp, paap, pap - paap))
		    break;
	    } else if (*pap == '\t') {
		pap++;
		while (*pp == ' ' || *pp == '\t')
		    pp++;
	    } else {
		 if (*pap == '\n')
		    while (*pp == ' ' || *pp == '\t')
			pp++;
		if (*pap != *pp)
		    break;
		pap++;
		pp++;
	    }
	}
      no: ;
    }
    return 0;
}

/* XXX
   the UseBackground voodoo will horribly fail, if multiple sections link
   to the same Xsetup file
*/

static int mod_usebg;

static int
edit_setup(File *file, char **nbuf ATTR_UNUSED, int *nlen ATTR_UNUSED)
{
    putval ("UseBackground", 
		(delstr (file, "\n"
			"(\n"
			"  PIDFILE=/var/run/kdmdesktop-$DISPLAY.pid\n"
			"  */kdmdesktop\t&\n"
			"  echo $! >$PIDFILE\n"
			"  wait $!\n"
			"  rm $PIDFILE\n"
			")\t&\n") ||
		 delstr (file, "\n"
			"*/kdmdesktop\t&\n") ||
		 delstr (file, "\n"
			"kdmdesktop\t&\n") ||
		 delstr (file, "\n"
			"kdmdesktop\n")) ? "true" : "false");
    return 0;
}

static void
mk_setup(Entry *ce, Section *cs)
{
    setsect (resect (cs->name, "Greeter"));
    if (old_scripts || mixed_scripts) {
	if (mod_usebg && *ce->value)
	    putval ("UseBackground", "false");
	linkfile (ce);
    } else {
	if (ce->active && inNewDir (ce->value)) {
	    if (mod_usebg)
		copyfile (ce, ce->value, 0755, edit_setup);
	    else
		linkfile (ce);
	} else {
	    ce->value = KDMCONF "/Xsetup";
	    ce->active = ce->written = 1;
	    writefile (ce->value, 0755, def_setup);
	}
    }
}

static int
edit_startup(File *file, char **nbuf ATTR_UNUSED, int *nlen ATTR_UNUSED)
{
    if (!delstr (file, "\n"
		"PIDFILE=/var/run/kdmdesktop-$DISPLAY.pid\n"
		"if [ -f $PIDFILE ] ; then\n"
		"     kill `cat $PIDFILE`\n"
		"fi\n"))
	delstr (file, "\n"
		"PIDFILE=/var/run/kdmdesktop-$DISPLAY.pid\n"
		"test -f $PIDFILE && kill `cat $PIDFILE`\n");
    return 0;
}

static void
mk_startup(Entry *ce, Section *cs ATTR_UNUSED)
{
    if (old_scripts || mixed_scripts)
	linkfile (ce);
    else {
	if (ce->active && inNewDir (ce->value)) {
	    if (mod_usebg)
		copyfile (ce, ce->value, 0755, edit_startup);
	    else
		linkfile (ce);
	} else {
	    ce->value = KDMCONF "/Xstartup";
	    ce->active = ce->written = 1;
	    writefile (ce->value, 0755, def_startup);
	}
    }
}

static void
mk_reset(Entry *ce, Section *cs ATTR_UNUSED)
{
    if (old_scripts || mixed_scripts || (ce->active && inNewDir (ce->value)))
	linkfile (ce);
    else {
	ce->value = KDMCONF "/Xreset";
	ce->active = ce->written = 1;
	writefile (ce->value, 0755, def_reset);
    }
}

static void
mk_session(Entry *ce, Section *cs ATTR_UNUSED)
{
    if ((old_scripts || (ce->active && inNewDir (ce->value))) &&
	oldver >= 0x202)
	linkfile (ce);
    else {
	ce->value = KDMCONF "/Xsession";
	ce->active = ce->written = 1;
	writefile (ce->value, 0755, def_session);
    }
}

static void
upd_language(Entry *ce, Section *cs ATTR_UNUSED)
{
    if (!strcmp (ce->value, "C"))
	ce->value = (char *)"en_US";
}

static void
upd_guistyle(Entry *ce, Section *cs ATTR_UNUSED)
{
    if (!strcmp (ce->value, "Motif+"))
	ce->value = (char *)"MotifPlus";
    else if (!strcmp (ce->value, "KDE"))
	ce->value = (char *)"Default";
}

static void
upd_showusers(Entry *ce, Section *cs)
{
    if (!strcmp (ce->value, "All"))
	ce->value = (char *)"NotHidden";
    else if (!strcmp (ce->value, "None")) {
	if (ce->active)
	    putfqval (cs->name, "UserList", "false");
	ce->value = (char *)"Selected";
	ce->active = 0;
	ce->written = 1;
    }
}

static const char *defminuid, *defmaxuid;

static void
upd_minshowuid(Entry *ce, Section *cs ATTR_UNUSED)
{
    if (!ce->active) {
	ce->value = defminuid;
	ce->active = ce->written = 1;
    }
}

static void
upd_maxshowuid(Entry *ce, Section *cs ATTR_UNUSED)
{
    if (!ce->active) {
	ce->value = defmaxuid;
	ce->active = ce->written = 1;
    }
}

static void
upd_hiddenusers(Entry *ce, Section *cs ATTR_UNUSED)
{
    char *nv;
    const char *msu, *pt, *et;
    struct passwd *pw;
    unsigned minuid, maxuid;
    char nbuf[128];

    if (!ce->active)
	return;

    msu = getfqval (cs->name, "MinShowUID", "0");
    sscanf (msu, "%u", &minuid);
    msu = getfqval (cs->name, "MaxShowUID", "65535");
    sscanf (msu, "%u", &maxuid);

    nv = 0;
    pt = ce->value;
    for (;;) {
	et = strpbrk (pt, ";,");
	if (et) {
	    memcpy (nbuf, pt, et - pt);
	    nbuf[et - pt] = 0;
	} else
	    strcpy (nbuf, pt);
	if ((pw = getpwnam (nbuf))) {
	    if (!pw->pw_uid ||
		(pw->pw_uid >= minuid && pw->pw_uid <= maxuid))
	    {
		if (nv)
		    StrCat (&nv, ",%s", nbuf);
		else
		    nv = mstrdup (nbuf);
	    }
	}
	if (!et)
	    break;
	pt = et + 1;
    }
    ce->value = nv ? nv : "";
}

static void
upd_forgingseed(Entry *ce, Section *cs ATTR_UNUSED)
{
    if (!ce->active) {
	ASPrintf ((char **)&ce->value, "%d", time(0));
	ce->active = ce->written = 1;
    }
}

static void
upd_fifodir(Entry *ce, Section *cs ATTR_UNUSED)
{
    const char *dir;
    struct stat st;

    if (use_destdir)
	return;
    dir = ce->active ? ce->value : def_FifoDir;
    stat (dir, &st);
    chmod (dir, st.st_mode | 0755);
}

static void
upd_datadir(Entry *ce, Section *cs ATTR_UNUSED)
{
    char *oldsts, *newsts;
    const char *dir;

    if (use_destdir)
	return;
    dir = ce->active ? ce->value : def_DataDir;
    if (mkdirp (dir, 0755, "data", 0) && oldkde) {
	ASPrintf (&oldsts, "%s/kdm/kdmsts", oldkde);
	ASPrintf (&newsts, "%s/kdmsts", dir);
	rename (oldsts, newsts);
    }
}

static void
CopyFile (const char *from, const char *to)
{
    File file;
    int fd;

    if (readFile (&file, from)) {
	if ((fd = creat (to, 0644)) >= 0) {
	    write (fd, file.buf, file.eof - file.buf);
	    close (fd);
	}
	freeBuf (&file);
    }
}

static void
upd_facedir(Entry *ce, Section *cs ATTR_UNUSED)
{
    char *oldpic, *newpic, *defpic, *rootpic;
    const char *dir;
    struct passwd *pw;

    if (use_destdir)
	return;
    dir = ce->active ? ce->value : def_FaceDir;
    if (mkdirp (dir, 0755, "user face", 0)) {
	ASPrintf (&defpic, "%s/.default.face.icon", dir);
	ASPrintf (&rootpic, "%s/root.face.icon", dir);
	if (oldkde) {
	    setpwent ();
	    while ((pw = getpwent()))
		if (strcmp (pw->pw_name, "root")) {
		    ASPrintf (&oldpic, "%s/../apps/kdm/pics/users/%s.png",
					oldkde, pw->pw_name);
		    ASPrintf (&newpic, "%s/%s.face.icon", dir, pw->pw_name);
		    rename (oldpic, newpic);
		    free (newpic);
		    free (oldpic);
		}
	    endpwent ();
	    ASPrintf (&oldpic, "%s/../apps/kdm/pics/users/default.png", oldkde);
	    if (!rename (oldpic, defpic))
		defpic = 0;
	    ASPrintf (&oldpic, "%s/../apps/kdm/pics/users/root.png", oldkde);
	    if (!rename (oldpic, rootpic))
		rootpic = 0;
	}
	if (defpic) {
	    ASPrintf (&oldpic, "%s/default1.png", facesrc);
	    CopyFile (oldpic, defpic);
	}
	if (rootpic) {
	    ASPrintf (&oldpic, "%s/root1.png", facesrc);
	    CopyFile (oldpic, rootpic);
	}
    }
}

CONF_GEN_ENTRIES

static Sect *
findSect (const char *name)
{
    const char *p;
    int i;

    p = strrchr (name, '-');
    if (!p)
	p = name;
    for (i = 0; i < as(allSects); i++)
	if (!strcmp (allSects[i]->name, p))
	    return allSects[i];
    fprintf (stderr, "Internal error: unknown section %s\n", name);
    exit(1);
}

static Ent *
findEnt (Sect *sect, const char *key)
{
    int i;

    for (i = 0; i < sect->nents; i++)
	if (!strcmp (sect->ents[i].key, key))
	    return sect->ents + i;
    fprintf (stderr, "Internal error: unknown key %s in section %s\n", 
	     key, sect->name);
    exit(1);
}


/*
 * defaults
 */

typedef struct DEnt {
	const char	*key;
	const char	*value;
	int		active;
} DEnt;

typedef struct DSect {
	const char	*name;
	DEnt		*ents;
	int		nents;
	const char	*comment;
} DSect;

CONF_GEN_EXAMPLE

static void
mkdefconf (void)
{
    Section *cs, **csp;
    Entry *ce, **cep;
    int sc, ec;

    for (csp = &config, sc = 0; sc < as(dAllSects); csp = &(cs->next), sc++) {
	cs = mcalloc (sizeof(*cs));
	*csp = cs;
	cs->spec = findSect (dAllSects[sc].name);
	cs->name = dAllSects[sc].name;
	cs->comment = dAllSects[sc].comment;
	for (cep = &(cs->ents), ec = 0; ec < dAllSects[sc].nents; 
	     cep = &(ce->next), ec++) {
	    ce = mcalloc (sizeof(*ce));
	    *cep = ce;
	    ce->spec = findEnt (cs->spec, dAllSects[sc].ents[ec].key);
	    ce->value = dAllSects[sc].ents[ec].value;
	    ce->active = dAllSects[sc].ents[ec].active;
	}
    }
}


/*
 * read rc file structure
 */

typedef struct REntry {
	struct REntry	*next;
	const char		*key;
	const char		*value;
} REntry;

typedef struct RSection {
	struct RSection	*next;
	const char		*name;
	REntry		*ents;
} RSection;

static RSection *
ReadConf (const char *fname)
{
    char *nstr;
    char *s, *e, *st, *en, *ek, *sl;
    RSection *rootsec = 0, *cursec;
    REntry *curent;
    int nlen;
    int line, sectmoan;
    File file;

    if (!readFile (&file, fname))
	return 0;
    usedFile (fname);

    for (s = file.buf, line = 0, cursec = 0, sectmoan = 1; s < file.eof; s++) {
	line++;

	while ((s < file.eof) && isspace (*s) && (*s != '\n'))
	    s++;

	if ((s < file.eof) && ((*s == '\n') || (*s == '#'))) {
	  sktoeol:
	    while ((s < file.eof) && (*s != '\n'))
		s++;
	    continue;
	}
	sl = s;

	if (*s == '[') {
	    while ((s < file.eof) && (*s != '\n'))
		s++;
	    e = s - 1;
	    while ((e > sl) && isspace (*e))
		e--;
	    if (*e != ']') {
		fprintf (stderr, "Invalid section header at %s:%d\n", 
			 fname, line);
		continue;
	    }
	    sectmoan = 0;
	    nstr = sl + 1;
	    nlen = e - nstr;
	    for (cursec = rootsec; cursec; cursec = cursec->next)
		if (!memcmp (nstr, cursec->name, nlen) &&
		    !cursec->name[nlen])
		{
#if 0	/* not our business ... */
		    fprintf (stderr, "Warning: Multiple occurrences of section "
				     "[%.*s] in %s. Consider merging them.\n", 
			     nlen, nstr, fname);
#endif
		    goto secfnd;
		}
	    cursec = mmalloc (sizeof(*cursec));
	    ASPrintf ((char **)&cursec->name, "%.*s", nlen, nstr);
	    cursec->ents = 0;
	    cursec->next = rootsec;
	    rootsec = cursec;
	  secfnd:
	    continue;
	}

	if (!cursec) {
	    if (sectmoan) {
		sectmoan = 0;
		fprintf (stderr, "Entry outside any section at %s:%d",
			 fname, line);
	    }
	    goto sktoeol;
	}

	for (; (s < file.eof) && (*s != '\n'); s++)
	    if (*s == '=')
		goto haveeq;
	fprintf (stderr, "Invalid entry (missing '=') at %s:%d\n", fname, line);
	continue;

      haveeq:
	for (ek = s - 1;; ek--) {
	    if (ek < sl) {
		fprintf (stderr, "Invalid entry (empty key) at %s:%d\n", 
			 fname, line);
		goto sktoeol;
	    }
	    if (!isspace (*ek))
		break;
	}

	s++;
	while ((s < file.eof) && isspace(*s) && (*s != '\n'))
	    s++;
	st = s;
	while ((s < file.eof) && (*s != '\n'))
	    s++;
	for (en = s - 1; en >= st && isspace (*en); en--);

	nstr = sl;
	nlen = ek - sl + 1;
	for (curent = cursec->ents; curent; curent = curent->next)
	    if (!memcmp (nstr, curent->key, nlen) &&
		!curent->key[nlen]) {
		fprintf (stderr, "Multiple occurrences of key '%s' in section "
			 "[%s] of %s.\n", curent->key, cursec->name, fname);
		goto keyfnd;
	    }
	curent = mmalloc (sizeof (*curent));
	ASPrintf( (char **)&curent->key, "%.*s", nlen, nstr);
	ASPrintf( (char **)&curent->value, "%.*s", en - st + 1, st);
	curent->next = cursec->ents;
	cursec->ents = curent;
      keyfnd:
	continue;
    }
    return rootsec;
}


static int
isTrue (const char *val)
{
    return !strcmp(val, "true") || 
	   !strcmp(val, "yes") || 
	   !strcmp(val, "on") || 
	   !strcmp(val, "0");
}

static int
mergeKdmRcOld (const char *path)
{
    char *p;
    struct stat st;

    ASPrintf (&p, "%s/kdmrc", path);
    if (stat (p, &st)) {
	free (p);
	return 0;
    }
    printf ("Information: ignoring old kdmrc %s from kde < 2.2\n", p);
    free (p);
    return 1;
}

typedef struct {
    const char *sect, *key, *def;
    int (*cond)(void);
} FDefs;

static void
applydefs (FDefs *chgdef, int ndefs, const char *path)
{
    char *p;
    int i;

    for (i = 0; i < ndefs; i++)
	if (!getfqval (chgdef[i].sect, chgdef[i].key, 0) &&
	    (!chgdef[i].cond || chgdef[i].cond()))
	{
	    ASPrintf (&p, chgdef[i].def, path);
	    putfqval (chgdef[i].sect, chgdef[i].key, p);
	    free (p);
	}
}

#ifdef XDMCP
static FDefs kdmdefs_all[] = {
{ "Xdmcp",	"Xaccess",	"%s/kdm/Xaccess",	0		},
{ "Xdmcp",	"Willing",	"",			0		},
};
#endif

static FDefs kdmdefs_eq_22[] = {
{ "General",	"Xservers",	"%s/kdm/Xservers", 	0		},
{ "General",	"PidFile",	"/var/run/xdm.pid", 	0		},
{ "X-*-Core",	"Setup",	"%s/kdm/Xsetup", 	0		},
{ "X-*-Core",	"Startup",	"%s/kdm/Xstartup", 	0		},
{ "X-*-Core",	"Reset",	"%s/kdm/Xreset", 	0		},
{ "X-*-Core",	"Session",	"%s/kdm/Xsession", 	0		},
};

#ifdef XDMCP
static int
if_xdmcp (void)
{
    return isTrue (getfqval ("Xdmcp", "Enable", "true"));
}

static FDefs kdmdefs_le_30[] = {
{ "Xdmcp",	"KeyFile",	"%s/kdm/kdmkeys",	if_xdmcp	},
};
#endif

/* HACK: misused by is22conf() below */
static FDefs kdmdefs_ge_30[] = {
{ "X-*-Core",	"Setup",	"",			0		},
{ "X-*-Core",	"Startup",	"",			0		},
{ "X-*-Core",	"Reset",	"",			0		},
{ "X-*-Core",	"Session",	XBINDIR "/xterm -ls -T", 0		},
};

static int
if_usebg (void)
{
    return isTrue (getfqval ("X-*-Greeter", "UseBackground", "true"));
}

static FDefs kdmdefs_ge_31[] = {
{ "X-*-Greeter","BackgroundCfg","%s/kdm/backgroundrc",	if_usebg	},
};

static int
is22conf (const char *path)
{
    char *p;
    const char *val;
    int i, sl;

    sl = ASPrintf (&p, "%s/kdm/", path);
    /* safe bet, i guess ... */
    for (i = 0; i < 4; i++) {
	val = getfqval ("X-*-Core", kdmdefs_ge_30[i].key, 0);
	if (val && !memcmp (val, p, sl)) {
	    free (p);
	    return 0;
	}
    }
    free (p);
    return 1;
}

typedef struct KUpdEnt {
    const char	*okey, *nsec, *nkey;
    void	(*func)(const char *sect, const char **value);
} KUpdEnt;

typedef struct KUpdSec {
    const char	*osec;
    KUpdEnt	*ents;
    int		nents;
} KUpdSec;

#ifdef XDMCP
static void
P_EnableChooser (const char *sect ATTR_UNUSED, const char **value)
{
    *value = isTrue (*value) ? "DefaultLocal" : "LocalOnly";
}
#endif

CONF_GEN_KMERGE

static int
mergeKdmRcNewer (const char *path)
{
    char *p;
    const char *cp, *sec, *key;
    RSection *rootsect, *cs;
    REntry *ce;
    int i, j;
    static char sname[64];

    ASPrintf (&p, "%s/kdm/kdmrc", path);
    if (!(rootsect = ReadConf (p))) {
	free (p);
	return 0;
    }
    printf ("Information: reading old kdmrc %s (from kde >= 2.2.x)\n", p);
    free (p);

    for (cs = rootsect; cs; cs = cs->next) {
	if (!strcmp (cs->name, "Desktop0")) {
	    background = mstrdup ("[Desktop0]\n");
	    for (ce = cs->ents; ce; ce = ce->next)
		StrCat (&background, "%s=%s\n", ce->key, ce->value);
	} else {
	    cp = strrchr (cs->name, '-');
	    if (!cp)
		cp = cs->name;
	    else if (cs->name[0] != 'X' || cs->name[1] != '-')
		goto dropsec;
	    for (i = 0; i < as(kupsects); i++)
		if (!strcmp (cp, kupsects[i].osec)) {
		    for (ce = cs->ents; ce; ce = ce->next) {
			for (j = 0; j < kupsects[i].nents; j++)
			    if (!strcmp (ce->key, kupsects[i].ents[j].okey)) {
				if (!kupsects[i].ents[j].nsec)
				    sec = cs->name;
				else {
				    sec = sname;
				    sprintf (sname, "%.*s-%s", cp - cs->name, cs->name,
					     kupsects[i].ents[j].nsec);
				}
				if (!kupsects[i].ents[j].nkey)
				    key = ce->key;
				else
				    key = kupsects[i].ents[j].nkey;
				if (kupsects[i].ents[j].func)
				    kupsects[i].ents[j].func (sec, &ce->value);
				putfqval (sec, key, ce->value);
				goto gotkey;
			    }
			printf ("Information: dropping key %s from section [%s]\n",
				ce->key, cs->name);
		      gotkey: ;
		    }
		    goto gotsec;
		}
	  dropsec:
	    printf ("Information: dropping section [%s]\n", cs->name);
	  gotsec: ;
	}
    }

#ifdef XDMCP
    applydefs (kdmdefs_all, as(kdmdefs_all), path);
#endif
    if (!*(cp = getfqval ("General", "ConfigVersion", ""))) {	/* < 3.1 */
	mod_usebg = 1;
	if (is22conf (path)) {
	    /* work around 2.2.x defaults borkedness */
	    applydefs (kdmdefs_eq_22, as(kdmdefs_eq_22), path);
	    printf ("Information: old kdmrc is from kde 2.2\n");
	} else {
	    applydefs (kdmdefs_ge_30, as(kdmdefs_ge_30), path);
	    printf ("Information: old kdmrc is from kde 3.0\n");
	}
#ifdef XDMCP
	/* work around minor <= 3.0.x defaults borkedness */
	applydefs (kdmdefs_le_30, as(kdmdefs_le_30), path);
#endif
    } else {
	int ma, mi;
	sscanf (cp, "%d.%d", &ma, &mi);
	oldver = (ma << 8) | mi;
	printf ("Information: old kdmrc is from kde >= 3.1 (config version %d.%d)\n", ma, mi);
	applydefs (kdmdefs_ge_30, as(kdmdefs_ge_30), path);
	applydefs (kdmdefs_ge_31, as(kdmdefs_ge_31), path);
    }

    return 1;
}


typedef struct XResEnt {
    const char	*xname;
    const char	*ksec, *kname;
    void	(*func)(const char *sect, char **value);
} XResEnt;

static void
handleXdmVal (const char *dpy, const char *key, char *value,
	      const XResEnt *ents, int nents)
{
    const char *kname;
    int i;
    char knameb[80], sname[80];

    for (i = 0; i < nents; i++)
	if (!strcmp (key, ents[i].xname) ||
	    (key[0] == toupper (ents[i].xname[0]) && 
	     !strcmp (key + 1, ents[i].xname + 1)))
	{
	    sprintf (sname, ents[i].ksec, dpy);
	    if (ents[i].kname)
		kname = ents[i].kname;
	    else {
		kname = knameb;
		sprintf (knameb, "%c%s", 
			 toupper (ents[i].xname[0]), ents[i].xname + 1);
	    }
	    if (ents[i].func)
		ents[i].func (sname, &value);
	    putfqval (sname, kname, value);
	    break;
	}
}

static void 
P_List (const char *sect ATTR_UNUSED, char **value)
{
    int is, d, s;
    char *st;

    for (st = *value, is = d = s = 0; st[s]; s++)
	if (st[s] == ' ' || st[s] == '\t') {
	    if (!is)
		st[d++] = ',';
	    is = 1;
	} else {
	    st[d++] = st[s];
	    is = 0;
	}
    st[d] = 0;
}

static void 
P_authDir (const char *sect ATTR_UNUSED, char **value)
{
    int l;

    l = strlen (*value);
    if (l < 4) {
	*value = 0;
	return;
    }
    if ((*value)[l-1] == '/')
	(*value)[--l] = 0;
    if (!strncmp (*value, "/tmp/", 5) ||
	!strncmp (*value, "/var/tmp/", 9))
    {
	printf ("Warning: Resetting inappropriate value %s for AuthDir to default\n",
		*value);
	*value = 0;
	return;
    }
    if ((l >= 4 && !strcmp (*value + l - 4, "/tmp")) ||
	(l >= 6 && !strcmp (*value + l - 6, "/xauth")) ||
	(l >= 8 && !strcmp (*value + l - 8, "/authdir")) ||
	(l >= 10 && !strcmp (*value + l - 10, "/authfiles")))
	return;
    ASPrintf (value, "%s/authdir", *value);
}

static void 
P_openDelay (const char *sect, char **value)
{
    putfqval (sect, "ServerTimeout", *value);
}

static void 
P_noPassUsers (const char *sect, char **value ATTR_UNUSED)
{
    putfqval (sect, "NoPassEnable", "true");
}

static void 
P_autoUser (const char *sect, char **value ATTR_UNUSED)
{
    putfqval (sect, "AutoLoginEnable", "true");
}

#ifdef XDMCP
static void 
P_requestPort (const char *sect, char **value)
{
    if (!strcmp (*value, "0")) {
	*value = 0;
	putfqval (sect, "Enable", "false");
    } else
	putfqval (sect, "Enable", "true");
}
#endif

static int kdmrcmode = 0644;

static void 
P_autoPass (const char *sect ATTR_UNUSED, char **value ATTR_UNUSED)
{
    kdmrcmode = 0600;
}

CONF_GEN_XMERGE

static XrmQuark XrmQString, empty = NULLQUARK;

static Bool 
DumpEntry(
    XrmDatabase         *db ATTR_UNUSED,
    XrmBindingList      bindings,
    XrmQuarkList        quarks,
    XrmRepresentation   *type,
    XrmValuePtr         value,
    XPointer            data ATTR_UNUSED)
{
    const char *dpy, *key;
    int el, hasu;
    char dpybuf[80];

    if (*type != XrmQString)
	return False;
    if (*bindings == XrmBindLoosely || 
	strcmp (XrmQuarkToString (*quarks), "DisplayManager"))
	return False;
    bindings++, quarks++;
    if (!*quarks)
	return False;
    if (*bindings != XrmBindLoosely && !quarks[1]) {	/* DM.foo */
	key = XrmQuarkToString (*quarks);
	handleXdmVal (0, key, value->addr, globents, as(globents));
	return False;
    } else if (*bindings == XrmBindLoosely && !quarks[1]) { /* DM*bar */
	dpy = "*";
	key = XrmQuarkToString (*quarks);
    } else if (*bindings != XrmBindLoosely && quarks[1] &&
	       *bindings != XrmBindLoosely && !quarks[2]) { /* DM.foo.bar */
	dpy = dpybuf + 4;
	strcpy (dpybuf + 4, XrmQuarkToString (*quarks));
	for (hasu = 0, el = 4; dpybuf[el]; el++)
	    if (dpybuf[el] == '_')
		hasu = 1;
	if (!hasu/* && isupper (dpy[0])*/) {
	    dpy = dpybuf;
	    memcpy (dpybuf, "*:*_", 4);
	} else {
	    for (; --el >= 0; )
		if (dpybuf[el] == '_') {
		    dpybuf[el] = ':';
		    for (; --el >= 4; )
			if (dpybuf[el] == '_')
			    dpybuf[el] = '.';
		    break;
		}
	}
	key = XrmQuarkToString (quarks[1]);
    } else
	return False;
    handleXdmVal (dpy, key, value->addr, dpyents, as(dpyents));
    return False;
}

static FDefs xdmdefs[] = {
#ifdef XDMCP
{ "Xdmcp",	"Xaccess",	"%s/Xaccess",		0	},
{ "Xdmcp",	"Willing",	"",			0	},
#endif
{ "X-*-Core",	"Setup",	"",			0	},
{ "X-*-Core",	"Startup",	"",			0	},
{ "X-*-Core",	"Reset",	"",			0	},
{ "X-*-Core",	"Session",	"",			0	},
};

static int
mergeXdmCfg (const char *path)
{
    char *p;
    XrmDatabase db;

    ASPrintf (&p, "%s/xdm-config", path);
    if ((db = XrmGetFileDatabase (p))) {
	printf ("Information: reading old xdm config file %s\n", p);
	usedFile (p);
	free (p);
	XrmEnumerateDatabase(db, &empty, &empty, XrmEnumAllLevels,
			     DumpEntry, (XPointer) 0);
	applydefs (xdmdefs, as(xdmdefs), path);
	mod_usebg = 1;
	return 1;
    }
    free (p);
    return 0;
}

static void
fwrapprintf (FILE *f, const char *msg, ...)
{
    char *txt, *ftxt, *line;
    va_list ap;
    int col, lword, fspace;

    va_start (ap, msg);
    VASPrintf (&txt, msg, ap);
    va_end (ap);
    ftxt = 0;
    for (line = txt, col = 0, lword = fspace = -1; line[col]; ) {
	if (line[col] == '\n') {
	    StrCat (&ftxt, "%.*s", ++col, line);
	    line += col;
	    col = 0;
	    lword = fspace = -1;
	    continue;
	} else if (line[col] == ' ') {
	    if (lword >= 0) {
		fspace = col;
		lword = -1;
	    }
	} else {
	    if (lword < 0)
		lword = col;
	    if (col >= 78 && fspace >= 0) {
		StrCat (&ftxt, "%.*s\n", fspace, line);
		line += lword;
		col -= lword;
		lword = 0;
		fspace = -1;
	    }
	}
	col++;
    }
    free (txt);
    fputs (ftxt, f);
    free (ftxt);
}


static const char *oldkdes[] = {
    KDE_CONFDIR, 
    "/opt/kde3/share/config",
    "/usr/local/kde3/share/config",

    "/opt/kde/share/config",
    "/usr/local/kde/share/config",
    "/usr/local/share/config",
    "/usr/share/config",

    "/opt/kde2/share/config",
    "/usr/local/kde2/share/config",
};

static const char *oldxdms[] = {
    "/etc/X11/xdm", 
    XLIBDIR "/xdm",
};

int main(int argc, char **argv)
{
    const char **where;
    char *newkdmrc;
    FILE *f;
    StrList *fp;
    Section *cs;
    Entry *ce, **cep;
    int i, ap, newer, locals, foreigns;
    int no_old_xdm = 0, no_old_kde = 0;
    struct stat st;
    char nname[80];

    for (ap = 1; ap < argc; ap++) {
	if (!strcmp(argv[ap], "--help")) {
	    printf (
"genkdmconf - generate configuration files for kdm\n"
"\n"
"If an older xdm/kdm configuration is found, its config files are \"absorbed\";\n"
"if it lives in the new target directory, its scripts are reused (and possibly\n"
"modified) as well, otherwise the scripts are ignored and default scripts are\n"
"installed.\n"
"\n"
"options:\n"
"  --in /path/to/new/kdm-config-dir\n"
"    In which directory to put the new configuration. You can use this\n"
"    to support a $(DESTDIR), but not to change the final location of\n"
"    the installation - the paths inside the files are not affected.\n"
"    Default is " KDMCONF ".\n"
"  --old-xdm /path/to/old/xdm-dir\n"
"    Where to look for the config files of an xdm/older kdm.\n"
"    Default is to scan /etc/X11/kdm, $XLIBDIR/kdm, /etc/X11/xdm,\n"
"    $XLIBDIR/xdm; there in turn look for kdm-config and xdm-config.\n"
"    Note that you possibly need to use --no-old-kde to make this take effect.\n"
"  --old-kde /path/to/old/kde-config-dir\n"
"    Where to look for the kdmrc of an older kdm.\n"
"    Default is to scan " KDE_CONFDIR " and\n"
"    {/usr,/usr/local,{/opt,/usr/local}/{kde3,kde,kde2,kde1}}/share/config.\n"
"  --no-old\n"
"    Don't look at older xdm/kdm configurations, just create default config.\n"
"  --no-old-xdm\n"
"    Don't look at older xdm configurations.\n"
"  --no-old-kde\n"
"    Don't look at older kdm configurations.\n"
"  --old-scripts\n"
"    Directly use all scripts from the older xdm/kdm configuration.\n"
"  --no-old-scripts\n"
"    Don't use scripts from the older xdm/kdm configuration even if it lives\n"
"    in the new target directory.\n"
"  --old-confs\n"
"    Directly use all ancillary config files from the older xdm/kdm\n"
"    configuration. This is usually a bad idea.\n"
"  --no-backup\n"
"    Overwrite/delete old config files instead of backing them up.\n"
"  --no-in-notice\n"
"    Don't put the notice about --in being used into the generated README.\n"
);
	    exit (0);
	}
	if (!strcmp(argv[ap], "--no-old")) {
	    no_old = 1;
	    continue;
	}
	if (!strcmp(argv[ap], "--old-scripts")) {
	    old_scripts = 1;
	    continue;
	}
	if (!strcmp(argv[ap], "--no-old-scripts")) {
	    no_old_scripts = 1;
	    continue;
	}
	if (!strcmp(argv[ap], "--old-confs")) {
	    old_confs = 1;
	    continue;
	}
	if (!strcmp(argv[ap], "--no-old-xdm")) {
	    no_old_xdm = 1;
	    continue;
	}
	if (!strcmp(argv[ap], "--no-old-kde")) {
	    no_old_kde = 1;
	    continue;
	}
	if (!strcmp(argv[ap], "--no-backup")) {
	    no_backup = 1;
	    continue;
	}
	if (!strcmp(argv[ap], "--no-in-notice")) {
	    no_in_notice = 1;
	    continue;
	}
	where = 0;
	if (!strcmp(argv[ap], "--in"))
	    where = &newdir;
	else if (!strcmp(argv[ap], "--old-xdm"))
	    where = &oldxdm;
	else if (!strcmp(argv[ap], "--old-kde"))
	    where = &oldkde;
	else if (!strcmp(argv[ap], "--face-src"))
	    where = &facesrc;
	else {
	    fprintf (stderr, "Unknown command line option '%s', try --help\n", argv[ap]);
	    exit (1);
	}
	if (ap + 1 == argc || argv[ap + 1][0] == '-') {
	    fprintf (stderr, "Missing argument to option '%s', try --help\n", argv[ap]);
	    exit (1);
	}
	*where = argv[++ap];
    }
    if (memcmp (newdir, KDMCONF, sizeof(KDMCONF)))
	use_destdir = 1;

    if (!mkdirp (newdir, 0755, "target", 1))
	exit (1);

    mkdefconf();
    newer = 0;
    if (no_old) {
	DIR *dir;
	if ((dir = opendir (newdir))) {
	    struct dirent *ent;
	    char bn[PATH_MAX];
	    readdir (dir); /* . */
	    readdir (dir); /* .. */
	    while ((ent = readdir (dir))) {
		int l = sprintf (bn, "%s/%s", newdir, ent->d_name); /* cannot overflow (kernel would not allow the creation of a longer path) */
		if (!stat (bn, &st) && !S_ISREG (st.st_mode))
		    continue;
		if (no_backup || !memcmp (bn + l - 4, ".bak", 5))
		    unlink (bn);
		else
		    displace (bn);
	    }
	    closedir (dir);
	}
    } else {
	if (oldkde) {
	    if (!(newer = mergeKdmRcNewer (oldkde)) && !mergeKdmRcOld (oldkde))
		fprintf (stderr, 
			 "Cannot read old kdmrc at specified location\n");
	} else if (!no_old_kde) {
	    for (i = 0; i < as(oldkdes); i++) {
		if ((newer = mergeKdmRcNewer (oldkdes[i])) ||
		    mergeKdmRcOld (oldkdes[i])) {
		    oldkde = oldkdes[i];
		    break;
		}
	    }
	}
	if (!newer && !no_old_xdm) {
	    XrmInitialize ();
	    XrmQString = XrmPermStringToQuark("String");
	    if (oldxdm) {
		if (!mergeXdmCfg (oldxdm))
		    fprintf (stderr, 
			     "Cannot read old kdm-config/xdm-config at specified location\n");
	    } else
		for (i = 0; i < as(oldxdms); i++)
		    if (mergeXdmCfg (oldxdms[i])) {
			oldxdm = oldxdms[i];
			break;
		    }
	} else
	    oldxdm = 0;
    }
    if (no_old_scripts)
	goto no_old_s;
    if (!old_scripts) {
	locals = foreigns = 0;
	for (cs = config; cs; cs = cs->next)
	    if (!strcmp (cs->spec->name, "-Core")) {
		for (ce = cs->ents; ce; ce = ce->next)
		    if (ce->active &&
			(!strcmp (ce->spec->key, "Setup") ||
			 !strcmp (ce->spec->key, "Startup") ||
			 !strcmp (ce->spec->key, "Reset")))
		    {
			if (inNewDir (ce->value))
			    locals = 1;
			else
			    foreigns = 1;
		    }
	    }
	if (foreigns) {
	    if (locals) {
		fprintf (stderr, 
			 "Warning: both local and foreign scripts referenced. "
			 "Won't touch any.\n");
		mixed_scripts = 1;
	    } else {
	      no_old_s:
		for (cs = config; cs; cs = cs->next) {
		    if (!strcmp (cs->spec->name, "Xdmcp")) {
			for (ce = cs->ents; ce; ce = ce->next)
			    if (!strcmp (ce->spec->key, "Willing"))
				ce->active = ce->written = 0;
		    } else if (!strcmp (cs->spec->name, "-Core")) {
			for (cep = &cs->ents; (ce = *cep); ) {
			    if (ce->active &&
				(!strcmp (ce->spec->key, "Setup") ||
				 !strcmp (ce->spec->key, "Startup") ||
				 !strcmp (ce->spec->key, "Reset") ||
				 !strcmp (ce->spec->key, "Session")))
			    {
				if (!memcmp (cs->name, "X-*-", 4))
				    ce->active = ce->written = 0;
				else {
				    *cep = ce->next;
				    free (ce);
				    continue;				    
				}
			    }
			    cep = &ce->next;
			}
		    }
		}
	    }
	}
    }
#ifdef __linux__
    if (!stat( "/etc/debian_version", &st )) {	/* debian */
	defminuid = "1000";
	defmaxuid = "29999";
    } else if (!stat( "/usr/portage", &st )) {	/* gentoo */
	defminuid = "1000";
	defmaxuid = "65000";
    } else if (!stat( "/etc/mandrake-release", &st )) {	/* mandrake - check before redhat! */
	defminuid = "500";
	defmaxuid = "65000";
    } else if (!stat( "/etc/redhat-release", &st )) {	/* redhat */
	defminuid = "100";
	defmaxuid = "65000";
    } else /* if (!stat( "/etc/SuSE-release", &st )) */ {	/* suse */
	defminuid = "500";
	defmaxuid = "65000";
    }
#else
    defminuid = "1000";
    defmaxuid = "65000";
#endif
    for (i = 0; i < CONF_MAX_PRIO; i++)
	for (cs = config; cs; cs = cs->next)
	    for (ce = cs->ents; ce; ce = ce->next)
		if (ce->spec->func && i == ce->spec->prio)
		    ce->spec->func (ce, cs);
    ASPrintf (&newkdmrc, "%s/kdmrc", newdir);
    f = Create (newkdmrc, kdmrcmode);
    wrconf (f);
    fclose (f);

    sprintf (nname, "%s/README", newdir);
    f = Create (nname, 0644);
    fprintf (f, 
"This automatically generated configuration consists of the following files:\n");
    fprintf (f, "- " KDMCONF "/kdmrc\n");
    for (fp = aflist; fp; fp = fp->next)
	fprintf (f, "- %s\n", fp->str);
    if (use_destdir && !no_in_notice)
	fwrapprintf (f,
"All files destined for " KDMCONF " were actually saved in %s; "
"this config won't be workable until moved in place.\n", newdir);
    if (uflist || eflist || cflist || lflist) {
	fprintf (f, 
"\n"
"This config was derived from existing files. As the used algorithms are\n"
"pretty dumb, it may be broken.\n");
	if (uflist) {
	    fprintf (f, 
"Information from these files was extracted:\n");
	    for (fp = uflist; fp; fp = fp->next)
		fprintf (f, "- %s\n", fp->str);
	}
	if (lflist) {
	    fprintf (f, 
"These files were directly incorporated:\n");
	    for (fp = lflist; fp; fp = fp->next)
		fprintf (f, "- %s\n", fp->str);
	}
	if (cflist) {
	    fprintf (f, 
"These files were copied verbatim:\n");
	    for (fp = cflist; fp; fp = fp->next)
		fprintf (f, "- %s\n", fp->str);
	}
	if (eflist) {
	    fprintf (f, 
"These files were copied with modifications:\n");
	    for (fp = eflist; fp; fp = fp->next)
		fprintf (f, "- %s\n", fp->str);
	}
	if (!no_backup && !use_destdir)
	    fprintf (f, 
"Old files that would have been overwritten were renamed to <oldname>.bak.\n");
    }
    fprintf (f, 
"\nTry 'genkdmconf --help' if you want to generate another configuration.\n"
"\nYou may delete this README.\n");
    fclose (f);

    return 0;
}
