-- $Id: 500_install_os.lua,v 1.33 2005/01/27 23:39:49 cpressey Exp $

require "gettext"
require "target_system"

local srclist

--
-- During the 'copy mountpoints' stage,
-- only bother to copy the mountpoint IF:
-- o   We have said to copy it at some point
--     (something in srclist is a prefix of it); and
-- o   We have not already said to copy it
--     (it is not a prefix of anything in srclist.)
--
local is_valid_mountpoint = function(root, mountpoint)
	local seen_it = false
	local i, src

	local starts_with = function(str, prefix)
		return string.sub(str, string.len(prefix)) == prefix
	end

	for i, src in ipairs(srclist) do
		if starts_with(mountpoint, root .. src) then
			seen_it = true
		end
		if starts_with(root .. src, mountpoint) then
			return false
		end
	end
	
	return seen_it
end

return {
    name = "install_os",
    title = "Install OS",
    action = function(fsm)
	local spd
	local cmds
	local i, src
	local crash_device

	--
	-- Final confirmation.
	--
	if App.c:present({
	    id = "ready_to_install",
	    name = _("Ready to Install"),
	    short_desc = _(
		"Everything is now ready to install the actual files which "	..
		"comprise the %s operating system "				..
		"on the selected partition of the selected disk.\n\n"		..
		"Note that this process will take quite a while to finish. "	..
		"You may wish to take a break now and come back to the "	..
		"computer in a short while.", App.os.name),
	    actions = {
		{
		    id = "ok",
		    name = _("Begin Installing Files")
		},
		{
		    id = "cancel",
		    name = _("Return to %s", fsm:prev().title)
		}
	    }
	}).action_id ~= "ok" then
		return fsm:prev()
	end

	--
	-- If there is a target system mounted, unmount it before starting.
	--
	if App.state.target ~= nil and App.state.target:is_mounted() then
		if not App.state.target:unmount() then
			Dialog.inform(App.c,
			    _("Warning: already-mounted target system could " ..
			      "not be correctly unmounted first."))
			return fsm:current()
		end
	end

	--
	-- Populate the directory list.
	--
	srclist = App.load_conf("sources")

	--
	-- Create a command chain.
	--
	cmds = CmdChain.new()

	--
	-- Activate swap, if there is none activated so far.
	--
	if App.state.storage:get_activated_swap() == 0 then
		for spd in App.state.sel_part:get_subparts() do
			if spd:get_fstype() == "swap" then
				cmds:add("${root}${SWAPON} ${root}dev/" ..
				    spd:get_device_name())
			end
		end
	end

	--
	-- Mount the target system.  This will create the
	-- mountpoint directories the user asked for and
	-- will mount their subpartitions on them.
	--
	App.state.target = TargetSystem.new(App.state.sel_part, "mnt")
	if not App.state.target:create() then
		Dialog.inform(App.c,
		    _("Could not create the skeletal target system.")
		)
		return fsm:current()
	end
	if not App.state.target:mount() then
		Dialog.inform(App.c,
		    _("Could not mount the skeletal target system.")
		)
		return fsm:current()
	end

	--
	-- Add commands to copy sources (files and directories) now.
	--
	for i, src in ipairs(srclist) do
		--
		-- Create intermediate directories as needed.
		--
		cmds:add("${root}${MKDIR} -p ${root}mnt/" ..
		    FileSystem.dirname(src))

		--
		-- If a source by the same name but with the suffix
		-- ".hdd" exists on the installation media, cpdup that
		-- instead.  This is particularly useful with /etc, which
		-- may have significantly different behaviour on the
		-- live CD compared to a standard HDD boot.
		--
		local dest = src
		if FileSystem.is_dir(App.dir.root .. src .. ".hdd") or
		   FileSystem.is_file(App.dir.root .. src .. ".hdd") then
			src = src .. ".hdd"
		end

		--
		-- Cpdup the chosen source onto the HDD.
		--
		cmds:add({
		    cmdline = "${root}${CPDUP} ${root}" .. src ..
		              " ${root}mnt/" .. dest,
		    log_mode = CmdChain.LOG_QUIET	-- don't spam log
		})
	end

	--
	-- Now, because cpdup does not cross mount points,
	-- we must copy anything that the user might've made a
	-- seperate mount point for (e.g. /usr/libdata/lint.)
	--
	for spd in App.state.sel_part:get_subparts() do
		--
		-- Only copy files into the subpartition if:
		-- o  It is a regular (i.e.  not swap) subpartition, and
		-- o  A directory exists on the install medium for it
		--
		if spd:get_fstype() == "4.2BSD" and
		    FileSystem.is_dir(App.dir.root .. spd:get_mountpoint()) then
	
			if is_valid_mountpoint(App.dir.root, spd:get_mountpoint()) then
				--
				-- Cpdup the subpartition.
				--
				-- XXX check for .hdd-extended source dirs here, too,
				-- eventually - but for now, /etc.hdd will never be
				-- the kind of tricky sub-mount-within-a-mount-point
				-- that this part of the code is meant to handle.
				--
				cmds:add({
				    cmdline = "${root}${CPDUP} ${root}" ..
					spd:get_mountpoint() ..
					" ${root}mnt" .. spd:get_mountpoint(),
				    log_mode = CmdChain.LOG_QUIET
				})
			end
		end
	end

	--
	-- If we were to copy /usr/local and /usr/X11R6 onto the HDD,
	-- *all* packages installed on the LiveCD would be copied to
	-- the HDD.  This may not be what the user wants.  So instead,
	-- create an appropriate filesystem hierarchy under /usr/local
	-- and /usr/X11R6 and populate them with only mandatory packages.
	-- And first, remove the copied-over package database, as it is
	-- inaccurate.
	--

	cmds:add(
	    "${root}${RM} -rf ${root}mnt/var/db/pkg",
	    "${root}${MKDIR} -p ${root}mnt/var/db/pkg",
	    "${root}${CHMOD} 755 ${root}mnt/var/db/pkg",
	    "${root}${MKDIR} -p ${root}mnt/usr/local",
	    "${root}${MTREE} -deU -f ${root}etc/mtree/BSD.local.dist -p ${root}mnt/usr/local",
	    "${root}${MKDIR} -p ${root}mnt/usr/X11R6",
	    "${root}${MTREE} -deU -f ${root}etc/mtree/BSD.x11-4.dist -p ${root}mnt/usr/X11R6"
	)

	--
	-- Create symlinks.
	--

	--
	-- If the user has both /var and /tmp subparitions,
	-- symlink /var/tmp to /tmp.
	--
	if App.state.sel_part:get_subpart_by_mountpoint("/tmp") and
	   App.state.sel_part:get_subpart_by_mountpoint("/var") then
		cmds:add(
		    "${root}${CHMOD} 1777 ${root}mnt/tmp",
		    "${root}${RM} -rf ${root}mnt/var/tmp",
		    "${root}${LN} -s /tmp ${root}mnt/var/tmp"
		)
	end

	--
	-- If the user has /var, but no /tmp,
	-- symlink /tmp to /var/tmp.
	--
	if not App.state.sel_part:get_subpart_by_mountpoint("/tmp") and
	   App.state.sel_part:get_subpart_by_mountpoint("/var") then
		cmds:add(
		    "${root}${RM} -rf ${root}mnt/tmp",
		    "${root}${LN} -s /var/tmp ${root}mnt/tmp"
		)
	end

	--
	-- If the user has /usr, but no /home,
	-- symlink /home to /usr/home.
	--
	if not App.state.sel_part:get_subpart_by_mountpoint("/home") and
	   App.state.sel_part:get_subpart_by_mountpoint("/usr") then
		cmds:add(
		     "${root}${RM} -rf ${root}mnt/home",
		     "${root}${MKDIR} ${root}mnt/usr/home",
		     "${root}${LN} -s /usr/home ${root}mnt/home"
		)
	end

	--
	-- Clean up.  In case some file didn't make it, use rm -f
	--
	if App.os == "DragonFly" then
		cmds:add("${root}${RM} -f ${root}mnt/boot/loader.conf")
	end
	cmds:set_replacements{
	    logfile = App.log_filename
	}
	cmds:add("${root}${RM} -f ${root}mnt/tmp/${logfile}")

	-- Create missing directories.
	cmds:add(
	    "${root}${MKDIR} ${root}mnt/proc",
	    "${root}${MKDIR} ${root}mnt/mnt"
	)

	-- Write the fstab.
	App.state.sel_part:cmds_write_fstab(cmds, "${root}mnt/etc/fstab")

	--
	-- If it was enabled and selected, write the crash device
	-- in the user's new rc.conf file on the HDD.
	--
	if App.state.crash_device ~= nil then
		local cv = ConfigVars.new()
		cv:set("dumpdev", "/dev/" .. App.state.crash_device)
		cv:set("dumpdir", "/var/crash")
		cv:write(App.expander:expand("${root}mnt/etc/rc.conf"), "sh")
	end

	--
	-- Install requested packages.
	--
	if App.state.sel_pkgs ~= nil then
		local pkg_name, selected
		local pkg_seen = {}

		for pkg_name, selected in App.state.sel_pkgs do
			if selected then
				Package.cmds_copy(
				    "mnt/", cmds, pkg_name, pkg_seen
				)
			end
		end
	end

	-- Backup the disklabel and the log.
	cmds:add({
	    cmdline = "${root}${DISKLABEL} ${part} > ${root}mnt/etc/disklabel.${part}",
	    replacements = {
	        part = App.state.sel_part:get_device_name()
	    }
	})
	cmds:add(
	    "${root}${CP} ${tmp}${logfile} ${root}mnt/var/log/${logfile}",
	    "${root}${CHMOD} 600 ${root}mnt/var/log/${logfile}"
	)

	--
	-- Do it!
	--
	if cmds:execute() then
		--
		-- If the install went successfully,
		-- make sure once and for all that the disklabel is bootable.
		--
		cmds = CmdChain.new()
		App.state.sel_part:cmds_install_bootstrap(cmds)
		if not cmds:execute() then
			Dialog.inform(App.c,
			    _("Failed to make sure that the partition is bootable."))
		end
		return fsm:next()
	else
		Dialog.inform(App.c,
		    _("%s was not fully installed.", App.os.name))
		return fsm:current()
	end
    end
}
