ec5946be744821dcc1bc79c5dd05f836dcc526d7
[dragonfly.git] / contrib / bsdinstaller-1.1.6 / src / backend / lua / lib / Capacity.lua
1 -- lib/storage.lua
2 -- $Id: Capacity.lua,v 1.76 2005/03/29 21:04:19 cpressey Exp $
3 -- Storage Descriptors (a la libinstaller) in Lua.
4
5 -- BEGIN lib/storage.lua --
6
7 local App = require("app")
8 local FileName = require("filename")
9 require "cmdchain"
10 require "bitwise"
11 require "mountpoint"
12
13 --
14 -- Note: these methods should try to use consistent terminology:
15 --
16 -- 'capacity' of an object refers to its capacity in megabytes.
17 -- 'size' of an object refers to its size in blocks or sectors
18 --   (which are assumed to be 512 bytes each.)
19 -- 'capstring' refers to a string which includes a unit suffix:
20 --   'M' for megabytes
21 --   'G' for gigabytes
22 --   '*' to indicate 'use the remaining space on the device'
23 --
24
25 --[[-------------------]]--
26 --[[ StorageDescriptor ]]--
27 --[[-------------------]]--
28
29 -- This class returns an object which can represent
30 -- the system's data storage capabilities.
31
32 StorageDescriptor = {}
33 StorageDescriptor.new = function()
34         local disk = {}         -- disks in this storage descriptor
35         local ram = 0           -- in megabytes
36         local sd = {}           -- instance variable
37
38         -- Internal function.
39         local next_power_of_two = function(n)
40                 local i = 1
41                 n = math.ceil(n)
42
43                 while i < n and i >= 1 do
44                         i = i * 2
45                 end
46
47                 if i > n then
48                         return i
49                 else
50                         return n
51                 end
52         end
53
54         -- Now set up this object's interface functions
55
56         -- Look through `dmesg', `atacontrol list', etc, and
57         -- populate disks and ram with values
58         -- that are as accurate and readable as possible.
59         -- XXX not yet fully implemented.
60         sd.survey = function(sd)
61                 local pty, line
62                 local disk_name, dd
63                 local cmd
64                 local found, len, cap
65
66                 cmd = App.expand("${root}${SYSCTL} -n hw.physmem")
67                 pty = Pty.open(cmd)
68                 line = pty:readline()
69                 pty:close()
70                 
71                 App.log("`" .. cmd .. "` returned: " .. line)
72
73                 ram = next_power_of_two(tonumber(line) / (1024 * 1024))
74
75                 cmd = App.expand("${root}${SYSCTL} -n kern.disks")
76                 pty = Pty.open(cmd)
77                 line = pty:readline()
78                 pty:close()
79
80                 App.log("`" .. cmd .. "` returned: " .. line)
81
82                 for disk_name in string.gfind(line, "%s*(%w+)") do
83                         if not string.find(disk_name, "^md") then
84                                 disk[disk_name] = DiskDescriptor.new(sd, disk_name)
85                         end
86                 end
87
88                 for line in io.lines("/var/run/dmesg.boot") do
89                         for disk_name, dd in disk do
90                                 found, len, cap =
91                                     string.find(line, "^" .. disk_name .. ":%s*(.*)$")
92                                 if found then
93                                         dd:set_desc(disk_name .. ": " .. cap)
94                                 end
95                         end
96                 end
97
98                 cmd = App.expand("${root}${ATACONTROL} list")
99                 pty = Pty.open(cmd)
100                 line = pty:readline()
101                 while line do
102                         for disk_name, dd in disk do
103                                 found, len, cap =
104                                     string.find(line, "^%s*Master:%s*" ..
105                                       disk_name .. "%s*(%<.*%>)$")
106                                 if not found then
107                                         found, len, cap =
108                                             string.find(line, "^%s*Slave:%s*" ..
109                                               disk_name .. "%s*(%<.*%>)$")
110                                 end
111                                 if found then
112                                         dd:set_desc(disk_name .. ": " .. cap)
113                                 end
114                         end
115                         line = pty:readline()
116                 end
117                 pty:close()
118         end
119
120         -- Refresh our view of the storage connected to the
121         -- system, but remember what disk and/or partition
122         -- was selected as well.
123         sd.resurvey = function(sd, sel_disk, sel_part)
124                 local sel_disk_name, sel_part_no
125
126                 if sel_disk then
127                         sel_disk_name = sel_disk:get_name()
128                 end
129
130                 if sel_part then
131                         sel_part_no = sel_part:get_number()
132                 end
133
134                 sd:survey()
135
136                 if sel_disk then
137                         sel_disk = sd:get_disk_by_name(sel_disk_name)
138                         if not sel_disk then
139                                 -- XXX warn that sel disk was lost!
140                         end
141                 end
142
143                 if sel_disk and sel_part then
144                         sel_part = sel_disk:find_part_by_number(sel_part_no)
145                         if not sel_part then
146                                 -- XXX warn that sel part was lost!
147                         end
148                 end
149
150                 return sel_disk, sel_part
151         end
152
153         -- Return an iterator which yields the next next
154         -- DiskDescriptor object in this StorageDescriptor
155         -- each time it is called (typically in a for loop.)
156         sd.get_disks = function(sd)
157                 local disk_name, dd
158                 local list = {}
159                 local i, n = 0, 0
160                 
161                 for disk_name, dd in disk do
162                         table.insert(list, dd)
163                         n = n + 1
164                 end
165                 
166                 table.sort(list, function(a, b)
167                         return a:get_name() < b:get_name()
168                 end)
169
170                 return function()
171                         if i <= n then
172                                 i = i + 1
173                                 return list[i]
174                         end
175                 end
176         end
177
178         -- Given the name of a disk, return that disk descriptor,
179         -- or nil if no disk by that name was found.
180         sd.get_disk_by_name = function(sd, name)
181                 local dd
182
183                 for dd in sd:get_disks() do
184                         if dd:get_name() == name then
185                                 return dd
186                         end
187                 end
188                 
189                 return nil
190         end
191
192         sd.get_disk_count = function(sd)
193                 local disk_name, dd
194                 local n = 0
195
196                 for disk_name, dd in disk do
197                         n = n + 1
198                 end
199                 
200                 return n
201         end
202
203         sd.get_ram = function(sd)                       -- in megabytes
204                 return ram
205         end
206
207         sd.get_activated_swap = function(sd)            -- in megabytes
208                 local pty, line
209                 local swap = 0
210                 local found, len, devname, amount
211                 
212                 pty = Pty.open(App.expand("${root}${SWAPINFO} -k"))
213                 line = pty:readline()
214                 while line do
215                         if not string.find(line, "^Device") then
216                                 found, len, devname, amount =
217                                     string.find(line, "^([^%s]+)%s+(%d+)")
218                                 swap = swap + tonumber(amount)
219                         end
220                         line = pty:readline()
221                 end
222                 pty:close()
223                 
224                 return math.floor(swap / 1024)
225         end
226
227         sd.dump = function(sd)
228                 local disk_name, dd
229
230                 print("*** DUMP of StorageDescriptor ***")
231                 for disk_name, dd in disk do
232                         dd:dump()
233                 end
234         end
235
236         return sd
237 end
238
239 --
240 -- The following global static utility functions in the
241 -- StorageDescriptor class are really human interface functions;
242 -- they might be better placed elsewhere.
243 --
244
245 --
246 -- Take a capstring and return a number indicating size in blocks.
247 -- If the capstring is "*", the supplied remainder is returned.
248 -- If the capstring could not be parsed, returns nil.
249 --
250 StorageDescriptor.parse_capstring = function(str, remainder)
251         if str == "*" then
252                 return remainder
253         else
254                 local suffix = string.sub(str, -1, -1)
255                 local body = string.sub(str, 1, string.len(str) - 1)
256                                 
257                 if suffix == "G" or suffix == "g" then
258                         return math.floor(tonumber(body) * 1024 * 1024 * 2)
259                 elseif suffix == "M" or suffix == "m" then
260                         return math.floor(tonumber(body) * 1024 * 2)
261                 else
262                         -- bad suffix
263                         return nil
264                 end
265         end
266 end
267
268 --
269 -- Takes a number specifying a size in blocks and
270 -- convert it to a capstring.
271 --
272 StorageDescriptor.format_capstring = function(blocks)
273         if blocks >= 1024 * 1024 * 2 then
274                 return tostring(math.floor(blocks / (1024 * 1024 * 2) * 100) / 100) .. "G"
275         else
276                 return tostring(math.floor(blocks / (1024 * 2) * 100) / 100) .. "M"
277         end
278 end
279
280
281 --[[----------------]]--
282 --[[ DiskDescriptor ]]--
283 --[[----------------]]--
284
285 DiskDescriptor = {}
286 DiskDescriptor.new = function(parent, name)
287         local dd = {}           -- instance variable
288         local part = {}         -- private: partitions on this disk
289         local desc = name       -- private: description of disk
290         local cyl, head, sec    -- private: geometry of disk
291         local touched = false   -- private: whether we formatted it
292
293         -- Set up this object instance's interface functions first:
294
295         dd.get_parent = function(dd)
296                 return parent
297         end
298
299         dd.get_name = function(dd)
300                 return name
301         end
302
303         dd.set_desc = function(dd, new_desc)
304                 --
305                 -- Calculate a score for how well this string describes
306                 -- a disk.  Reject obviously bogus descriptions (usually
307                 -- erroneously harvested from error messages in dmesg.)
308                 --
309                 local calculate_score = function(s)
310                         local score = 0
311
312                         -- In the absence of any good discriminator,
313                         -- the longest disk description wins.
314                         score = string.len(s)
315
316                         -- Look for clues
317                         if string.find(s, "%d+MB") then
318                                 score = score + 10
319                         end
320                         if string.find(s, "%<.*%>") then
321                                 score = score + 10
322                         end
323                         if string.find(s, "%[%d+%/%d+%/%d+%]") then
324                                 score = score + 10
325                         end
326
327                         -- Look for error messages
328                         if string.find(s, "resetting") then
329                                 score = 0
330                         end
331
332                         return score
333                 end
334
335                 if calculate_score(new_desc) > calculate_score(desc) then
336                         desc = new_desc
337                 end
338         end
339
340         dd.get_desc = function(dd)
341                 return desc
342         end
343
344         dd.get_geometry = function(dd)
345                 return cyl, head, sec
346         end
347
348         dd.get_device_name = function(dd)
349                 return name
350         end
351
352         dd.get_raw_device_name = function(dd)
353                 -- XXX depends on operating system
354                 return name
355         end
356
357         -- Return an iterator which yields the next next
358         -- PartitionDescriptor object in this DiskDescriptor
359         -- each time it is called (typically in a for loop.)
360         dd.get_parts = function(dd)
361                 local i, n = 0, table.getn(part)
362
363                 return function()
364                         if i <= n then
365                                 i = i + 1
366                                 return part[i]
367                         end
368                 end
369         end
370
371         -- Given the number of a partition, return that
372         -- partition descriptor, or nil if not found.
373         dd.get_part_by_number = function(dd, number)
374                 local pd
375
376                 for pd in dd:get_parts() do
377                         if pd:get_number() == number then
378                                 return pd
379                         end
380                 end
381
382                 return nil
383         end
384
385         dd.get_part_count = function(dd)
386                 return table.getn(part)
387         end
388
389         -- return the disk's capacity in megabytes.
390         -- this is actually the sum of the capacities of the
391         -- partitions on this disk.
392         dd.get_capacity = function(dd)
393                 local pd
394                 local cap = 0
395
396                 for pd in dd:get_parts() do
397                         cap = cap + pd:get_capacity()
398                 end
399                 
400                 return cap
401         end
402
403         -- return the disk's raw size in sectors.
404         dd.get_raw_size = function(dd)
405                 pty = Pty.open(App.expand(
406                     "${root}${FDISK} -t -I " ..
407                     dd:get_raw_device_name()))
408                 line = pty:readline()
409                 while line do
410                         local found, len, start, size =
411                             string.find(line, "start%s*(%d+)%s*,%s*size%s*(%d+)")
412                         if found then
413                                 pty:close()
414                                 return tonumber(start) + tonumber(size)
415                         end
416                         line = pty:readline()
417                 end
418                 pty:close()
419
420                 return nil
421         end
422
423         dd.touch = function(dd)
424                 touched = true
425         end
426
427         dd.has_been_touched = function(dd)
428                 return touched
429         end
430
431         --
432         -- Determine whether any subpartition from any partition of this
433         -- disk is mounted somewhere in the filesystem.
434         --
435         dd.is_mounted = function(dd)
436                 local fs_descs = MountPoints.enumerate()
437                 local i, fs_desc, dev_name
438
439                 dev_name = dd:get_device_name()
440                 for i, fs_desc in fs_descs do
441                         if string.find(fs_desc.device, dev_name, 1, true) then
442                                 return true
443                         end
444                 end
445
446                 return false
447         end
448
449         --
450         -- Methods to manipulate the contents of this DiskDescriptor.
451         --
452
453         dd.clear_parts = function(dd)
454                 part = {}
455         end
456
457         dd.add_part = function(dd, pd)
458                 part[pd:get_number()] = pd
459                 -- pd:set_parent(dd)
460         end
461
462         --
463         -- Methods to add appropriate commands to CmdChains.
464         --
465
466         -- Commands to ensure this device exists.
467         dd.cmds_ensure_dev = function(dd, cmds)
468                 cmds:add({
469                     cmdline = "cd ${root}dev && ${root}${TEST_DEV} ${dev} || " ..
470                               "${root}${SH} MAKEDEV ${dev}",
471                     replacements = {
472                         dev = FileName.basename(dd:get_device_name())
473                     }
474                 })
475         end
476
477         -- Commands to format this disk.
478         dd.cmds_format = function(dd, cmds)
479                 dd:cmds_ensure_dev(cmds)
480
481                 --
482                 -- Currently you need to pass 'yes' to OpenBSD's fdisk to
483                 -- be able to do these.  (This is a shot in the dark:)
484                 --
485                 if App.os.name == "OpenBSD" then
486                         cmds:add("${root}${ECHO} 'yes\nyes\nyes\n' | " ..
487                             "${root}${FDISK} -I " ..
488                             dd:get_raw_device_name())
489                         cmds:add("${root}${ECHO} 'yes\nyes\nyes\n' | " ..
490                             "${root}${FDISK} -B " ..
491                             dd:get_raw_device_name())
492                 else
493                         cmds:add("${root}${FDISK} -I " ..
494                             dd:get_raw_device_name())
495                         cmds:add("${root}${YES} | ${root}${FDISK} -B " ..
496                             dd:get_raw_device_name())
497                 end
498         end
499
500         -- Commands to partition this disk.
501         dd.cmds_partition = function(dd, cmds)
502                 local i, pd
503                 local active_part_no
504                 local cyl, head, sec = dd:get_geometry()
505
506                 dd:cmds_ensure_dev(cmds)
507
508                 cmds:add({
509                     cmdline = "${root}${ECHO} 'g c${cyl} h${head} s${sec}' >${tmp}new.fdisk",
510                     replacements = {
511                         cyl = cyl,
512                         head = head,
513                         sec = sec
514                     }
515                 })
516
517                 i = 1
518                 while i <= 4 do
519                         local sysid, start, size = 0, 0, 0
520
521                         pd = dd:get_part_by_number(i)
522                         if pd then
523                                 sysid = pd:get_sysid()
524                                 start = pd:get_start()
525                                 size  = pd:get_size()
526                                 if pd:is_active() then
527                                         active_part_no = pd:get_number()
528                                 end
529                         end
530
531                         cmds:add({
532                             cmdline = "${root}${ECHO} 'p ${number} ${sysid} ${start} ${size}' >>${tmp}new.fdisk",
533                             replacements = {
534                                 number = i,
535                                 sysid = sysid,
536                                 start = start,
537                                 size = size
538                             }
539                         })
540
541                         i = i + 1
542                 end
543
544                 if active_part_no then
545                         cmds:add({
546                             cmdline = "${root}${ECHO} 'a ${number}' >>${tmp}new.fdisk",
547                             replacements = {
548                                 number = active_part_no
549                             }
550                         })
551                 end
552
553                 cmds:add("${root}${CAT} ${tmp}new.fdisk")
554
555                 App.register_tmpfile("new.fdisk")
556         
557                 --
558                 -- Execute the fdisk script.
559                 --
560                 cmds:add("${root}${FDISK} -v -f ${tmp}new.fdisk " ..
561                     dd:get_raw_device_name())
562         end
563
564         dd.cmds_install_bootblock = function(dd, cmds, packet_mode)
565                 local o = " "
566                 if packet_mode then
567                         o = "-o packet "
568                 end
569                 cmds:add(
570                     {
571                         cmdline = "${root}${BOOT0CFG} -B " ..
572                             o .. dd:get_raw_device_name(),
573                         failure = CmdChain.FAILURE_WARN,
574                         tag = dd
575                     },
576                     {
577                         cmdline = "${root}${BOOT0CFG} -v " ..
578                             dd:get_raw_device_name(),
579                         failure = CmdChain.FAILURE_WARN,
580                         tag = dd
581                     }
582                 )
583         end
584
585         dd.cmds_wipe_start = function(dd, cmds)
586                 dd:cmds_ensure_dev(cmds)
587                 cmds:add("${root}${DD} if=${root}dev/zero of=${root}dev/" ..
588                     dd:get_raw_device_name() .. " bs=32k count=16")
589         end
590
591         dd.dump = function(dd)
592                 local part_no
593
594                 print("\t" .. name .. ": " .. cyl .. "/" .. head .. "/" .. sec .. ": " .. desc)
595                 for part_no in part do
596                         part[part_no]:dump()
597                 end
598         end
599
600         -- 'Constructor' - initialize our private state.
601         -- Try to find out what we can about ourselves from fdisk.
602
603         local pty, line, found, len
604
605         -- Get the geometry from 'fdisk'.
606         pty = Pty.open(App.expand("${root}${FDISK} " .. name))
607         line = pty:readline()
608         while line and not found do
609                 found = string.find(line, "^%s*parameters to be used for BIOS")
610                 line = pty:readline()
611         end
612
613         if found then
614                 found, len, cyl, head, sec =
615                     string.find(line, "^%s*cylinders=(%d+)%s*heads=(%d+)%s*" ..
616                                       "sectors/track=(%d+)")
617                 cyl = tonumber(cyl)
618                 head = tonumber(head)
619                 sec = tonumber(sec)
620         end
621         pty:close()
622
623         if not found then
624                 App.log("Warning!  Could not determine geometry of disk " .. name .. "!")
625                 return nil
626         end
627
628         App.log("New Disk: " .. name .. ": " .. cyl .. "/" .. head .. "/" .. sec)
629
630         -- Get the partitions from 'fdisk -s'.
631         pty = Pty.open(App.expand("${root}${FDISK} -s " .. name))
632         line = pty:readline()  -- geometry - we already have it
633         line = pty:readline()   -- headings, just ignore
634         line = pty:readline()
635         while line do
636                 local part_no, start, size, sysid, flags
637                 found, len, part_no, start, size, sysid, flags =
638                     string.find(line, "^%s*(%d+):%s*(%d+)%s*(%d+)" ..
639                                       "%s*0x(%x+)%s*0x(%x+)%s*$")
640                 if found then
641                         part_no = tonumber(part_no)
642                         part[part_no] = PartitionDescriptor.new{
643                             parent = dd,
644                             number = part_no,
645                             start  = tonumber(start),
646                             size   = tonumber(size),
647                             sysid  = tonumber(sysid, 16),
648                             flags  = tonumber(flags, 16)
649                         }
650                 end
651                 line = pty:readline()
652         end
653         pty:close()
654
655         return dd
656 end
657
658 --[[---------------------]]--
659 --[[ PartitionDescriptor ]]--
660 --[[---------------------]]--
661
662 PartitionDescriptor = {}
663 PartitionDescriptor.new = function(params)
664         local pd = {}           -- instance variable
665         local subpart = {}      -- subpartitions on this partition
666
667         local parent = assert(params.parent)
668         local number = assert(params.number)
669         local start  = assert(params.start)
670         local size   = assert(params.size)
671         local sysid  = assert(params.sysid)
672         local flags  = params.flags
673         if params.active ~= nil then
674                 if params.active then
675                         flags = 256
676                 else
677                         flags = 0
678                 end
679         end
680         assert(type(flags) == "number")
681
682         -- First set up this object's interface functions
683
684         pd.get_parent = function(pd)
685                 return parent
686         end
687
688         pd.get_number = function(pd)
689                 return number
690         end
691
692         pd.get_params = function(pd)
693                 return start, size, sysid, flags
694         end
695
696         pd.get_start = function(pd)
697                 return start
698         end
699
700         pd.get_size = function(pd)
701                 return size
702         end
703
704         pd.get_sysid = function(pd)
705                 return sysid
706         end
707
708         pd.get_flags = function(pd)
709                 return flags
710         end
711
712         -- 'size' is the partition size, in blocks.
713         -- return the partition's capacity in megabytes.
714         pd.get_capacity = function(pd)
715                 return math.floor(size / 2048)
716         end
717
718         pd.is_active = function(pd)
719                 return Bitwise.bw_and(flags, 256) == 256
720         end
721                 
722         pd.get_desc = function(pd)
723                 return tostring(number) .. ": " ..
724                     tostring(pd:get_capacity()) .. "M (" ..
725                     tostring(start) .. "-" .. tostring(start + size) ..
726                     ") id=" .. sysid
727         end
728
729         pd.get_device_name = function(pd)
730                 return parent.get_name() .. "s" .. number
731         end
732
733         pd.get_raw_device_name = function(pd)
734                 -- XXX depends on operating system
735                 return parent.get_name() .. "s" .. number -- .. "c"
736         end
737
738         pd.get_activated_swap = function(pd)    -- in megabytes
739                 local pty, line
740                 local swap = 0
741                 local found, len, devname, amount
742                 
743                 pty = Pty.open(App.expand("${root}${SWAPINFO} -k"))
744                 line = pty:readline()
745                 while line do
746                         if not string.find(line, "^Device") then
747                                 found, len, devname, amount =
748                                     string.find(line, "^([^%s]+)%s+(%d+)")
749                                 if string.find(devname, pd:get_device_name()) then
750                                         swap = swap + tonumber(amount)
751                                 end
752                         end
753                         line = pty:readline()
754                 end
755                 pty:close()
756                 
757                 return math.floor(swap / 1024)
758         end
759
760         -- Return an iterator which yields the next next
761         -- PartitionDescriptor object in this DiskDescriptor
762         -- each time it is called (typically in a for loop.)
763         pd.get_subparts = function(pd)
764                 local letter, spd
765                 local list = {}
766                 local i, n = 0, 0
767                 
768                 for letter, spd in subpart do
769                         table.insert(list, spd)
770                 end
771
772                 table.sort(list, function(a, b)
773                         -- not sure why we ever get a nil here, but we do:
774                         if not a and not b then return false end
775
776                         return a:get_letter() < b:get_letter()
777                 end)
778
779                 n = table.getn(list)
780
781                 return function()
782                         if i <= n then
783                                 i = i + 1
784                                 return list[i]
785                         end
786                 end
787         end
788         
789         pd.clear_subparts = function(pd)
790                 subpart = {}
791         end
792         
793         pd.add_subpart = function(pd, spd)
794                 subpart[spd:get_letter()] = spd
795                 -- spd:set_parent(pd)
796         end
797
798         pd.get_subpart_by_letter = function(pd, letter)
799                 return subpart[letter]
800         end
801
802         pd.get_subpart_by_mountpoint = function(pd, mountpoint)
803                 local letter, spd
804                 
805                 for letter, spd in subpart do
806                         if spd:get_mountpoint() == mountpoint then
807                                 return spd
808                         end
809                 end
810                 
811                 return nil
812         end
813
814         pd.get_subpart_by_device_name = function(pd, device_name)
815                 local letter, spd
816
817                 -- Strip any leading /dev/ or whatever.
818                 device_name = FileName.basename(device_name)
819
820                 for letter, spd in subpart do
821                         if spd:get_device_name() == device_name then
822                                 return spd
823                         end
824                 end
825                 
826                 return nil
827         end
828
829         --
830         -- Determine whether any subpartition of this
831         -- partition is mounted somewhere in the filesystem.
832         --
833         pd.is_mounted = function(pd)
834                 local fs_descs = MountPoints.enumerate()
835                 local i, fs_desc, dev_name
836
837                 dev_name = pd:get_device_name()
838                 for i, fs_desc in fs_descs do
839                         if string.find(fs_desc.device, dev_name, 1, true) then
840                                 return true
841                         end
842                 end
843
844                 return false
845         end
846         
847         --
848         -- Methods to add appropriate commands to CmdChains.
849         --
850
851         -- Commands to ensure this device exists.
852         pd.cmds_ensure_dev = function(pd, cmds)
853                 cmds:add({
854                     cmdline = "cd ${root}dev && ${root}${TEST_DEV} ${dev} || " ..
855                               "${root}${SH} MAKEDEV ${dev}",
856                     replacements = {
857                         dev = FileName.basename(pd:get_device_name())
858                     }
859                 })
860         end
861
862         -- Commands to format this partition.
863         pd.cmds_format = function(pd, cmds)
864                 pd:cmds_ensure_dev(cmds)
865
866                 -- The information in parent NEEDS to be accurate here!
867                 -- Presumably we just did a survey_storage() recently.
868         
869                 --
870                 -- Set the slice's sysid to 165.
871                 --
872
873                 local cyl, head, sec = parent:get_geometry()
874                 local start, size, sysid, flags = pd:get_params()
875
876                 cmds:set_replacements{
877                     cyl = cyl,
878                     head = head,
879                     sec = sec,
880                     number = pd:get_number(),
881                     sysid = 165,
882                     start = start,
883                     size = size,
884                     dev = pd:get_raw_device_name(),
885                     parent_dev = parent:get_raw_device_name()
886                 }
887
888                 cmds:add(
889                     "${root}${ECHO} 'g c${cyl} h${head} s${sec}' >${tmp}new.fdisk",
890                     "${root}${ECHO} 'p ${number} ${sysid} ${start} ${size}' >>${tmp}new.fdisk"
891                 )
892
893                 if pd:is_active() then
894                         cmds:add("${root}${ECHO} 'a ${number}' >>${tmp}new.fdisk")
895                 end
896
897                 App.register_tmpfile("new.fdisk")
898         
899                 --
900                 -- Dump the fdisk script to the log for debugging.
901                 -- Execute the fdisk script
902                 -- Auto-disklabel the slice.
903                 -- Remove any old disklabel that might be hanging around.
904                 --
905                 cmds:add(
906                     "${root}${CAT} ${tmp}new.fdisk",
907                     "${root}${FDISK} -v -f ${tmp}new.fdisk ${parent_dev}",
908                     "${root}${DISKLABEL} -B -r -w ${dev} auto",
909                     "${root}${RM} -f ${tmp}install.disklabel.${parent_dev}"
910                 )
911         end
912
913         pd.cmds_wipe_start = function(pd, cmds)
914                 pd:cmds_ensure_dev(cmds)
915                 cmds:add(
916                     "${root}${DD} if=${root}dev/zero of=${root}dev/" ..
917                     pd:get_raw_device_name() .. " bs=32k count=16"
918                 )
919         end
920
921         pd.cmds_install_bootstrap = function(pd, cmds)
922                 pd:cmds_ensure_dev(cmds)
923                 --
924                 -- NB: one cannot use "/dev/adXsY" here -
925                 -- it must be in the form "adXsY".
926                 --
927                 cmds:add(
928                     "${root}${DISKLABEL} -B " ..
929                     pd:get_raw_device_name()
930                 )
931                 return cmds
932         end
933
934         pd.cmds_disklabel = function(pd, cmds)
935                 -- Disklabel the given partition with the
936                 -- subpartitions attached to it.
937
938                 local num_parts = 8
939                 if App.os.name == "DragonFly" then
940                         num_parts = 16
941                 end
942
943                 cmds:set_replacements{
944                     part = pd:get_device_name(),
945                     num_parts = tostring(num_parts)
946                 }
947
948                 if not FileName.is_file(App.dir.tmp .. "install.disklabel" .. pd:get_device_name()) then
949                         --
950                         -- Get a copy of the 'virgin' disklabel.
951                         -- XXX It might make more sense for this to
952                         -- happen right after format_slice() instead.
953                         --
954                         cmds:add("${root}${DISKLABEL} -r ${part} >${tmp}install.disklabel.${part}")
955                 end
956
957                 --
958                 -- Weave together a new disklabel out the of the 'virgin'
959                 -- disklabel, and the user's subpartition choices.
960                 --
961
962                 --
963                 -- Take everything from the 'virgin' disklabel up until the
964                 -- '8 or 16 partitions' line, which looks like:
965                 --
966                 -- 8 or 16 partitions:
967                 -- #        size   offset    fstype   [fsize bsize bps/cpg]
968                 -- c:  2128833        0    unused        0     0        # (Cyl.    0 - 2111*)
969                 --
970         
971                 cmds:add(
972                     "${root}${AWK} '$2==\"partitions:\" || cut { cut = 1 } !cut { print $0 }' " ..
973                       "<${tmp}install.disklabel.${part} >${tmp}install.disklabel",
974                     "${root}${ECHO} '${num_parts} partitions:' >>${tmp}install.disklabel",
975                     "${root}${ECHO} '#        size   offset    fstype   [fsize bsize bps/cpg]' " ..
976                       ">>${tmp}install.disklabel"
977                 )
978
979                 --
980                 -- Write a line for each subpartition the user wants.
981                 --
982
983                 local spd = nil
984                 local copied_original = false
985
986                 for spd in pd:get_subparts() do
987                         if spd:get_letter() > "c" and not copied_original then
988                                 --
989                                 -- Copy the 'c' line from the 'virgin' disklabel.
990                                 --
991                                 cmds:add("${root}${GREP} '^  c:' ${tmp}install.disklabel.${part} " ..
992                                          ">>${tmp}install.disklabel")
993                                 copied_original = true
994                         end
995
996                         cmds:set_replacements{
997                             letter = spd:get_letter(),
998                             fsize = spd:get_fsize(),
999                             bsize = spd:get_bsize()
1000                         }
1001
1002                         if spd:get_letter() == "a" then
1003                                 cmds:set_replacements{ offset = "0" }
1004                         else
1005                                 cmds:set_replacements{ offset = "*" }
1006                         end
1007                         if spd:get_size() == -1 then
1008                                 cmds:set_replacements{ size = "*" }
1009                         else
1010                                 cmds:set_replacements{ size = tostring(spd:get_size()) }
1011                         end
1012
1013                         if spd:is_swap() then
1014                                 cmds:add("${root}${ECHO} '  ${letter}:\t${size}\t*\tswap' >>${tmp}install.disklabel")
1015                         else
1016                                 cmds:add("${root}${ECHO} '  ${letter}:\t${size}\t${offset}\t4.2BSD\t${fsize}\t${bsize}\t99' >>${tmp}install.disklabel")
1017                         end
1018                 end
1019
1020                 if not copied_original then
1021                         --
1022                         -- Copy the 'c' line from the 'virgin' disklabel,
1023                         -- if we haven't yet (less than 2 subpartitions.)
1024                         --
1025                         cmds:add("${root}${GREP} '^  c:' ${tmp}install.disklabel.${part} >>${tmp}install.disklabel")
1026                 end
1027
1028                 App.register_tmpfile("install.disklabel")
1029         
1030                 --
1031                 -- Label the slice from the disklabel we just wove together.
1032                 --
1033                 -- Then create a snapshot of the disklabel we just created
1034                 -- for debugging inspection in the log.
1035                 --
1036                 cmds:add(
1037                     "${root}${DISKLABEL} -R -B -r ${part} ${tmp}install.disklabel",
1038                     "${root}${DISKLABEL} ${part}"
1039                 )
1040         
1041                 --
1042                 -- Create filesystems on the newly-created subpartitions.
1043                 --
1044                 pd:get_parent():cmds_ensure_dev(cmds)
1045                 pd:cmds_ensure_dev(cmds)
1046
1047                 for spd in pd:get_subparts() do
1048                         if not spd:is_swap() then
1049                                 spd:cmds_ensure_dev(cmds)
1050
1051                                 if spd:is_softupdated() then
1052                                         cmds:add("${root}${NEWFS} -U ${root}dev/" ..
1053                                             spd:get_device_name())
1054                                 else
1055                                         cmds:add("${root}${NEWFS} ${root}dev/" ..
1056                                             spd:get_device_name())
1057                                 end
1058                         end
1059                 end
1060         end
1061
1062         pd.cmds_write_fstab = function(pd, cmds, filename)
1063                 -- Write a new fstab for the given partition
1064                 -- to the given filename.
1065
1066                 if not filename then
1067                         filename = "${root}mnt/etc/fstab"
1068                 end
1069
1070                 cmds:set_replacements{
1071                     header = "# Device\t\tMountpoint\tFStype\tOptions\t\tDump\tPass#",
1072                     procline = "proc\t\t\t/proc\t\tprocfs\trw\t\t0\t0",
1073                     device = "???",
1074                     mountpoint = "???",
1075                     filename = App.expand(filename)
1076                 }
1077
1078                 cmds:add("${root}${ECHO} '${header}' >${filename}")
1079         
1080                 for spd in pd:get_subparts() do
1081                         cmds:set_replacements{
1082                             device = spd:get_device_name(),
1083                             mountpoint = spd:get_mountpoint()
1084                         }
1085
1086                         if spd:get_mountpoint() == "/" then
1087                                 cmds:add("${root}${ECHO} '/dev/${device}\t\t${mountpoint}\t\tufs\trw\t\t1\t1' >>${filename}")
1088                         elseif spd:is_swap() then
1089                                 cmds:add("${root}${ECHO} '/dev/${device}\t\tnone\t\tswap\tsw\t\t0\t0' >>${filename}")
1090                         else
1091                                 cmds:add("${root}${ECHO} '/dev/${device}\t\t${mountpoint}\t\tufs\trw\t\t2\t2' >>${filename}")
1092                         end
1093                 end
1094         
1095                 cmds:add("${root}${ECHO} '${procline}' >>${filename}")
1096         end
1097
1098         pd.dump = function(pd)
1099                 local letter, spd
1100
1101                 print("\t\tPartition " .. number .. ": " ..
1102                     start .. "," .. size .. ":" .. sysid .. "/" .. flags)
1103                 for spd in pd:get_subparts() do
1104                         spd:dump()
1105                 end
1106         end
1107
1108         App.log("New Partition: " .. number .. ": " ..
1109             start .. "," .. size .. ":" .. sysid .. "/" .. flags)
1110
1111         -- 'Constructor' - initialize this object's state.
1112         -- If this looks like a BSD slice, try to probe it with
1113         -- disklabel to get an idea of the subpartitions on it.
1114
1115         local pty, line, found, len
1116         local letter, size, offset, fstype, fsize, bsize
1117
1118         if sysid == 165 then
1119                 pty = Pty.open(App.expand("${root}${DISKLABEL} " .. parent:get_name() ..
1120                     "s" .. number))
1121                 line = pty:readline()
1122                 found = false
1123                 while line and not found do
1124                         found = string.find(line, "^%d+%s+partitions:")
1125                         line = pty:readline()
1126                 end
1127                 if found then
1128                         while line do
1129                                 found, len, letter, size, offset, fstype,
1130                                     fsize, bsize = string.find(line,
1131                                     "^%s*(%a):%s*(%d+)%s*(%d+)%s*([^%s]+)")
1132                                 if found then
1133                                         fsize, bsize = 0, 0
1134                                         if fstype == "4.2BSD" then
1135                                                 found, len, letter, size,
1136                                                     offset, fstype, fsize,
1137                                                     bsize = string.find(line,
1138                                                     "^%s*(%a):%s*(%d+)%s*" ..
1139                                                     "(%d+)%s*([^%s]+)%s*" ..
1140                                                     "(%d+)%s*(%d+)")
1141                                         end
1142                                         subpart[letter] =
1143                                             SubpartitionDescriptor.new{
1144                                                 parent = pd,
1145                                                 letter = letter,
1146                                                 size = size,
1147                                                 offset = offset,
1148                                                 fstype = fstype,
1149                                                 fsize = fsize,
1150                                                 bsize = bsize
1151                                             }
1152                                 end
1153                                 line = pty:readline()
1154                         end
1155                 end
1156                         
1157                 pty:close()
1158         end
1159
1160         return pd
1161 end
1162
1163 --[[------------------------]]--
1164 --[[ SubpartitionDescriptor ]]--
1165 --[[------------------------]]--
1166
1167 SubpartitionDescriptor = {}
1168 SubpartitionDescriptor.new = function(params)
1169         local spd = {}          -- instance variable
1170
1171         local parent = assert(params.parent)
1172         local letter = assert(params.letter)
1173         local size   = assert(params.size)
1174         local offset = assert(params.offset)
1175         local fstype = assert(params.fstype)
1176         local fsize  = assert(params.fsize)
1177         local bsize  = assert(params.bsize)
1178         local mountpoint = params.mountpoint
1179
1180         -- Now set up this object's interface functions
1181
1182         spd.get_parent = function(spd)
1183                 return parent
1184         end
1185         
1186         spd.get_letter = function(spd)
1187                 return letter
1188         end
1189
1190         spd.set_mountpoint = function(spd, new_mountpoint)
1191                 mountpoint = new_mountpoint
1192         end
1193
1194         spd.get_mountpoint = function(spd)
1195                 return mountpoint
1196         end
1197
1198         spd.get_fstype = function(spd)
1199                 return fstype
1200         end
1201
1202         spd.get_device_name = function(spd)
1203                 return parent.get_parent().get_name() ..
1204                     "s" .. parent.get_number() .. letter
1205         end
1206
1207         spd.get_raw_device_name = function(spd)
1208                 -- XXX depends on operating system
1209                 return parent.get_parent().get_name() ..
1210                     "s" .. parent.get_number() .. letter
1211         end
1212
1213         spd.get_capacity = function(spd)        -- in megabytes
1214                 return math.floor(size / 2048)
1215         end
1216
1217         spd.get_size = function(spd)            -- in sectors
1218                 return size
1219         end
1220
1221         spd.get_fsize = function(spd)
1222                 return fsize
1223         end
1224
1225         spd.get_bsize = function(spd)
1226                 return bsize
1227         end
1228
1229         spd.is_swap = function(spd)
1230                 return fstype == "swap"
1231         end
1232
1233         spd.is_softupdated = function(spd)
1234                 -- XXX this should be a property
1235                 return mountpoint ~= "/"
1236         end
1237
1238         spd.dump = function(pd)
1239                 print("\t\t\t" .. letter .. ": " .. offset .. "," .. size ..
1240                       ": " .. fstype .. " -> " .. mountpoint)
1241         end
1242
1243         -- Commands to ensure this device exists.
1244         spd.cmds_ensure_dev = function(spd, cmds)
1245                 cmds:add({
1246                     cmdline = "cd ${root}dev && ${root}${TEST_DEV} ${dev} || " ..
1247                               "${root}${SH} MAKEDEV ${dev}",
1248                     replacements = {
1249                         dev = FileName.basename(spd:get_device_name())
1250                     }
1251                 })
1252         end
1253
1254         --
1255         -- Constructor.
1256         --
1257
1258         App.log("New Subpartition on " .. parent:get_device_name() .. ": " ..
1259             letter .. ": " .. offset .. "," .. size .. ": " .. fstype ..
1260             "  F=" .. fsize .. ", B=" .. bsize)
1261
1262         return spd
1263 end
1264
1265 -- END of lib/storage.lua --