#lang racket/base
(require racket/cmdline
racket/system
(planet neil/sudo:1:1)
(planet neil/parted:1:0))
(define (system-command/ignored-output
#:error-name (error-name 'system-command/ignore-output)
#:sudo? (sudo? #f)
#:command command
#:args (args '()))
(let ((stdout-ob (open-output-bytes))
(stderr-ob (open-output-bytes))
(stdin-ib (open-input-bytes #"")))
(let* ((exit-code (parameterize ((current-output-port stdout-ob)
(current-error-port stderr-ob)
(current-input-port stdin-ib))
(apply (if sudo?
system*/exit-code/sudo
system*/exit-code)
command
args)))
(stdout-bytes (get-output-bytes stdout-ob))
(stderr-bytes (get-output-bytes stderr-ob)))
(if (zero? exit-code)
(void)
(error error-name
"command ~S had non-zero exit code (exit-code ~S, stderr ~S, stdout ~S)"
(cons command args)
exit-code
stderr-bytes
stdout-bytes)))))
(define (install-rackout-live
#:image-path image-path
#:disk-path disk-path
#:erase-disk? (erase-disk? #false)
#:want-home? (want-home? #true)
#:force-disk-path? (force-disk-path? #false)
)
(let* ((image-path (cleanse-path image-path))
(disk-path (cleanse-path disk-path)))
(or (regexp-match? #rx"\\.(?:img|iso)$" (path->string image-path))
(error 'install-rackout-live
"Image path ~S unacceptable; must end in \".img\" or \".iso\"."
(path->string image-path)))
(or force-disk-path?
(regexp-match? #rx"^/dev/disk/by-id/usb-" (path->string disk-path))
(error 'install-rackout-live
"Disk path ~S unacceptable; must start with \"/dev/disk/by-id/usb-\", to reduce accidents."
(path->string disk-path)))
(log-debug (format "Getting initial partition info for disk ~S..."
(path->string disk-path)))
(let* ((initial-disk (get-parted-disk #:disk disk-path))
(initial-partitions (parted-disk-partitions initial-disk))
(initial-partitions-length (length initial-partitions)))
(log-debug (format "Disk ~S has ~S partition(s)."
(path->string disk-path)
initial-partitions-length))
(let-values (((partitions-to-restore create-home?)
(if (or erase-disk? (zero? initial-partitions-length))
(values '() want-home?)
(let loop ((initial-partitions initial-partitions)
(live-partition #f)
(partitions-to-restore '()))
(if (null? initial-partitions)
(cond ((not live-partition)
(error 'install-rackout-live
"Could not find existing (likely) RackOut Live partition on disk ~S."
(path->string disk-path)))
(else (values (reverse partitions-to-restore) #f)))
(let ((partition (car initial-partitions)))
(if (equal? 1 (parted-partition-number partition))
(if (and (equal? "fat16" (parted-partition-filesystem partition))
(<= 1e8 (parted-partition-size partition) 7e8))
(if live-partition
(error 'install-rackout-live
"There are two partitions that appear to be RackOut Live: ~S ~S"
live-partition
partition)
(loop (cdr initial-partitions)
partition
partitions-to-restore))
(error 'install-rackout-live
"Partition #1 does not appear to be RackOut Live: ~S"
partition))
(loop (cdr initial-partitions)
live-partition
(cons partition partitions-to-restore)))))))))
(or (file-exists? image-path)
(error 'install-rackout-live
"image file ~S does not exist"
(path->string image-path)))
(log-debug (format "Copying image ~S to disk ~S..."
(path->string image-path)
(path->string disk-path)))
(dynamic-wind
void
(lambda ()
(system-command/ignored-output
#:error-name 'install-rackout-live
#:sudo? #true
#:command "/bin/dd"
#:args (list (string-append "if=" (path->string image-path))
(string-append "of=" (path->string disk-path))))
(and create-home?
(begin
(log-debug "Creating home partition...")
(log-warning "Not actually creating home partition!!!")
)))
(lambda ()
(if (null? partitions-to-restore)
(log-debug "No partitions to restore.")
(begin
(log-debug "Restoring any partitions...")
(for-each (lambda (partition)
(log-debug (format "Restoring partition ~S..."
partition))
(with-handlers ((exn:fail? (lambda (e)
(log-warning (format "Exception while restoring partition ~S: ~A"
partition
(exn-message e))))))
(parted-mkpart/partition #:disk disk-path
#:partition partition)))
partitions-to-restore)
(log-debug "Doing a \"sync\"...")
(with-handlers ((exn:fail? (lambda (e)
(log-warning (format "Exception while sync-ing: ~A"
(exn-message e))))))
(system-command/ignored-output
#:error-name 'install-rackout-live
#:command "/bin/sync"))))))))))
(define (get-rackout-boot-device-path-string)
(error 'get-rackout-boot-device-path-string
"!!! UNIMPLEMENTED !!!"))
(let ((image-path-string #f)
(disk-path-string #f)
(erase-disk? #false)
(want-home? #true)
(force-disk-path? #false))
(command-line
#:program "rackout-live-install"
#:once-each
(("-i" "--image") file
"!!!"
(set! image-path-string file))
(("--image-from-boot")
"!!!"
(set! image-path-string (get-rackout-boot-device-path-string)))
#:once-each
(("-d" "--disk") file
"!!!"
(set! disk-path-string file))
#:once-any
("--erase-disk" "!!!" (set! erase-disk? #true))
("--no-erase-disk" "!!!" (set! erase-disk? #false))
#:once-any
("--want-home" "!!!" (set! want-home? #true))
("--no-want-home" "!!!" (set! want-home? #false))
#:once-any
("--force-disk-path" "!!!" (set! force-disk-path? #true))
("--no-force-disk-path" "!!!" (set! force-disk-path? #false))
)
(or image-path-string
(error "Must specify \"--image\"."))
(or disk-path-string
(error "Must specify \"--disk\"."))
(install-rackout-live #:image-path image-path-string
#:disk-path disk-path-string))