1 -- $Id: target_system.lua,v 1.25 2005/03/29 21:04:19 cpressey Exp $
3 local App = require("app")
4 local FileName = require("filename")
10 --[[--------------]]--
11 --[[ TargetSystem ]]--
12 --[[--------------]]--
17 -- There are three general use cases for this class.
19 -- The first case is when mounting a virgin target system
20 -- for a fresh install. In this situation, the needed
21 -- mountpoint directories (taken from the subpartition descriptors
22 -- in the partition descriptor) are created on the target system
23 -- before being mounted:
25 -- local ts = TargetSystem.new(pd, "mnt")
26 -- if ts:create() then
33 -- The second case is when mounting an existing target system
34 -- for configuration. In this situation, the root partition is
35 -- mounted, then the file /etc/fstab on that partition is parsed,
36 -- and everything that can reasonably be mounted from that is.
38 -- local ts = TargetSystem.new(pd, "mnt")
46 -- The third case is when configuring the booted system, such as
47 -- when the configurator may be started from the installed system
48 -- itself. In this case, no mounting is necessary. But the target
49 -- system must know that this is the case, so that it need not
50 -- e.g. uselessly chroot to itself.
52 -- local ts = TargetSystem.new(pd)
53 -- if ts:use_current() then
57 TargetSystem.new = function(pd, base)
58 local ts = {} -- instance variable
59 local fstab = nil -- our representation of fstab
60 local root_is_mounted = false -- flag: is the root fs mounted?
61 local is_mounted = false -- flag: is everything mounted?
62 local using_current = false -- flag: using booted system?
65 -- Private utility helper functions.
69 -- Unmount all mountpoints under a given directory. Recursively unmounts
70 -- dependent mountpoints, so that unmount_all'ing /mnt will first unmount
71 -- /mnt/usr/local, then /mnt/usr, then /mnt itself.
73 -- The third argument is generally not necessary when calling this function;
74 -- it is used only when it recursively calls itself.
76 local unmount_all_under
77 unmount_all_under = function(cmds, dirname, fs_descs)
78 local unmount_me = false
82 dirname = App.expand("${root}${base}", {
85 dirname = FileName.remove_trailing_slash(dirname)
89 fs_descs = MountPoints.enumerate()
92 for i, fs_desc in fs_descs do
93 if fs_desc.mountpoint == dirname then
97 if string.sub(fs_desc.mountpoint, 1, string.len(dirname)) == dirname and
98 string.len(dirname) < string.len(fs_desc.mountpoint) then
99 unmount_all_under(cmds, fs_desc.mountpoint, fs_descs)
105 cmdline = "${root}${UMOUNT} ${dirname}",
106 replacements = { dirname = dirname }
112 -- Convert the options for swap-backed devices from their
113 -- fstab format to command line format.
115 local convert_swap_options = function(opts)
119 for opt in string.gfind(opts, "[^,]") do
121 -- Honour options that begin with -, but
122 -- don't bother trying to honour the -C
123 -- option, since we can't copy files from
124 -- the right place anyway.
126 if string.find(opt, "^-[^C]") then
128 string.gsub(opt, "=", " ") .. " "
136 -- Mount this TargetSystem's root filesystem.
137 -- Note: this doesn't just queue up commands, it actually does it.
138 -- This is necessary for reading /etc/fstab.
139 -- Any optimizations will come later...
141 local mount_root_filesystem = function(ts)
144 if root_is_mounted then
145 return false, "Root filesystem is already mounted"
149 -- Create a command chain.
151 cmds = CmdChain.new()
154 -- Find the root subpartition of the partition.
155 -- It's always the first one, called "a".
157 spd = pd:get_subpart_by_letter("a")
160 -- If there isn't one, then this partition isn't
161 -- correctly formatted. One possible cause is
162 -- an incomplete formatting operation; perhaps the
163 -- partition was disklabeled, but never newfs'ed.
170 -- Ensure that the devices we'll be using exist.
172 pd:get_parent():cmds_ensure_dev(cmds)
173 pd:cmds_ensure_dev(cmds)
174 spd:cmds_ensure_dev(cmds)
177 -- Make sure nothing is mounted under where we want
178 -- to mount this filesystem.
180 unmount_all_under(cmds)
183 -- Mount the target's root filesystem
186 cmdline = "${root}${MOUNT} ${root}dev/${dev} ${root}${base}",
188 dev = spd:get_device_name(),
196 root_is_mounted = cmds:execute()
197 return root_is_mounted
204 ts.get_part = function(ts)
208 ts.get_base = function(ts)
212 ts.is_mounted = function(ts)
217 -- Command-generating methods.
220 ts.cmds_set_password = function(ts, cmds, username, password)
222 cmdline = "${root}${CHROOT} ${root}${base} " ..
223 "/${PW} usermod ${username} -h 0",
228 desc = _("Setting password for user `%s'...", username),
229 input = password .. "\n",
234 ts.cmds_add_user = function(ts, cmds, tab)
236 local add_flag = function(flag, setting)
237 if setting ~= nil and setting ~= "" then
238 return flag .. " " .. tostring(setting)
245 if not tab.home or not FileName.is_dir(tab.home) then
246 home_skel = "-m -k /usr/share/skel"
250 cmdline = "${root}${CHROOT} ${root}${base} /${PW} useradd " ..
251 "'${username}' ${spec_uid} ${spec_gid} -c \"${gecos}\"" ..
252 "${spec_home} -s ${shell} ${spec_groups} ${home_skel}",
255 username = assert(tab.username),
256 gecos = tab.gecos or "Unnamed User",
257 shell = tab.shell or "/bin/sh",
258 spec_uid = add_flag("-u", tab.uid),
259 spec_gid = add_flag("-g", tab.group),
260 spec_home = add_flag("-d", tab.home),
261 spec_groups = add_flag("-G", tab.groups),
262 home_skel = home_skel
267 ts:cmds_set_password(cmds, tab.username, tab.password)
272 -- Create commands to copy files and directories to the traget system.
274 ts.cmds_install_srcs = function(ts, cmds, srclist)
275 local i, src, dest, spd
278 -- Only bother to copy the mountpoint IF:
279 -- o We have said to copy it at some point
280 -- (something in srclist is a prefix of it); and
281 -- o We have not already said to copy it
282 -- (it is not a prefix of anything in srclist.)
284 local is_valid_mountpoint = function(root, mountpoint)
285 local seen_it = false
288 local starts_with = function(str, prefix)
289 return string.sub(str, string.len(prefix)) == prefix
292 for i, src in ipairs(srclist) do
293 if starts_with(mountpoint, root .. src) then
296 if starts_with(root .. src, mountpoint) then
304 for i, src in ipairs(srclist) do
306 -- Create intermediate directories as needed.
309 cmdline = "${root}${MKDIR} -p ${root}${base}${src_dir}",
312 src_dir = FileName.dirname(src)
317 -- If a source by the same name but with the suffix
318 -- ".hdd" exists on the installation media, cpdup that
319 -- instead. This is particularly useful with /etc, which
320 -- may have significantly different behaviour on the
321 -- live CD compared to a standard HDD boot.
324 if FileName.is_dir(App.dir.root .. src .. ".hdd") or
325 FileName.is_file(App.dir.root .. src .. ".hdd") then
330 -- Cpdup the chosen source onto the HDD.
333 cmdline = "${root}${CPDUP} ${root}${src} ${root}${base}${dest}",
339 log_mode = CmdChain.LOG_QUIET -- don't spam log
344 -- Now, because cpdup does not cross mount points,
345 -- we must copy anything that the user might've made a
346 -- seperate mount point for (e.g. /usr/libdata/lint.)
348 for spd in pd:get_subparts() do
350 -- Only copy files into the subpartition if:
351 -- o It is a regular (i.e. not swap) subpartition, and
352 -- o A directory exists on the install medium for it
356 -- This assumes that the subpartition descriptors
357 -- have mountpoints associated with them, which should
358 -- (nowadays) always be the case.
360 if spd:get_fstype() == "4.2BSD" and
361 FileName.is_dir(App.dir.root .. spd:get_mountpoint()) then
362 if is_valid_mountpoint(App.dir.root, spd:get_mountpoint()) then
364 -- Cpdup the subpartition.
366 -- XXX check for .hdd-extended source dirs here, too,
367 -- eventually - but for now, /etc.hdd will never be
368 -- the kind of tricky sub-mount-within-a-mount-point
369 -- that this part of the code is meant to handle.
372 cmdline = "${root}${CPDUP} ${root}${mtpt} " ..
373 "${root}${base}${mtpt}",
376 mtpt = spd:get_mountpoint()
378 log_mode = CmdChain.LOG_QUIET
386 -- Create mountpoint directories on a new system, based on what
387 -- the user wants (the subpartition descriptors under the given
388 -- partition descriptor) and return a fstab structure describing them.
390 ts.create = function(ts)
394 -- Mount the target system's root filesystem,
395 -- if not already mounted
397 if not root_is_mounted then
398 if not mount_root_filesystem() then
404 -- Create mount points for later mounting of subpartitions.
406 cmds = CmdChain.new()
408 for spd in pd:get_subparts() do
409 local mtpt = spd:get_mountpoint()
410 local dev = spd:get_device_name()
412 cmds:set_replacements{
415 mtpt = FileName.remove_leading_slash(mtpt)
418 if spd:is_swap() then
419 if App.option.enable_crashdumps and
420 spd:get_capacity() >= App.state.store:get_ram() then
422 -- Set this subpartition as the dump device.
424 cmds:add("${root}${DUMPON} -v ${root}dev/${dev}")
425 App.state.crash_device = mnt_dev
428 device = "/dev/" .. dev,
435 if spd:get_mountpoint() ~= "/" then
436 cmds:add("${root}${MKDIR} -p ${root}${base}${mtpt}")
441 device = "/dev/" .. dev,
453 return cmds:execute()
457 -- Parse the fstab of a mounted target system.
458 -- Returns either a table representing the fstab, or
459 -- nil plus an error message string.
461 -- As a side effect, this function also associates mountpoints
462 -- with the subpartition descriptors under the partition
463 -- descriptor with which this target system is associated.
465 ts.probe = function(ts)
466 local fstab_filename, fstab_file, errmsg
470 -- Mount the target system's root filesystem,
471 -- if not already mounted.
473 if not root_is_mounted then
474 if not mount_root_filesystem() then
475 return nil, "Could not mount / of target system."
480 -- Open the target system's fstab and parse it.
482 fstab_filename = App.expand("${root}${base}etc/fstab", {
485 fstab_file, errmsg = io.open(fstab_filename, "r")
486 if not fstab_file then
487 return nil, "Could not open /etc/fstab of target system."
491 line = fstab_file:read()
494 -- Parse the fstab line.
496 if string.find(line, "^%s*#") then
498 elseif string.find(line, "^%s*$") then
499 -- blank line: skip it
501 local found, len, dev, mtpt, fstype, opts, dump, pass =
502 string.find(line, "%s*([^%s]*)%s*([^%s]*)%s*" ..
503 "([^%s]*)%s*([^%s]*)%s*([^%s]*)%s*([^%s]*)")
505 App.log("Warning: malformed line in fstab: " ..
515 spd = pd:get_subpart_by_device_name(dev)
516 if fstype ~= "ufs" then
517 -- Don't associate non-ufs
518 -- fs's with any mountpoint.
520 -- This can happen if e.g.
521 -- the user has included a
522 -- subpartition from another
523 -- drive in their fstab.
525 -- Associate mountpoint.
526 spd:set_mountpoint(mtpt)
530 line = fstab_file:read()
537 ts.use_current = function(ts)
544 -- Mount the system on the given partition into the given mount
545 -- directory (typically "mnt".)
547 ts.mount = function(ts)
548 local cmds, i, mtpt, mtpts, fsdesc
550 if using_current or is_mounted then
554 if not root_is_mounted or fstab == nil then
559 -- Go through each of the mountpoints in our fstab,
560 -- and if it looks like we should, try mount it under base.
564 for mtpt, fsdesc in fstab do
565 table.insert(mtpts, mtpt)
569 cmds = CmdChain.new()
570 for i, mtpt in mtpts do
573 -- It's already been mounted by
574 -- read_target_fstab() or create_mountpoints.
575 elseif string.find(fsdesc.options, "noauto") then
576 -- It's optional. Don't mount it.
577 elseif (not string.find(fsdesc.device, "^/dev/") and
578 fsdesc.device ~= "swap") then
579 -- Device doesn't start with /dev/ and
580 -- it isn't 'swap'. Don't even go near it.
581 elseif mtpt == "none" or fsdesc.fstype == "swap" then
582 -- Swap partition. Don't mount it.
583 elseif fsdesc.device == "swap" then
584 -- It's swap-backed. mount_mfs it.
587 cmdline = "${root}${MOUNT_MFS} ${swap_opts} swap ${root}${base}${mtpt}",
589 swap_opts = convert_swap_options(fsdesc.options),
591 mtpt = FileName.remove_leading_slash(mtpt)
595 -- If we got here, it must be normal and valid.
596 cmds:set_replacements{
597 dev = FileName.basename(fsdesc.device),
598 opts = fsdesc.options, -- XXX this may need further cleaning?
600 mtpt = FileName.remove_leading_slash(mtpt)
603 "cd ${root}dev && ${root}${TEST_DEV} ${dev} || " ..
604 "${root}${SH} MAKEDEV ${dev}",
605 "${root}${MOUNT} -o ${opts} ${root}dev/${dev} ${root}${base}${mtpt}"
610 is_mounted = cmds:execute()
615 -- Unmount the target system.
617 ts.unmount = function(ts)
619 (not is_mounted and not root_is_mounted) then
622 local cmds = CmdChain.new()
623 unmount_all_under(cmds)
624 if cmds:execute() then
626 root_is_mounted = false
634 -- 'Constructor' - initialize instance state.
641 base = FileName.add_trailing_slash(base)