#!/usr/bin/perl
#
#	mkdist 1.6 - Bootable Minix-vmd on one or more floppies.
#							Author: Kees J. Bot
#								27 May 1996
# Creates single or dual floppy Minix-vmd systems:
#	'-demo'	  - Single floppy demo.
#	'-dist'	  - Single/dual floppy distribution install set. (default)
#	'-repair' - Dual floppy system repair set.
#	'-sync'	  - One floppy to install or synchronize PC's at the VU.

# List of files to install on one or two floppies.
$FILELIST = <<'EOFL';
	-all
		/bin
		/bin/*
		/boot	/usr/mdec/boot
		/dev
		/etc
	-repair
		/dev/*
		/etc/*
	-demo,dist,sync
		MAKEDEV			# Use 'MAKEDEV std' to fill /dev
		/etc/fstab	/usr/src/etc/fstab	root
		/etc/group	/usr/src/etc/group	root
		/etc/motd	/usr/src/etc/motd	root
		/etc/mtab	/usr/src/etc/mtab	root
		/etc/passwd	/usr/src/etc/passwd	root
		/etc/profile	/usr/src/etc/profile	root
		/etc/rc
		/etc/shadow	/usr/src/etc/shadow	root operator 0600
		/etc/syslog.conf /usr/src/etc/syslog.conf root
		/etc/termcap	/usr/src/etc/termcap	root
		/etc/timeinfo	/usr/src/etc/timeinfo	root
		/etc/ttytab	/usr/src/etc/ttytab	root
		/etc/utmp	/usr/src/etc/utmp	root
	-dist,sync
		/etc/inetd.conf	/usr/src/etc/inetd.conf	root
		/etc/services	/usr/src/etc/services	root
		/etc/protocols	/usr/src/etc/protocols	root
	-dist,repair
		/fd0
		/fd1
		/home
		/opt
		/root
	-all
		/minix
		MINIX		# Select the newest image from /minix
		/mnt
		/sbin
		/sbin/*
		/tmp
		/usr
		/var
	-repair
		/misc?
		/save
	-all
		/usr/bin
		/usr/bin/arch
		/usr/bin/chmod
		/usr/bin/cp
		/usr/bin/dd
		/usr/bin/hostname
		/usr/bin/ln
		/usr/bin/login
		/usr/bin/mined
		/usr/bin/mkdir
		/usr/bin/mv
		/usr/bin/rm
		/usr/bin/stty
		/usr/bin/sysenv
		/usr/bin/uname
	-demo,dist,repair
		/usr/bin/df
		/usr/bin/kill
		/usr/bin/lc
		/usr/bin/less
		/usr/bin/lf
		/usr/bin/ll
		/usr/bin/lm
		/usr/bin/ls
		/usr/bin/lx
		/usr/bin/mknod
		/usr/bin/more
		/usr/bin/ps
		/usr/bin/readall
		/usr/bin/sort
	-dist,repair,sync
		/usr/bin/gunzip
		/usr/bin/gzcat
		/usr/bin/gzip
		/usr/bin/mkfs
		/usr/bin/mkfs2f
		/usr/bin/sleep
		/usr/bin/zexec
	-dist,repair
		/usr/bin/badblocks
		/usr/bin/chgrp
		/usr/bin/clone
		/usr/bin/cpdir
		/usr/bin/de
		/usr/bin/du
		/usr/bin/head
		/usr/bin/isodir
		/usr/bin/isoinfo
		/usr/bin/isoread
		/usr/bin/mattrib
		/usr/bin/mcd
		/usr/bin/mcopy
		/usr/bin/mdel
		/usr/bin/mdeltree
		/usr/bin/mdir
		/usr/bin/mformat
		/usr/bin/mlabel
		/usr/bin/mmd
		/usr/bin/mmove
		/usr/bin/mrd
		/usr/bin/mren
		/usr/bin/mtype
		/usr/bin/od
		/usr/bin/rmdir
		/usr/bin/sed
		/usr/bin/tail
		/usr/bin/tar
		/usr/bin/tget
		/usr/bin/vol
	-net,sync
		/usr/bin/hostaddr
		/usr/bin/ping
		/usr/bin/rcp
		/usr/bin/rlogin
		/usr/bin/rsh
		/usr/bin/synctree
		/usr/bin/rget
		/usr/bin/rput
	-net
		/usr/bin/ftp
		/usr/bin/telnet
	-demo,net
		/usr/bin/rawspeed
	-all
		/usr/etc
		/usr/etc/rc
	-all
		/usr/lib
		/usr/lib/pwdauth
	-demo,dist,repair
		/usr/lib/keymaps
		/usr/lib/keymaps/*
	-dist,repair,sync
		/usr/mdec
		/usr/mdec/boot
		/usr/mdec/bootblock
		/usr/mdec/masterboot
	-dist
		/usr/mdec/dosboot.com
	-all
		/usr/sbin
		/usr/sbin/getty
		/usr/sbin/update
	-dist,repair,sync
		/usr/sbin/installboot
		/usr/sbin/repartition
		/usr/sbin/shutdown
	-dist,repair
		/usr/sbin/MAKEDEV
		/usr/sbin/chown
		/usr/sbin/edparams
		/usr/sbin/mkswap
		/usr/sbin/part
		/usr/sbin/setup
	-sync
		/usr/sbin/partition
		/usr/sbin/rdate
	-net,sync
		/usr/sbin/ifconfig
		/usr/sbin/in.rld
		/usr/sbin/in.rshd
		/usr/sbin/inet
		/usr/sbin/inetd
		/usr/sbin/irdpd
		/usr/sbin/nonamed
		/usr/sbin/rarpd
	-net
		/usr/sbin/add_route
		/usr/sbin/pr_routes
	-all
		/usr/src
		/usr/src/.ashrc
		/usr/src/.profile
	-sync
		/usr/local
		/usr/local/sbin
		/usr/local/sbin/install_nt	# Oh joy.
		/usr/local/sbin/synchronize
	-demo
		/var/tmp
EOFL

sub major { ($_[0] >> 8) & 0xFF; }
sub minor { ($_[0] >> 0) & 0xFF; }

sub glob
{	local($pathname) = @_;
	# Expand a pathname if it contains wildcards, otherwise return the
	# pathname itself.  Expand like the shell, i.e. return the pathname
	# itself if the expansion fails.

	local(@pathnames);

	return ($pathname) unless ($pathname =~ /[*?\[]/);
	@pathnames = eval "<$pathname>";
	@pathnames == 0 ? ($pathname) : @pathnames;
}

sub select_newest
{	local($dir) = @_;
	# Select the newest file from a directory, a kernel image perhaps?

	local(%times, $file, @files);

	opendir(DIR, $dir) || die "$0: can't read $dir: $!\n";

	while ($file = readdir(DIR)) {
		next if $file eq '.' || $file eq '..';
		$file = "$dir/$file";
		$times{(lstat($file))[9]} = $file;
	}
	closedir(DIR);
	@files = sort(keys(%times));
	$times{$files[$#files]};
}

sub member
{
	# Is a string present in an array?

	foreach (@_[1..$#_]) {
		return 1 if @_[0] eq $_;
	}
	0;
}

sub select_compress
{
	# Select files that may be compressed executables.  If /usr/bin/zexec
	# is to be installed then all binaries in the /usr/bin and /usr/sbin
	# directories may be compressed except, of course, zexec and the gzip
	# links.

	%file'zexec = ();

	return unless &member('/usr/bin/zexec', @_);

	foreach (@_) {
		next unless m:^/usr/s?bin/:;
		next if $_ eq '/usr/bin/zexec';
		next if $_ eq '/usr/bin/gzip';
		next if $_ eq '/usr/bin/gunzip';
		next if $_ eq '/usr/bin/gzcat';
		$file'zexec{$_} = 1;
	}
}

sub ex
{	local($echo, $check, $cmdfmt, @args) = @_;
	# Execute a command, optionally with echoing and an error check.

	local($command) = sprintf($cmdfmt, @args);

	print "$command\n" if $echo;

	system($command) != 0 && $check && exit(1);
}

sub initialize
{
	%installed = ();
}

sub install
{	local($file, $prefix, $dir) = @_;
	# Install a file in the directory dir (first strip prefix).  Preserve
	# ownership, mode, etc.  There must be an (initially empty) dictionary
	# 'installed' that can be used to track links.  The dictionary
	# 'zexecfile' tells which executables may be compressed.  Tuples
	# of (name, target [, owner [,group [, mode]]]) are for files that
	# must be installed from a different source.

	$prefix .= '/' unless $prefix =~ m:/$:;

	die "$prefix not a prefix of $file"
		unless $prefix eq substr($file, 0, length($prefix));

	local($source) = $file'source{$file};
	local($target) = $dir . "/" . substr($file, length($prefix));

	local($instopt) = $file'zexec{$file} ? '-cs6' : '-cs';

	local(@attr_file) = lstat($source);

	unless (@attr_file) {
		print STDERR "$0: WARNING: $source: $!\n";
		return;
	}

	local($dev, $ino, $mode, $uid, $gid, $rdev) = @attr_file[0,1,2,4,5,6];
	local($type) = $mode & ~07777;
	$mode &= 07777;

	$uid = $file'uid{$file} if $file'uid{$file} ne '';
	$gid = $file'gid{$file} if $file'gid{$file} ne '';
	$mode = $file'mode{$file} if $file'mode{$file} ne '';

	local($link) = $installed{$dev,$ino};

	if ($link) {
		print "ln $link $target\n";
		link($link, $target) || die "$0: $target: $!\n";
	}
	elsif (-d _) {
		print "mkdir $target\n";
		mkdir($target, $mode) || die "$0: $target: $!\n";
		chmod($mode, $target) || die "$0: $target: $!\n";
		chown($uid, $gid, $target) || die "$0: $target: $!\n";
	}
	elsif (-f _) {
		&ex(1, 1, "install %s -m %o -o %s -g %s %s %s",
			$instopt, $mode, $uid, $gid, $source, $target);
	}
	elsif (-l _) {
		($link = readlink($source)) || die "$0: $source: $!\n";
		print "ln -s $link, $target\n";
		symlink($link, $target) || die "$0: $target: $!\n";
	}
	elsif (-b _ || -c _) {
		&ex(1, 1, "mknod $target %s %d %d",
			(-b _) ? 'b' : 'c',
			&major($rdev), &minor($rdev));
		chmod($mode, $target) || die "$0: $target: $!\n";
		chown($uid, $gid, $target) || die "$0: $target: $!\n";
	}
	elsif (-p _) {
		&ex(1, 1, "mknod $target p");
		chmod($mode, $target) || die "$0: $target: $!\n";
		chown($uid, $gid, $target) || die "$0: $target: $!\n";
	} else {
		$mode = sprintf("%o", $type|$mode);
		die "$target: strange file type 0$mode";
	}

	$installed{$dev,$ino} = $target;
}

sub makedev
{
	# Create the standard device directory with 'MAKEDEV std' and return
	# a file list for it.

	unless ($makedev_dir) {
		$makedev_dir = '/usr/tmp/.dev';
		&ex(0, 1, "rm -rf $makedev_dir");
		mkdir($makedev_dir, 0700) || die "$0: $makedev_dir: $!\n";
		&ex(1, 1, "cd $makedev_dir && exec MAKEDEV std");
	}

	opendir(DIR, $makedev_dir) || die "$0: $makedev_dir: $!\n";
	local(@devices);
	foreach (readdir(DIR)) {
		next if $_ eq '.' || $_ eq '..';
		push(@devices, "/dev/$_ $makedev_dir/$_");
	}
	closedir(DIR);
	@devices;
}

sub dolistline
{	local($file, $source, $owner, $group, $mode) = @_;
	# Interpret one line of the file list.

	if ($owner && $owner !~ /^\d+$/) {
		local($uid) = $owner2uid{$owner};
		if (!$uid) {
			local(@pw) = getpwnam($owner);
			die "$0: $owner: unknown user\n" unless @pw;
			$owner2uid{$owner} = $uid = $pw[2];
		}
		$owner = $uid;
	}
	if ($group && $group !~ /^\d+$/) {
		local($gid) = $group2gid{$group};
		if (!$gid) {
			local(@gr) = getgrnam($group);
			die "$0: $group: unknown group\n" unless @gr;
			$group2gid{$group} = $gid = $gr[2];
		}
		$group = $gid;
	}
	local(@files) = &glob($file);

	foreach $file (@files) {
		$file'source{$file} = $source || $file;
		$file'uid{$file} = $owner;
		$file'gid{$file} = $group;
		$file'mode{$file} = oct($mode) if $mode;
	}
	@files;
}

sub extractlist
{	local($floppy) = @_;
	# Extract a list of files from $FILELIST for the floppy to make.

	local(%pairs, $take);
	foreach (split(/\n/, $FILELIST)) {
		s/[ \t]+/ /g;
		s/ #.*//;
		s/^#.*//;
		s/^ //;
		s/ $//;
		next if /^$/;
		if (/^-/) {
			$take = /all/ || /$how/ || /$floppy/;
			next;
		}
		next unless $take;

		if ($how eq 'dist' || $how eq 'repair') {
			next if $floppy eq 'root' && m:^/usr/:;
			next if ($floppy eq 'usr' || $floppy eq 'net')
				&& !m:^/usr/:;
		}

		if (m:^/:) {
			$pairs{$_} = 1;
		}
		elsif ($_ eq 'MAKEDEV') {
			foreach (&makedev()) {
				$pairs{$_} = 1;
			}
		}
		elsif ($_ eq 'MINIX') {
			$pairs{&select_newest('/minix')} = 1;
		}
		else {
			die "$_: unknown special file name";
		}
	}

	local(%files);
	foreach (keys(%pairs)) {
		foreach (&dolistline(split)) {
			$files{$_} = 1;
		}
	}
	local(@files) = sort(keys(%files));
	&select_compress(@files);
	@files;
}

# Only root can make device files and such.
die "$0 must be run by root\n" unless $> == 0;

sub usage
{
	die "Usage: $0 [-demo|-dist|-repair|-sync] [-2] device\n";
}

$how = 'dist';
$dual = 0;

while ($ARGV[0] =~ /^-/) {
	$opt = substr(shift(@ARGV), 1);
	if ($opt eq '-') {
		last;
	}
	elsif (&member($opt, ('demo', 'dist', 'repair', 'sync'))) {
		$how = $opt;
	}
	elsif ($opt eq '2') {
		$dual = 1;
	} else {
		&usage();
	}
}

($device = shift(@ARGV)) || &usage();

&usage() if @ARGV;

# First word on the mounted device table is the root device.
open(MTAB, '/etc/mtab') || die "$0: /etc/mtab: $!\n";
$rootdev = (split(/[ \t\n]/, <MTAB>))[0];
close(MTAB);

# Make sure the device is free for use.
&ex(0, 0, "umount -v $device 2>/dev/null");
&ex(0, 0, "umount -v ${device}c 2>/dev/null");
&ex(0, 0, "umount -v /mnt 2>/dev/null");

# First the root floppy.
&initialize();
@files = &extractlist('root');

print "Insert the $how floppy and hit RETURN"; <STDIN>;

# Mkfs, mount, copy, make bootable.
if ($how eq 'demo') {
	&ex(1, 1, "mkfs -t 2f -i %d $device 1200", @files + 64);
}
elsif ($how eq 'dist' || $how eq 'repair') {
	&ex(1, 1, "mkfs -t 2f -i %d $device 720", @files + 32);
}
elsif ($how eq 'sync') {
	&ex(1, 1, "mkfs -t 2f -i %d $device 1440", @files + 32);
} else {
	die "how $how?";
}

&ex(0, 1, "mount -v $device /mnt");

chmod(0755, "/mnt") || die "$0: /mnt: $!\n";

# Install the files from the root directory.
foreach (@files) {
	&install($_, '/', '/mnt');
}

# Fix up the wrongly copied mountpoints if any.  (The author likes it if
# mountpoints have mode 555 for some reason.  Let's humour him.)
foreach ('mnt', 'var', 'opt', 'home') {
	next unless -d "/mnt/$_";
	chmod(0555, "/mnt/$_") || die "$0: /mnt/$_: $!\n";
}

# Adapt the demo and sync fstab to the device it is likely to see.
if ($how eq 'demo' || $how eq 'sync') {
	open(MTAB, ">/mnt/etc/fstab");
	print MTAB "\
# fstab - File System Table
#
# Device	Dir	Type	Options		Freq	Pass	Time
/dev/fd0	/	2f	rw		0	1	0
";
	close(MTAB);
}

# Delete the passwords from the sync floppy password file.
if ($how eq 'sync') {
	open(PWIN, "/usr/src/etc/passwd");
	open(PWOUT, ">/mnt/etc/passwd");
	while (<PWIN>) {
		s/:##root:/::/;
		s/:##bin:/::/;
		print PWOUT;
	}
	print PWOUT "synch::0:0:Synchronize:/tmp:/usr/local/sbin/synchronize\n";
	print PWOUT "dont::0:0:Install NT:/tmp:/usr/local/sbin/install_nt\n";
	close(PWIN);
	close(PWOUT);
}

# Unmount and make bootable.
&ex(0, 1, "umount -v /mnt");
&ex(1, 1, "installboot -device $device /usr/mdec/bootblock boot >/dev/null");

# Copy the boot parameters to a repair floppy.
if ($how eq 'repair') {
	&ex(0, 1, "dd if=$rootdev of=$device skip=1 seek=1 count=1");
}

# A demo floppy needs no RAM disk.
if ($how eq 'demo') {
	&ex(0, 1, "edparams $device 'rootdev=bootdev;save'");
}

# A sync floppy has its own weird menu.
if ($how eq 'sync') {
	&ex(0, 1, "edparams $device '
		r(r,reset options) { unset has_minix has_windows has_mx14 has_4fg has_mxe17s has_barbados has_stealth; menu }
		m(m,Minix) { has_minix=t; menu }
		w(w,MS-WINDOWS) { has_windows=t; menu }
		4(4,MAG MX14) { has_mx14=t; menu }
		n(n,NEC 4FG) { has_4fg=t; menu }
		7(7,MAG MXE17S) { has_mxe17s=t; menu }
		b(b,Barbados 64) { has_barbados=t; menu }
		s(s,Diamond Stealth 64) { has_stealth=t; menu }
		minix(=,Minix-vmd){boot}
		DPETH0=300:10
		servers=inet
		save'
		");
}

# A demo or sync floppy is just one floppy.
exit(0) if $how eq 'demo' || $how eq 'sync';

# Partition the floppy for single use.
&ex(0, 1, "partition -m $device 0 81:1440 0:0 81:1440 >/dev/null");
&ex(0, 1, "repartition $device >/dev/null");

# Now do the /usr partition (if any.)
&initialize();
@files = &extractlist('usr');

if ($dual) {
	print "Insert the /usr floppy and hit RETURN"; <STDIN>;
	$usrdev = $device;
} else {
	$usrdev = "${device}c";
}

# Mkfs, mount, copy, make *not* bootable.
&ex(1, 1, "mkfs -t 2f -i %d $usrdev 720", @files + 16);
&ex(0, 1, "mount -v $usrdev /mnt");

chmod(0755, "/mnt") || die "$0: /mnt: $!\n";

# Install the files from the /usr directory.
foreach (@files) {
	&install($_, '/usr', '/mnt');
}

# Unmount and make it boot something else if booted.
&ex(0, 1, "umount -v /mnt");
&ex(1, 1, "installboot -master $usrdev /usr/mdec/masterboot >/dev/null");

# Now do the network /usr floppy.
&initialize();
@files = &extractlist('net');

print "Insert the network /usr floppy and hit RETURN"; <STDIN>;

# Mkfs, mount, copy, make *not* bootable.
&ex(1, 1, "mkfs -t 2f -i %d $device 1200", @files + 16);

&ex(0, 1, "mount $device /mnt");

chmod(0755, "/mnt") || die "$0: /mnt: $!\n";

# Install the files from the /usr directory.
foreach (@files) {
	&install($_, '/usr', '/mnt');
}

# Unmount and make it boot something else if booted.
&ex(0, 1, "umount /mnt");
&ex(1, 1, "installboot -master $device /usr/mdec/masterboot >/dev/null");

# Done.
exit(0);
