FreeBSD partition resizing required

A while ago, I'd had to resize the root partition of my FreeBSD boxes from 128MB to 512MB, as the size of the kernel modules (complete with symbols), new kernel and old kernel together, exceeded the 128MB available. With FreeBSD 8.0 now released, I read that even the 512MB was not enough now, and 1GB was required for root.

Rather than repeat the manual process I'd used before, I wrote a shell script to automate the back-up, repartition and restore process. This worked well on crimson and chrome (both hosted on the same machine, so I could boot from one and work on the other's disk), but required some more attention when I had to repartition topaz, the Toshiba laptop. Here, I used a USB CD drive to boot the FreeBSD LiveFS CD, to allow me to wreak havoc on the hard drive. However, it seemed that there are some hardware problems with the CD drive, such that I was getting CD read errors during the kernel probing of the disk drives. The kernel still booted OK, but when I entered fixit mode and tried a simple ls command, the shell complained about ld-elf.so, the error message being strewn with garbage characters. Trying different CDs didn't help, even when the CD was written on a different burner.

In the end, I decided to try and use the /rescue binaries, which are statically linked. This is possible by prepending the PATH environment variable with the location of the /rescue directory:

  # PATH=/mnt2/rescue:$PATH
  # export PATH

The /rescue binaries seemed to work, so I was able to proceed with mounting a remote disk to hold the backup data:

  # ifconfig fxp0 inet 192.168.0.12 up
  # mkdir /tank
  # mount -t nfs 192.168.0.10:/tank /tank

There was one remaining problem with the script; it used grep to identify if the slice being processed had partitions already mounted. grep does not exist in /rescue. Instead I used expr, since this also has the capability to perform regexp matching in strings. Once that change was made, I was able to let the resizing run its automated course.

Here's the script. It is available for download.

  #!/bin/sh
  #
  # NAME
  #   resize-parts.sh - Script to automate resizing of FreeBSD partitions
  #                     within a slice
  #
  # SYNOPSIS
  #   sh resize-parts.sh [-b] [-l] [-r] [-m mnt] [-d dir] [-f label] target
  #
  #   Switches:
  #      -b        backup partition data
  #      -l        write new slice label for partitions (via bsdlabel)
  #      -r        restore partition data (presumably from a previous invocation 
  #                of resize-part.sh -b -l)
  #      -d  dir   set location for backup tar files
  #      -m  mnt   set mount point for slices during backup and restore.
  #                Default is /mnt
  #      -f  label use pre-defined bsdlabel input file.  If -f is not specified,
  #                bsdlabel will be invoked in edit mode (-e) to allow the
  #                interactive editing of the partition table.
  #
  #   target is one of chrome, crimson or topaz.  The script defines
  #   the root slice and the expected mapping between partition and 
  #   mount point.
  #
  #   If -b, -l and -r are omitted, all are assumed.
  #
  # DESCRIPTION 
  #   Automates backup, re-labelling, with different sizes, and
  #   restoring of FreeBSD partitions.  Assumes knowledge of bsdlabel
  #   syntax, and that a suitably sized directory is available for
  #   backup on /tank.
  #
  #   If a pre-defined bsdlabel file is provided via the -f switch, the
  #   script assumes that interactive control is not required.  No
  #   prompts are issued before critical actions (i.e. writing new label
  #   and formatting partitions/restoring data).
  #
  #   Backup directory is assumed to be /tank/${target}, unless changed
  #   with the -d switch.
  #
  #
  # MPW 20100130
  
  SCRIPT=${0##*/}
  backup=0;label=0;restore=0
  chkmount=1
  BACKUPROOT=/tank
  TEMPMNT="/mnt"
  LABELFILE=""
  
  # function to prompt user to continue
  continuewith() {
      echo -n "$SCRIPT: $1  OK to continue? (y/n): "
      read cont
      if [ $cont != "y" ]; then
          echo "${SCRIPT}: Terminated."
          exit 1
      fi
  }
  
  # exit script with message
  die() {
      echo "${SCRIPT}: $1"
      exit 1
  }
  
  while [ $# -gt 1 ]; do
     case $1 in
         -b)
             backup=1
             ;;
         -l)
             label=1
             ;;
         -r)
             restore=1
             ;;
         -d)
             BACKUPROOT=$2
             shift
             ;;
         -f)
             LABELFILE=$2
             shift
             ;;
         -m)
             TEMPMNT=$2
             shift
             ;;
         *)
             die "${SCRIPT}: unrecognised switch: $1. \
Usage: sh resize-parts.sh [-b] [-l] [-r] [-m mnt] [-d dir] \
[-f label] target"
             ;;
     esac;
     shift
  done
  
  if [ ${backup} -eq 0 -a ${label} -eq 0 -a ${restore} -eq 0 ]; then
      backup=1
      label=1
      restore=1
  fi
  
  TARGET=$1
  
  case ${TARGET} in
      chrome) 
          SLICE="ad3s1"
          PARTS="a=root d=var e=tmp f=usr g=home"
          ;;
      crimson) 
          SLICE="ad0s1"
          PARTS="a=root d=tmp e=var f=usr g=home h=rep" 
          ;;
      topaz) 
          SLICE="ad0s1"
          PARTS="a=root d=usr e=tmp f=var g=home h=rep" 
          ;;
      *) 
          die "illegal (or no) target specified."
          ;;
  esac
  
  BACKUPDIR="${BACKUPROOT}/${TARGET}"
  
  if [ ! -d ${BACKUPDIR} ]; then
      mkdir -p ${BACKUPDIR}
      if [ $? -ne 0 ]; then
          die "unable to create backup directory ${BACKUPDIR}."
      fi
  fi
  
  # if label file provided, check it is readable
  if [ "$LABELFILE" != "" -a ! -r ${LABELFILE} ]; then
      die "unable to read label file."
  fi
  
  # check no part of slice is mounted
  # use expr as it is available in /rescue (grep is not)
  mnts=$(expr "/`mount`" : ".*${SLICE}")
  if [ ${mnts} -ne 0 ]; then
      die "one or more partitions of ${SLICE} are mounted."
  fi
  
  # backup partition data
  if [ ${backup} -eq 1 ]; then
      for part in $PARTS
      do
          partid=${part%=*}
          mntpt=${part#*=}
          mount -t ufs /dev/${SLICE}${partid} ${TEMPMNT}
          if [ $? -ne 0 ]; then
              die " unable to mount ${SLICE}${partid} on ${mntpt}."
          fi
          echo "${SCRIPT}: backing up ${SLICE}${partid} to ${BACKUPDIR}/${mntpt}.tar.gz..."
          tar czf ${BACKUPDIR}/${mntpt}.tar.gz -C ${TEMPMNT} .
          if [ $? -ne 0 ]; then
              die "error backing up data."
          fi
          umount ${TEMPMNT}
      done
  fi
  
  echo "${SCRIPT}: backup directory:"
  ls -l ${BACKUPDIR}/*.tar.gz
  
  if [ ${label} -eq 1 ]; then
      if [ "${LABELFILE}" = "" ]; then
          # interactive; check ok to continue?
          continuewith "invoking bsdlabel (better know vi!)."
          # modify partition sizes as required (interactive)
          bsdlabel -e ${SLICE}
      else
          echo "${SCRIPT}: partitioning ${SLICE} with contents of ${LABELFILE}"
          bsdlabel -R ${SLICE} ${LABELFILE}
      fi
      if [ $? -ne 0 ]; then
          die "bsdlabel failed."
      fi
  fi
  
  if [ ${restore} -eq 1 ]; then
      if [ "${LABELFILE}" = "" -a ${label} -eq 1 ]; then
          # ok to continue? only asked if bsdlabel step has been run interactively
          continuewith "formatting partitions and restoring."
      fi
      # format new partitions and restore data
      for part in ${PARTS}
      do
          partid=${part%=*}
          mntpt=${part#*=}
          echo "${SCRIPT}: formatting ${SLICE}${partid}..."
          newfs -L ${TARGET}${mntpt} /dev/${SLICE}${partid}
          if [ $? -ne 0 ]; then
              die "unable to format the filesystem: ${SLICE}${partid}."
          fi
          mount -t ufs  /dev/${SLICE}${partid} ${TEMPMNT}
          if [ $? -ne 0 ]; then
              die "unable to mount new formatted filesystem: ${SLICE}${partid}."
          fi
          echo "${SCRIPT}: restoring ${BACKUPDIR}/${mntpt} to ${SLICE}${partid}..."
          tar xzpf ${BACKUPDIR}/${mntpt}.tar.gz -C ${TEMPMNT}
          if [ $? -ne 0 ]; then
              umount ${TEMPMNT}
              die "unable to restore ${mntpt} files to ${SLICE}${partid}."
          fi
          umount ${TEMPMNT}
      done
  fi

Here's an example of a bsdlabel file. This is the version for topaz:

  # /dev/ad0s1:
  8 partitions:
  #        size   offset    fstype   [fsize bsize bps/cpg]
    a:       1G       16    4.2BSD     2048 16384     8    # root
    b:     512M        *      swap                         # swap (natch)
    c: 156301425        0    unused        0     0         # "raw" part, don't edit
    d:       9G        *    4.2BSD     2048 16384 28552    # usr
    e:     512M        *    4.2BSD     2048 16384     8    # tmp
    f:       1G        *    4.2BSD     2048 16384 28552    # var
    g:      20G        *    4.2BSD     2048 16384 28552    # home
    h:        *        *    4.2BSD     2048 16384 28552    # rep

The asterisk in the size column of the last row means that rep gets the remaining disk space in that slice (partition).