Install a moduli(5) manual page.
[dragonfly.git] / contrib / bsdinstaller-1.1.6 / src / backend / lua / lib / target_system.lua
1 -- $Id: target_system.lua,v 1.25 2005/03/29 21:04:19 cpressey Exp $
2
3 local App = require("app")
4 local FileName = require("filename")
5 require "gettext"
6 require "cmdchain"
7 require "storage"
8 require "mountpoint"
9
10 --[[--------------]]--
11 --[[ TargetSystem ]]--
12 --[[--------------]]--
13
14 TargetSystem = {}
15
16 --
17 -- There are three general use cases for this class.
18 --
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:
24 --
25 --      local ts = TargetSystem.new(pd, "mnt")
26 --      if ts:create() then
27 --              if ts:mount() then
28 --                      ...
29 --                      ts:unmount()
30 --              end
31 --      end
32 --
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.
37 --
38 --      local ts = TargetSystem.new(pd, "mnt")
39 --      if ts:probe() then
40 --              if ts:mount() then
41 --                      ...
42 --                      ts:unmount()
43 --              end
44 --      end
45 --
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.
51 --
52 --      local ts = TargetSystem.new(pd)
53 --      if ts:use_current() then
54 --              ...
55 --      end
56 --
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?
63
64         --
65         -- Private utility helper functions.
66         --
67
68         --
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.
72         --
73         -- The third argument is generally not necessary when calling this function;
74         -- it is used only when it recursively calls itself.
75         --
76         local unmount_all_under
77         unmount_all_under = function(cmds, dirname, fs_descs)
78                 local unmount_me = false
79                 local i
80
81                 if not dirname then
82                         dirname = App.expand("${root}${base}", {
83                             base = base
84                         })
85                         dirname = FileName.remove_trailing_slash(dirname)
86                 end
87
88                 if not fs_descs then
89                         fs_descs = MountPoints.enumerate()
90                 end
91         
92                 for i, fs_desc in fs_descs do
93                         if fs_desc.mountpoint == dirname then
94                                 unmount_me = true
95                         end
96         
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)
100                         end
101                 end
102
103                 if unmount_me then
104                         cmds:add({
105                             cmdline = "${root}${UMOUNT} ${dirname}",
106                             replacements = { dirname = dirname }
107                         })
108                 end
109         end
110
111         --
112         -- Convert the options for swap-backed devices from their
113         -- fstab format to command line format.
114         --
115         local convert_swap_options = function(opts)
116                 local opt
117                 local result = ""
118
119                 for opt in string.gfind(opts, "[^,]") do
120                         --
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.
125                         --
126                         if string.find(opt, "^-[^C]") then
127                                 result = result ..
128                                     string.gsub(opt, "=", " ") .. " "
129                         end
130                 end
131                 
132                 return result
133         end
134
135         --
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...
140         --
141         local mount_root_filesystem = function(ts)
142                 local cmds, spd
143
144                 if root_is_mounted then
145                         return false, "Root filesystem is already mounted"
146                 end
147
148                 --
149                 -- Create a command chain.
150                 --
151                 cmds = CmdChain.new()
152         
153                 --
154                 -- Find the root subpartition of the partition.
155                 -- It's always the first one, called "a".
156                 --
157                 spd = pd:get_subpart_by_letter("a")
158
159                 --
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.
164                 --
165                 if not spd then
166                         return false
167                 end
168
169                 --
170                 -- Ensure that the devices we'll be using exist.
171                 --
172                 pd:get_parent():cmds_ensure_dev(cmds)
173                 pd:cmds_ensure_dev(cmds)
174                 spd:cmds_ensure_dev(cmds)
175         
176                 --
177                 -- Make sure nothing is mounted under where we want
178                 -- to mount this filesystem.
179                 --
180                 unmount_all_under(cmds)
181
182                 --
183                 -- Mount the target's root filesystem
184                 --
185                 cmds:add({
186                     cmdline = "${root}${MOUNT} ${root}dev/${dev} ${root}${base}",
187                     replacements = {
188                         dev = spd:get_device_name(),
189                         base = base
190                     }
191                 })
192                 
193                 --
194                 -- Do it.
195                 --
196                 root_is_mounted = cmds:execute()
197                 return root_is_mounted
198         end
199
200         --
201         -- Accessor methods.
202         --
203
204         ts.get_part = function(ts)
205                 return(pd)
206         end
207
208         ts.get_base = function(ts)
209                 return(base)
210         end
211
212         ts.is_mounted = function(ts)
213                 return(is_mounted)
214         end
215
216         --
217         -- Command-generating methods.
218         --
219
220         ts.cmds_set_password = function(ts, cmds, username, password)
221                 cmds:add({
222                     cmdline = "${root}${CHROOT} ${root}${base} " ..
223                               "/${PW} usermod ${username} -h 0",
224                     replacements = {
225                         base = base,
226                         username = username
227                     },
228                     desc = _("Setting password for user `%s'...", username),
229                     input = password .. "\n",
230                     sensitive = password
231                 })
232         end
233
234         ts.cmds_add_user = function(ts, cmds, tab)
235
236                 local add_flag = function(flag, setting)
237                         if setting ~= nil and setting ~= "" then
238                                 return flag .. " " .. tostring(setting)
239                         else
240                                 return ""
241                         end
242                 end
243
244                 local home_skel = ""
245                 if not tab.home or not FileName.is_dir(tab.home) then
246                         home_skel = "-m -k /usr/share/skel"
247                 end
248
249                 cmds:add({
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}",
253                     replacements = {
254                         base = base,
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
263                     },
264                 })
265
266                 if tab.password then
267                         ts:cmds_set_password(cmds, tab.username, tab.password)
268                 end
269         end
270
271         --
272         -- Create commands to copy files and directories to the traget system.
273         --
274         ts.cmds_install_srcs = function(ts, cmds, srclist)
275                 local i, src, dest, spd
276
277                 --
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.)
283                 --
284                 local is_valid_mountpoint = function(root, mountpoint)
285                         local seen_it = false
286                         local i, src
287                 
288                         local starts_with = function(str, prefix)
289                                 return string.sub(str, string.len(prefix)) == prefix
290                         end
291                 
292                         for i, src in ipairs(srclist) do
293                                 if starts_with(mountpoint, root .. src) then
294                                         seen_it = true
295                                 end
296                                 if starts_with(root .. src, mountpoint) then
297                                         return false
298                                 end
299                         end
300                         
301                         return seen_it
302                 end
303
304                 for i, src in ipairs(srclist) do
305                         --
306                         -- Create intermediate directories as needed.
307                         --
308                         cmds:add{
309                             cmdline = "${root}${MKDIR} -p ${root}${base}${src_dir}",
310                             replacements = {
311                                 base = base,
312                                 src_dir = FileName.dirname(src)
313                             }
314                         }
315
316                         --
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.
322                         --
323                         dest = src
324                         if FileName.is_dir(App.dir.root .. src .. ".hdd") or
325                            FileName.is_file(App.dir.root .. src .. ".hdd") then
326                                 src = src .. ".hdd"
327                         end
328
329                         --
330                         -- Cpdup the chosen source onto the HDD.
331                         --
332                         cmds:add({
333                             cmdline = "${root}${CPDUP} ${root}${src} ${root}${base}${dest}",
334                             replacements = {
335                                 base = base,
336                                 src = src,
337                                 dest = dest
338                             },
339                             log_mode = CmdChain.LOG_QUIET       -- don't spam log
340                         })
341                 end
342
343                 --
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.)
347                 --
348                 for spd in pd:get_subparts() do
349                         --
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
353                         --
354                         
355                         --
356                         -- This assumes that the subpartition descriptors
357                         -- have mountpoints associated with them, which should
358                         -- (nowadays) always be the case.
359                         --
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
363                                         --
364                                         -- Cpdup the subpartition.
365                                         --
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.
370                                         --
371                                         cmds:add{
372                                             cmdline = "${root}${CPDUP} ${root}${mtpt} " ..
373                                                 "${root}${base}${mtpt}",
374                                             replacements = {
375                                                 base = base,
376                                                 mtpt = spd:get_mountpoint()
377                                             },
378                                             log_mode = CmdChain.LOG_QUIET
379                                         }
380                                 end
381                         end
382                 end
383         end
384
385         --
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.
389         --
390         ts.create = function(ts)
391                 local spd, cmds
392
393                 --
394                 -- Mount the target system's root filesystem,
395                 -- if not already mounted
396                 --
397                 if not root_is_mounted then
398                         if not mount_root_filesystem() then
399                                 return false
400                         end
401                 end
402
403                 --
404                 -- Create mount points for later mounting of subpartitions.
405                 --
406                 cmds = CmdChain.new()
407                 fstab = {}
408                 for spd in pd:get_subparts() do
409                         local mtpt = spd:get_mountpoint()
410                         local dev = spd:get_device_name()
411
412                         cmds:set_replacements{
413                                 base = base,
414                                 dev = dev,
415                                 mtpt = FileName.remove_leading_slash(mtpt)
416                         }
417
418                         if spd:is_swap() then
419                                 if App.option.enable_crashdumps and
420                                    spd:get_capacity() >= App.state.store:get_ram() then
421                                         --
422                                         -- Set this subpartition as the dump device.
423                                         --
424                                         cmds:add("${root}${DUMPON} -v ${root}dev/${dev}")
425                                         App.state.crash_device = mnt_dev
426                                 end
427                                 fstab[mtpt] = {
428                                     device  = "/dev/" .. dev,
429                                     fstype  = "swap",
430                                     options = "sw",
431                                     dump    = 0,
432                                     pass    = 0
433                                 }
434                         else
435                                 if spd:get_mountpoint() ~= "/" then
436                                         cmds:add("${root}${MKDIR} -p ${root}${base}${mtpt}")
437                                 end
438                                 fstype = "ufs"
439                                 opts = "rw"
440                                 fstab[mtpt] = {
441                                     device  = "/dev/" .. dev,
442                                     fstype  = "ufs",
443                                     options = "rw",
444                                     dump    = 2,
445                                     pass    = 2
446                                 }
447                                 if mtpt == "/" then
448                                         fstab[mtpt].dump = 1
449                                         fstab[mtpt].pass = 1
450                                 end
451                         end
452                 end
453                 return cmds:execute()
454         end
455
456         --
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.
460         --
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.
464         --
465         ts.probe = function(ts)
466                 local fstab_filename, fstab_file, errmsg
467                 local spd
468
469                 --
470                 -- Mount the target system's root filesystem,
471                 -- if not already mounted.
472                 --
473                 if not root_is_mounted then
474                         if not mount_root_filesystem() then
475                                 return nil, "Could not mount / of target system."
476                         end
477                 end
478
479                 --
480                 -- Open the target system's fstab and parse it.
481                 --
482                 fstab_filename = App.expand("${root}${base}etc/fstab", {
483                     base = base
484                 })
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."
488                 end
489
490                 fstab = {}
491                 line = fstab_file:read()
492                 while line do
493                         --
494                         -- Parse the fstab line.
495                         --
496                         if string.find(line, "^%s*#") then
497                                 -- comment: skip it
498                         elseif string.find(line, "^%s*$") then
499                                 -- blank line: skip it
500                         else
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]*)")
504                                 if not found then
505                                         App.log("Warning: malformed line in fstab: " ..
506                                             line)
507                                 else
508                                         fstab[mtpt] = {
509                                             device  = dev,
510                                             fstype  = fstype,
511                                             options = opts,
512                                             dump    = dump,
513                                             pass    = pass
514                                         }
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.
519                                         elseif not spd then
520                                                 -- This can happen if e.g.
521                                                 -- the user has included a
522                                                 -- subpartition from another
523                                                 -- drive in their fstab.
524                                         else
525                                                 -- Associate mountpoint.
526                                                 spd:set_mountpoint(mtpt)
527                                         end
528                                 end
529                         end
530                         line = fstab_file:read()
531                 end
532                 fstab_file:close()
533
534                 return fstab
535         end
536
537         ts.use_current = function(ts)
538                 using_current = true
539                 base = "/"
540                 return true
541         end
542
543         --
544         -- Mount the system on the given partition into the given mount
545         -- directory (typically "mnt".)
546         --
547         ts.mount = function(ts)
548                 local cmds, i, mtpt, mtpts, fsdesc
549
550                 if using_current or is_mounted then
551                         return true
552                 end
553
554                 if not root_is_mounted or fstab == nil then
555                         return false
556                 end
557
558                 --
559                 -- Go through each of the mountpoints in our fstab,
560                 -- and if it looks like we should, try mount it under base.
561                 --
562         
563                 mtpts = {}
564                 for mtpt, fsdesc in fstab do
565                         table.insert(mtpts, mtpt)
566                 end
567                 table.sort(mtpts)
568
569                 cmds = CmdChain.new()
570                 for i, mtpt in mtpts do
571                         fsdesc = fstab[mtpt]
572                         if mtpt == "/" then
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.
585                                 
586                                 cmds:add({
587                                     cmdline = "${root}${MOUNT_MFS} ${swap_opts} swap ${root}${base}${mtpt}",
588                                     replacements = {
589                                         swap_opts = convert_swap_options(fsdesc.options),
590                                         base = base,
591                                         mtpt = FileName.remove_leading_slash(mtpt)
592                                     }
593                                 })
594                         else
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?
599                                     base = base,
600                                     mtpt = FileName.remove_leading_slash(mtpt)
601                                 }
602                                 cmds:add(
603                                     "cd ${root}dev && ${root}${TEST_DEV} ${dev} || " ..
604                                         "${root}${SH} MAKEDEV ${dev}",
605                                     "${root}${MOUNT} -o ${opts} ${root}dev/${dev} ${root}${base}${mtpt}"
606                                 )
607                         end
608                 end
609         
610                 is_mounted = cmds:execute()
611                 return is_mounted
612         end
613
614         --
615         -- Unmount the target system.
616         --
617         ts.unmount = function(ts)
618                 if using_current or
619                    (not is_mounted and not root_is_mounted) then
620                         return true
621                 end
622                 local cmds = CmdChain.new()
623                 unmount_all_under(cmds)
624                 if cmds:execute() then
625                         is_mounted = false
626                         root_is_mounted = false
627                         return true
628                 else
629                         return false
630                 end
631         end
632
633         --
634         -- 'Constructor' - initialize instance state.
635         --
636
637         --
638         -- Fix up base.
639         --
640         base = base or ""
641         base = FileName.add_trailing_slash(base)
642
643         return ts
644 end