Patch title: Release 94 bulk changes
Abstract:
File: /pliant/linux/storage/raid.pli
Key:
    Removed line
    Added line
abstract
  [The 'raid_convert' function will allow you to change the configuration of a Linux software RAID array with (or with the hope of) no data loss.] ; eol
  highlight "THIS IS VERY ALPHA CODE: IT MAY DESTROY ALL YOUR DATAS AS WELL."
 

# Copyright  Hubert Tonneau  hubert.tonneau@pliant.cx
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 2
# as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# version 2 along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

# release 5
# release 6

module "/pliant/language/stream.pli"
module "/pliant/language/context.pli"
module "/pliant/admin/file.pli"
module "/pliant/admin/execute.pli"
module "/pliant/language/os.pli"
module "/pliant/linux/storage/filesystem.pli"
module "/pliant/linux/storage/partition.pli"
module "/pliant/linux/misc/warn.pli"
if os_api<>"linux"
  error "This module is dealing only with Linux software RAID arrays"


constant conservative true
constant speed_report true
constant mdadm (file_query "file:/bin/mdadm" standard)=success


#-------------------------------------------------------------------------

doc
  [These are a few classical arithmetic functions on unlimited integers that we will use at a later point.]


function min a b -> c
  arg Intn a b c
  c := shunt a<=b a b

function max a b -> c
  arg Intn a b c
  c := shunt a>=b a b

function pgcd a b -> g
  arg Intn a b g
  var Intn x := a
  var Intn y := b
  g := y
  while x>0
    g := x
    x := y%x
    y := g

function ppcm a b -> m
  arg Intn a b m
  m := a*b\(pgcd a b)


#-------------------------------------------------------------------------


type Raid
  field Str device_name
  field Array:Stream devices
  field Int level
  field Int sparse <- 0
  field Int geometry <- 0
  field Int chunk_size
  field Intn size
 

doc
  [Opens the various Pliant streams used to access the disks of a RAID array.] ; eol
  [An example could be:]
  listing
    (var Raid raid) define "device:/md0" "device:/hda5 device:/hda6 device:/hda7" "level 5"
  [or, if you prefer or more compact encoding:]
  listing
    (var Raid raid) define "md0" "hda5 hda6 hda7" ""

method raid bind device_name devices_names
  arg_rw Raid raid ; arg Str device_name devices_names
  raid:device_name := shunt (device_name search ":" -1)=(-1) "device:/"+device_name device_name
  raid:devices size := 0
  var Str names := devices_names
  while names<>""
    var Int i := raid:devices:size
    if (names parse any:(var Str name1) _ any:(var Str name2))
      raid:devices size := i+1
      raid:devices:i open (shunt (name1 search ":" -1)=(-1) "device:/"+name1 name1) in+out+nocache
      names := name2
    else
      raid:devices size := i+1
      raid:devices:i open (shunt (names search ":" -1)=(-1) "device:/"+names names) in+out+nocache
      names := ""

method raid define device_name devices_names options
  arg_rw Raid raid ; arg Str device_name devices_names ; arg Str options
  raid bind device_name devices_names
  raid level := options option "level" Int (shunt raid:devices:size>2 5 1)
  raid sparse := options option "sparse" Int 0
  raid chunk_size := options option "chunk_size" Int 64*2^10
  if raid:sparse<>0 and raid:level<>5
    error "sparse disks is supported only with RAID 5."


#-------------------------------------------------------------------------


method raid build_configuration_file filename
  arg Raid raid ; arg Str filename
  (var Stream rt) open filename out
  rt writeline "raiddev "+(file_os_name raid:device_name)
  rt writeline "  raid-level "+(string raid:level)
  rt writeline "  nr-raid-disks "+(string raid:devices:size)
  rt writeline "  nr-spare-disks "+(string raid:sparse)
  rt writeline "  persistent-superblock 1"
  rt writeline "  chunk-size "+(string raid:chunk_size\1024)
  for (var Int i) 0 raid:devices:size-1
    rt writeline "  device "+(file_os_name raid:devices:i:name)
    rt writeline "  raid-disk "+string:i

method raid command cmd options param -> status
  arg Raid raid ; arg Str cmd options param ; arg Status status
  var Str temp := file_temporary
  raid build_configuration_file temp
  # status := shunt (execute cmd+" -c "+file_os_name:temp+(shunt options<>"" " "+options "")+" "+(file_os_name raid:device_name)+(shunt param<>"" " "+param "") quiet)=0 success failure
  status := shunt (execute cmd+" -c "+file_os_name:temp+(shunt options<>"" " "+options "")+" "+(file_os_name raid:device_name)+(shunt param<>"" " "+param "") output "file:/tmp/raid.log" mixed)=0 success failure
  file_delete temp
  sleep 2

method raid start -> status
  arg Raid raid ; arg Status status
  status := raid command "raidstart" "" ""
  if mdadm
    var Str cmd := "mdadm --assemble "+(file_os_name raid:device_name)
    for (var Int i) 0 raid:devices:size-1
      cmd += " "+(file_os_name raid:devices:i:name)
    status := shunt (execute cmd)=0 status failure
  else
    status := raid command "raidstart" "" ""

method raid stop -> status
  arg Raid raid ; arg Status status
  status := raid command "raidstop" "" ""
  if mdadm
    status := shunt (execute "mdadm --stop "+(file_os_name raid:device_name))=0 status failure
  else
    status := raid command "raidstop" "" ""

method raid initialize -> status
  arg Raid raid ; arg Status status
  raid command "raidstop" "" ""
  var FileInfo mtab := file_query "file:/etc/mtab" standard
  if mtab=undefined # get around the bug in raidtools
    (var Stream s) open "file:/etc/mtab" out+mkdir+safe
    s close
  status := raid command "mkraid" "--really-force" ""
  if mtab=undefined
    file_delete "file:/etc/mtab"
    file_delete "file:/etc/"
  if mdadm
    var Str cmd := "mdadm --create "+(file_os_name raid:device_name)
    cmd += " --level="+(string raid:level)+" --raid-devices="+(string raid:devices:size)+" --spare-devices="+(string raid:sparse)+" --chunk="+(string raid:chunk_size\1024)
    cmd += " --run"
    for (var Int i) 0 raid:devices:size-1
      cmd += " "+(file_os_name raid:devices:i:name)
    status := shunt (execute cmd)=0 status failure
  else
    var FileInfo mtab := file_query "file:/etc/mtab" standard
    if mtab=undefined # get around the bug in raidtools
      (var Stream s) open "file:/etc/mtab" out+mkdir+safe
      s close
    status := raid command "mkraid" "--really-force" ""
    if mtab=undefined
      file_delete "file:/etc/mtab"
      file_delete "file:/etc/"

method raid hotadd device -> status
  arg Raid raid ; arg Str device ; arg Status status
  status := raid command "raidhotadd" "" "/dev/"+device
  if mdadm
    status := shunt (execute "mdadm "+(file_os_name raid:device_name)+" -add /dev/"+device)=0 status failure
  else
    status := raid command "raidhotadd" "" "/dev/"+device


#-------------------------------------------------------------------------


doc
  [When the RAID is set in safe mode, an IO error will not produce an exception. This is very usefull during the conversion, since, in case of an IO error, we want to continue anyway with the faulty disk disabled.]

method raid set_safe_mode
  arg_rw Raid raid
  for (var Int i) 0 raid:devices:size-1
    raid:devices:i open raid:devices:i:name in+out+nocache+safe


doc
  [How many usefull disks do we have in the RAID array.]

method raid data_disks_count -> count
  arg Raid raid ; arg Int count
  if raid:level=0
    count := raid:devices:size
  eif raid:level=1
    count := 1
  eif raid:level=5
    count := raid:devices:size-raid:sparse-1
  else
    error "raid level "+(string raid:level)+" is not supported yet."
  
doc
  [How many extra disks do we have in the RAID array (spare disks are not counted here, and are more generally completely ignored by this software).]

method raid checksum_disks_count -> count
  arg Raid raid ; arg Int count
  if raid:level=0
    count := 0
  eif raid:level=1
    count := raid:devices:size-1
  eif raid:level=5
    count := 1
  else
    error "raid level "+(string raid:level)+" is not supported yet."

  
doc
  [Parses the /proc/mdstat file in order to extract informations on the RAID array. This function is very sensible to the format used in /proc/mdstat because we prefer to stop with an error rather that attempting a conversion on an array that would contain an misunderstood information.]

method raid query
  arg_rw Raid raid 
  (var Stream s) open "file:/proc/mdstat" in
  while not s:atend
    if (s:readline parse any:(var Str name) ":" word:"active" "raid" (var Int level) any:(var Str stat))
      if "/dev/"+name=(file_os_name raid:device_name)
        var Int disks_count := 0
        while (stat parse any:(var Str drop) "[lb]" (var Int i) "[rb]" _ any:(var Str stat2)) and (drop search " " -1)=(-1)
          disks_count += 1
          stat := stat2
        if disks_count<>raid:devices:size
          error "The number of disks in "+raid:device_name+" does not match your declarations."
        var CBool ok := false
        if level=0 and (stat parse (var uInt blocks) word:"blocks" (var Int chunk) "k" word:"chunks")
          raid chunk_size := chunk*1024
          ok := true
        eif level=1 and (stat parse (var uInt blocks) word:"blocks"  "[lb]" any "[rb]" "[lb]" any:(var Str ups) "[rb]")
          raid chunk_size := 512
          ok := ups=(repeat ups:len "U")
        eif level=5 and (stat parse (var uInt blocks) word:"blocks" word:"level" (var Int level) "," (var Int chunk) "k" word:"chunk" "," word:"algorithm" (var Int algorithm) "[lb]" any "[rb]" "[lb]" any:(var Str ups) "[rb]")
          raid chunk_size := chunk*1024
          raid geometry := algorithm
          ok := (algorithm=0 or algorithm=1) and ups=(repeat ups:len "U")
        if ok
          raid level := level
          raid size := 1024n*blocks
          if raid:size%(raid:chunk_size*raid:data_disks_count)<>0
            error "The total "+raid:device_name+" size does not match the chunk size and number of devices"
          return              
  error "Unexpected "+raid:device_name+" status !"


doc
  [This is the central function of the script: it defines on which device, and at which offset, an area is stored. The area is specifyed by it's start position in the array. On return, we also get how many bytes in the area are following on the same device.]

method raid map raid_offset parity device device_offset -> continuous_size
  arg Raid raid ; arg Intn raid_offset ; arg Int parity ; arg_w Int device ; arg_w Intn device_offset ; arg Int continuous_size
  if raid_offset<0 or raid_offset>=raid:size
    error "attempted to access raid device outside boundaries"
  if parity<0 or parity>raid:checksum_disks_count
    error "attempted to access an nonexistent parity disk"
  if raid:level=0
    var Intn chunk_number := raid_offset\raid:chunk_size
    continuous_size := (chunk_number+1)*raid:chunk_size-raid_offset
    var Int chunk_per_row := raid:devices:size
    var Intn row := chunk_number\chunk_per_row
    var Int col := chunk_number%chunk_per_row
    device := col
    device_offset := row*raid:chunk_size
  eif raid:level=1
    device := parity
    device_offset := raid_offset
    continuous_size := min raid:size-raid_offset 64*2^10
  eif raid:level=5
    var Intn chunk_number := raid_offset\raid:chunk_size
    continuous_size := (chunk_number+1)*raid:chunk_size-raid_offset
    var Int chunk_per_row := raid:devices:size-raid:sparse-1
    var Intn row := chunk_number\chunk_per_row
    var Int col := chunk_number%chunk_per_row
    var Int disks := raid:devices:size-raid:sparse
    var Int parity_disk := shunt raid:geometry=0 (disks-1)-row%disks raid:geometry=1 row%disks -1
    if col>=parity_disk
      col += 1
    if parity=1
      col := parity_disk
    device := col
    device_offset := row*raid:chunk_size
  else
    error "raid level "+(string raid:level)+" is not supported yet."

doc
  ['read' method will read an area in the RAID array. If parity=0 we read the true datas. If parity=1, we read the first extra disk datas (RAID 1 or RAID 5). If parity=2, we read the second extra disk datas (RAID 1). etc.] ; eol
  ['read' will call 'map' in order to determine where each area of the RAID truly stands.] ; eol
  [ It will also call 'read_using_checksums' in order to deduce the content using extra disks, if the data disk is dead.]

function memory_xor buffer1 buffer2 size
  arg Address buffer1 buffer2 ; arg Int size
  for (var Int i) 0 size step Int:size
    (buffer1 translate Byte i) map Int := ((buffer1 translate Byte i) map Int) .xor. ((buffer2 translate Byte i) map Int)

method raid read_using_checksums offset buffer size
  arg_rw Raid raid ; arg Intn offset ; arg Address buffer ; arg Int size
  var Int done := 0
  while done<size
    var Int step := min (raid map offset+done 0 (var Int d) (var Intn o)) size-done
    if raid:level=1
      var Int p := 1 ; var Int i
      while p<raid:checksum_disks_count and { i := (d+p)%raid:devices:size; raid:devices:i configure "seek "+string:o ; raid:devices:i raw_read (buffer translate Byte done) step ; raid:devices:i:is_crashed }
        p += 1
    eif raid:level=5
      var Address buf := memory_allocate step null
      memory_clear (buffer translate Byte done) step
      for (var Int i) 0 raid:devices:size-1
        if i<>d
          raid:devices:i configure "seek "+string:o
          raid:devices:i raw_read buf step
          memory_xor (buffer translate Byte done) buf step
      memory_free buf
    done += step
  
method raid read offset parity buffer size
  arg_rw Raid raid ; arg Intn offset ; arg Int parity ; arg Address buffer ; arg Int size
  var Int done := 0
  while done<size
    var Int step := min (raid map offset+done parity (var Int d) (var Intn o)) size-done
    raid:devices:d configure "seek "+string:o ?
    raid:devices:d raw_read (buffer translate Byte done) step ?
    if raid:devices:d:is_crashed and raid:level>0 and parity=0
      raid read_using_checksums offset+done (buffer translate Byte done) step
    done += step

  
doc
  ['write' method will ... write an area in the RAID array.]

method raid write offset parity buffer size
  arg_rw Raid raid ; arg Intn offset ; arg Int parity ; arg Address buffer ; arg Int size
  var Int done := 0
  while done<size
    var Int step := min (raid map offset+done parity (var Int d) (var Intn o)) size-done
    raid:devices:d configure "seek "+string:o ?
    raid:devices:d raw_write (buffer translate Byte done) step ?
    if raid:devices:d:is_crashed
      void
    done += step
  

#-------------------------------------------------------------------------


doc
  [This function is responsible for checking both the RAID array consistency, and the fact that this program is able to read it properly. In case of troubles, it will stop disgracefully, but since we are only reading, it is a good behavior.] ; eol
  para
    [We read the datas using three methods:]
    list
      item [using the 'map' function, then reading individual disk]
      item
        [directly from the /dev/md] ; italic [x] ; [ device]
        [ (if 'full_check' is true)]
      item [computing each area from the extra disks, without using the real datas (if 'full_check' is true and the RAID level is 1 or 5)]
    [If one method does not produce the same data stream as others, we stop.]
  para
    [We also recompute all the datas on each extra disk, and compare them to the content of the physical disk in order to verify that the RAID array is properly synced, and that this propram computes the extra disks content properly (the same way the Linux kernel does it).] ; eol
  [All this is not necessary for converting a RAID array, but we take maximum precautions before starting the real conversion. Again, a failure at this level will not hurt much.]

method raid check_checksums full_check continue_if_corrupted correct_if_corrupted
  arg_rw Raid raid ; arg CBool full_check continue_if_corrupted correct_if_corrupted
  var DateTime start := datetime
  var Int unit := raid:chunk_size*raid:data_disks_count
  var Address chunks := memory_allocate unit null
  var Address chunks2 := memory_allocate unit null
  var Address checksum1 := memory_allocate raid:chunk_size null
  var Address checksum2 := memory_allocate raid:chunk_size null
  if full_check
    (var Stream md) open raid:device_name in+out+nocache
  var Intn corrupted := 0
  var Intn position := 0
  while position<raid:size
    if position\2^20<>(position-unit)\2^20
      console "checked " position\2^20 " MB (" 100*position\raid:size "%  " (cast position\(max (cast (datetime:seconds-start:seconds)*1024 Int) 1) Int) " KB/s)"
      if corrupted<>0
        console " (" corrupted\raid:chunk_size " corrupted chunks)"
      console "   [cr]"
    raid read position 0 chunks unit
    if full_check
      md configure "seek "+string:position ?
      md raw_read chunks2 unit ?
      if (memory_compare chunks unit chunks2 unit)<>compare_equal
        error "This program is buggy (at position "+string:position+") !" ?
    if raid:level=1
      for (var Int i) 1 raid:checksum_disks_count
        raid read position i chunks2 unit
        if (memory_compare chunks unit chunks2 unit)<>compare_equal
          if continue_if_corrupted
            if correct_if_corrupted
              raid write position i chunks unit
            corrupted += unit
          else
            error "The raid array is corrupted (at position "+string:position+") !" ?
    eif raid:level=5
      memory_clear checksum1 raid:chunk_size
      for (var Int i) 0 unit-1 step raid:chunk_size
        memory_xor checksum1 (chunks translate Byte i) raid:chunk_size
      raid read position 1 checksum2 raid:chunk_size
      if (memory_compare checksum1 raid:chunk_size checksum2 raid:chunk_size)<>compare_equal
        if continue_if_corrupted
          if correct_if_corrupted
            raid write position 1 checksum1 raid:chunk_size
          corrupted += raid chunk_size
        else
          error "The raid array is corrupted (at position "+string:position+") !" ?
    if full_check and raid:level>0
      raid read_using_checksums position chunks2 unit
      if (memory_compare chunks unit chunks2 unit)<>compare_equal
        error "This program is not safe (at position "+string:position+") !" ?
    position += unit
  memory_free chunks
  memory_free chunks2
  memory_free checksum1
  memory_free checksum2
  if speed_report
    console "check passed (in " (cast datetime:seconds-start:seconds Int) " seconds, at " (cast raid:size\(max (cast (datetime:seconds-start:seconds)*1024 Int) 1) Int) " KB/s)   " eol
  if corrupted<>0
    console (shunt correct_if_corrupted "corrected" "found") " " corrupted " bytes corrupted (" corrupted\raid:chunk_size " chunks)" eol

doc
  [This is an additional security: we read all the sectors on the new RAID array, and stop disgracefully if we get a bad sector.]

function raid_check_surfaces raid
  arg_rw Raid raid
  var Int unit := raid:chunk_size*raid:data_disks_count
  var Address buffer := memory_allocate unit null
  for (var Int i) 0 raid:devices:size-1
    var Intn position := 0
    var Intn disksize := raid:size\raid:data_disks_count
    while position<disksize
      if position\2^20<>(position-unit)\2^20
        console "surface checked " i+1 "/" raid:devices:size "  " position\2^20 " MB (" 100*position\disksize "%)   [cr]"
      raid:devices:i configure "seek "+string:position ?
      raid:devices:i raw_read buffer unit ?
      position += unit
  memory_free buffer


doc
  [Now the real conversion.] ; eol
  [The main idea with this program is that if we convert 'common_unit' bytes at once (read all them on the old array, then write them on the new array), we get no clash (no data is written on the new array that will be red from the old array at the same physical place at a later point). ]
  [If the new array will contain more datas per row, we must go forward (we must go backward if it will contain less). ]
  [If the new array is bigger than the old one, we'll pad the extra space with zeros on all disks (so the value on extra disks will be consistent).]
  para
    [Since the Pliant streams that are used to access Linux devices are set in 'safe' mode, an IO failure on one of them will simply desable the device (subsequent IOs will just be dropped) but not stop the program. Since we also write extra disks informations on the fly, it means that a failure on a disk of a RAID5 array should not be fatal.]

function raid_convert old new
  arg_rw Raid old new
  var Int old_unit := old:chunk_size*old:data_disks_count
  var Int new_unit := new:chunk_size*new:data_disks_count
  var Int common_unit := ppcm old_unit new_unit
  while common_unit<64*2^10
    common_unit*=2
  if common_unit>memory_assigned
    error "The conversion would consume too much memory.[lf]I'd better not attempt it (no data has been modifyed yet)." ?
  var Address chunks := memory_allocate common_unit null
  var Address checksum := memory_allocate new:chunk_size null
  var Intn size := min old:size new:size
  var CBool forward := new:data_disks_count>=old:data_disks_count
  var DateTime start := datetime
  var Intn position := shunt forward 0 (size-1)\common_unit*common_unit
  while (shunt forward position<size position>=0)
    console "converted " (shunt forward position size-position)\2^20 " MB (" 100*(shunt forward position size-position)\size "%  " (cast position\(max (cast (datetime:seconds-start:seconds)*1024 Int) 1) Int) " KB/s)   [cr]"
    var Int step := min size-position common_unit
    old read position 0 chunks step
    new write position 0 chunks step
    if new:level=1
      for (var Int i) 1 new:checksum_disks_count
        new write position i chunks common_unit
    eif new:level=5
      for (var Int base) 0 common_unit-1 step new_unit
        memory_clear checksum new:chunk_size
        for (var Int i) 0 new_unit-1 step new:chunk_size
          memory_xor checksum (chunks translate Byte base+i) new:chunk_size
        new write position+base 1 checksum new:chunk_size
    position += shunt forward common_unit -common_unit
  position := size
  memory_clear chunks common_unit
  while position<new:size
    console "cleared " (position-size)\2^20 " MB (" 100*(position-size)\(new:size-size) "%)   [cr]"
    var Int step := min new:size-position common_unit
    new write position 0 chunks step
    if new:level=1
      for (var Int i) 1 new:checksum_disks_count
        new write position i chunks step
    eif new:level=5
      for (var Int base) 0 step-1 step new_unit
        new write position+base 1 chunks new:chunk_size
    position += step
  memory_free chunks
  memory_free checksum
  if speed_report
    console "conversion passed (in " (cast datetime:seconds-start:seconds Int) " seconds, at " (cast new:size\(max (cast (datetime:seconds-start:seconds)*1024 Int) 1) Int) " KB/s)   " eol


doc
  [This is just the main function that calls the various ones described above, and displays additional informations and warnings.] ; eol
  [A sample usage (from the shell prompt) might be:]
  fixed
    [pliant module /pliant/admin/raid.pli command 'raid_convert [dq]/dev/md0[dq] [dq]/dev/hda5 /dev/hda6 /dev/hda7[dq] [dq]/dev/hda5 /dev/hda6 /dev/hda7 /dev/hda8[dq] 5 64*2^10']
  

function raid_convert raid_device old_devices new_devices new_raid_level new_chunk_size -> deads
  arg Str raid_device old_devices new_devices ; arg Int new_raid_level new_chunk_size ; arg_w Array:Str deads
  (var Raid old) bind raid_device old_devices
  if (execute "raidstart "+file_os_name:raid_device quiet)=0
    sleep 5
  old:query ?
  console "[lf]The old RAID" old:level " array is a " old:size\2^20 " MB RAID" old:level " array on " old:devices:size " disks with " old:chunk_size\1024 " K chunks.[lf]"
  (var Raid new) define raid_device new_devices "level "+string:new_raid_level+" chunk_size "+string:new_chunk_size
  new size := old:size*new:data_disks_count\old:data_disks_count
  console "The new RAID" new:level " array will be a " new:size\2^20 " MB RAID" new:level " array on " new:devices:size " disks with " new:chunk_size\1024 " K chunks.[lf]"
  console "[lf]THIS IS VERY ALPHA CODE, IT MAY DESTROY ALL YOUR DATAS !" eol
  console "You have 10 seconds to press Ctrl+C if you changed your mind." eol
  sleep 10
  console "So, let's go.[lf][lf]"
  filesystem_dismount old:device_name
  if conservative
    console "step 1: checking consistency in the existing array   " eol
    console "It is still safe to stop while step 1 is running." eol
    old check_checksums true false false ?
  if (execute "raidstop "+(file_os_name old:device_name) quiet)<>0
    error "Failed to stop RAID array "+raid_device ?
  sleep 5
  if conservative
    console "step 2: testing disks surfaces   " eol
    console "It is still safe to stop while step 2 is running." eol
    raid_check_surfaces new ?
  old set_safe_mode
  new set_safe_mode
  void ?
  if conservative
    memory_checkup ?
    console "step 3: converting datas   " eol
  console "IF THIS PROCESS IS STOPPED IN THE MIDDLE, YOUR DATAS WILL BE LOST." eol
  sleep 2
  raid_convert old new
  var Int old_crashed_count := 0
  for (var Int i) 0 old:devices:size-1
    if old:devices:i:is_crashed
      console old:devices:i:name+" IS CRASHED IN THE OLD RAID !" eol
      old_crashed_count += 1
  deads size := 0
  for (var Int i) 0 new:devices:size-1
    if new:devices:i:is_crashed
      console new:devices:i:name+" IS CRASHED IN THE NEW RAID !" eol
      if (new:devices:i:name eparse "file:" any:(var Str device_name))
        deads += device_name
      else
        console "this program is really buggy !!!" eol
        deads += new:devices:i:name
  if old_crashed_count>old:checksum_disks_count
    console "YOU DATAS ARE LOST BECAUSE TOO MANY DISKS CRASHED IN THE OLD RAID ARRAY." eol
    return
  if deads:size>old:checksum_disks_count
    console "YOU DATAS ARE LOST BECAUSE TOO MANY DISKS CRASHED IN THE NEW RAID ARRAY." eol
    return
  console "The conversion is finished.[lf]"
  if conservative and deads:size=0
    console "step 4: checking consistency in the new array   " eol
    console "A failure in the middle of step 4 would not crash your datas:[lf]they are already converted." eol
    new check_checksums false false false ?
    memory_checkup
  console "Please UPDATE YOUR /etc/raidtab FILE FIRST,[lf]then run the following commands:" eol
  console "  mkraid --force --dangerous-no-resync "+raid_device eol
  for (var Int i) 0 deads:size-1
    console "  raidhotremove "+raid_device+" "+deads:i eol
  console "in order to update the raid superblocks." eol


function raid_check raid_device old_devices
  arg Str raid_device old_devices
  (var Raid raid) bind raid_device old_devices
  var CBool started := raid:start=success
  raid:query ?
  console "[lf]The RAID" raid:level " array is a " raid:size\2^20 " MB RAID" raid:level " array on " raid:devices:size " disks with " raid:chunk_size\1024 " K chunks.[lf]"
  filesystem_dismount raid_device
  if conservative
    console "step 1: checking consistency in the existing array   " eol
    raid check_checksums true false false ?
  if started and raid:stop=failure
    error "Failed to stop RAID array "+raid_device ?
  if conservative
    console "step 2: testing disks surfaces   " eol
    raid_check_surfaces raid ?
  console raid_device " checked." eol


export Raid '. define' '. build_configuration_file' '. initialize' '. start' '. stop' '. hotadd'
export raid_convert raid_check


# Sample recovery :
#
# module "/pliant/linux/storage/raid.pli"
# (gvar Raid r) define "md0" "sda1 sdb1" "" 
# r hotadd "sda1"


#-------------------------------------------------------------------------
  

if false
  
  doc
    para
      [All the rest of the script is just for building a sample RAID and test the conversion on it.]
    ['raid_configure' will write the /etc/raidtab file with the provided informations so that we can run 'raidstart'.] ; eol
    [Since this function will destroy existing configurations stored in /etc/raidtab, the first time it's used, the /etc/raidtab is copyed to /etc/raidtab.backup]
  
  function raid_configure raid_device devices_names level chunksize filename
    arg Str raid_device devices_names ; arg Int level chunksize ; arg Str filename
    var Str dev := shunt (raid_device search ":" -1)=(-1) "device:/"+raid_device raid_device
    var Array:Str devices
    var Str names := devices_names
    while names<>""
      if (names parse any:(var Str name1) _ any:(var Str name2))
        devices += shunt (name1 search ":" -1)=(-1) "device:/"+name1 name1
        names := name2
      else
        devices += shunt (names search ":" -1)=(-1) "device:/"+names names
        names := ""
    (var Stream rt) open filename out
    rt writeline "raiddev "+file_os_name:dev
    rt writeline "  raid-level "+string:level
    rt writeline "  nr-raid-disks "+(string devices:size)
    rt writeline "  nr-spare-disks 0"
    rt writeline "  persistent-superblock 1"
    rt writeline "  chunk-size "+(string chunksize\1024)
    for (var Int i) 0 devices:size-1
      rt writeline "  device "+(file_os_name devices:i)
      rt writeline "  raid-disk "+string:i
  
  function raid_configure raid_device devices_names level chunksize
    arg Str raid_device devices_names ; arg Int level chunksize
    if (file_query "file:/etc/raidtab" standard)=defined and (file_query "file:/etc/raidtab.backup" standard)=undefined
      file_copy "file:/etc/raidtab" "file:/etc/raidtab.backup"
      console "Your /etc/raidtab file has been saved to /etc/raidtab.backup" eol
    raid_configure raid_device devices_names level chunksize "file:/etc/raidtab"
  
  doc
    [Wait's for the resync to finish in the kernel.]
  
  function raid_wait
    while true
      var CBool more := false
      (var Stream s) open "file:/proc/mdstat" in
      while not s:atend
        if (s:readline search "resync" -1)<>-1
          more := true
      if not more
        return
      s close
      console "waiting for raid resync[cr]"
      sleep 60
      console "                       [cr]"
  
  
  doc
    [These two functions are simply generating a sample file, and checking it's content after the conversion.]
  
  function generate_file name size
    arg Str name ; arg Int size
    console "generating a " size\2^20 " MB test file" eol
    (var Stream s) open name out
    for (var Int i) 1 size\Int:size
      s raw_write addressof:i Int:size
    s close
      
  function check_file name size
    arg Str name ; arg Int size
    console "checking the " size\2^20 " MB test file" eol
    (var Stream s) open name in
    for (var Int i) 1 size\Int:size
      s raw_read addressof:(var Int j) Int:size
      if j<>i
        error error_id_corrupted "The test file is corrupted at offset "+(string i*Int:size)
      
  
  doc
    [This is my test process. It will build a RAID array, then feed it with sample datas, then convert it, and finally check some datas on the new array.] ; eol
    [If you want to use it, you have to update the constants at the beginning in order to match your configuration, then change 'if false' with 'if true'. Don't forget to put back 'if false' at the end because until you do it, each load of this module will run the sample, so erase all your datas on the sample raid.] ; eol
    [If you get 'sample test passed.' message at the end, then the test succeeded on your configuration. If it stops in the middle, there is a problem with your configuration. If it stops in the middle of step 3, there is a big problem because the datas are lost, so please email to hubert.tonneau@pliant.cx]
  
  function run_sample_raid_test
    constant raid_device "md0"
    constant initial_devices "hda5 hda6 hda7"
    constant initial_raid_level 5
    constant initial_chunksize 16*2^10
    constant final_devices "hda5 hda6 hda7 hda8"
    constant final_raid_level 5
    constant final_chunksize 64*2^10
    constant test_file_size 64*2^20
    constant doit false
    # do not allow to run this test if the constants have not been changed in
    # order to match site test configuration
    if not doit
      console "You must update the constants at the top of function 'run_sample_raid_test'[lf]before using it." eol
      return
    var Str dev := shunt (raid_device search ":" -1)=(-1) "device:/"+raid_device raid_device
    warn "You are attempting to run raid test reconfigure utility on "+file_os_name:dev+"[lf]It will erase all datas on this RAID device.[lf]A bug or misconfiguration in this program might also corrupt all your system:[lf]be sure to have up to date backups !"
    # initial cleanup
    filesystem_dismount "file:/mnt/raid"
    execute "raidstop "+file_os_name:dev quiet
    # create a sample raid
    raid_configure raid_device initial_devices initial_raid_level initial_chunksize
    execute "mkraid --really-force "+file_os_name:dev quiet
    raid_wait
    format_partition dev "name [dq]test[dq]"
    filesystem_mount dev "file:/mnt/raid/" ""
    file_copy "/" "file:/mnt/raid/pliant/" extended+recursive
    execute "tar -zc -f /mnt/raid/pliant.tgz /pliant/"
    generate_file "file:/mnt/raid/test" test_file_size
    filesystem_dismount dev
    # now convert it
    var Array:Str deads := raid_convert raid_device initial_devices final_devices final_raid_level final_chunksize ?
    raid_configure raid_device final_devices final_raid_level final_chunksize
    execute "mkraid --really-force --dangerous-no-resync "+file_os_name:dev quiet
    for (var Int i) 0 deads:size-1
      execute "raidhotremove "+file_os_name:dev+" "+deads:i quiet
    # and finally test it
    filesystem_mount dev "file:/mnt/raid/" ""
    if (execute "tar -ztv -f /mnt/raid/pliant.tgz" quiet)<>0
      error "The tar file is corrupted" ?
    check_file "file:/mnt/raid/test" test_file_size ?
    # final cleanup
    filesystem_dismount dev
    execute "raidstop "+file_os_name:dev quiet
    console "sample test passed." eol
  
  export run_sample_raid_test