#!/usr/bin/wish -f

# 
# File system tool
# (C) Copyright 1994 by Red Hat Software

if {[catch set env(CONTROL_PANEL_LIB_DIR)] != 0} {
    set env(CONTROL_PANEL_LIB_DIR) /usr/lib/rhs/control-panel
}
if {[catch {source $env(CONTROL_PANEL_LIB_DIR)/dialog.tcl}] != 0} {
    puts "Couldn't load dialog.tcl"
    puts "Start from control-panel or set environment variable"
    puts "CONTROL_PANEL_LIB_DIR to the control-panel library dir."
    puts "(normally this is /usr/lib/rhs/control-panel)"
    exit 0
}
if {[catch {source $env(CONTROL_PANEL_LIB_DIR)/bindings.tcl}] != 0} {
    puts "Couldn't load bindings.tcl"
    puts "Start from control-panel or set environment variable"
    puts "CONTROL_PANEL_LIB_DIR to the control-panel library dir."
    puts "(normally this is /usr/lib/rhs/control-panel)"
    exit 0
}

#########################################################
## @@ Random Data

# trigger is used to watch for "OK" and "Cancel" presses
set trigger 0

set selected_type ""
set selected_hint ""
set delete_index ""

set fsck "$env(CONTROL_PANEL_LIB_DIR)/fstool-aux fsck"
set mkfs "$env(CONTROL_PANEL_LIB_DIR)/fstool-aux mkfs"

set short_partitiontype(Empty) EMTY
set short_partitiontype(AIX) AIX
set short_partitiontype(PC/IX) PCIX
set short_partitiontype(BSDI_swap) BSDS
set short_partitiontype(DOS_12-bit_FAT) DS12
set short_partitiontype(AIX_bootable) AIXB
set short_partitiontype(Old_MINIX) OMNX
set short_partitiontype(Syrinx) SYRX
set short_partitiontype(XENIX_root) XNXR
set short_partitiontype(OPUS) OPUS
set short_partitiontype(Linux/MINIX) LINM
set short_partitiontype(CP/M) CPM
set short_partitiontype(XENIX_usr) XNXU
set short_partitiontype(Venix_80286) VNX
set short_partitiontype(Linux_swap) LNXS
set short_partitiontype(DOS_access) DOSA
set short_partitiontype(DOS_16-bit_<32M) DOS<
set short_partitiontype(Novell) NOVL
set short_partitiontype(Linux_native) LNUX
set short_partitiontype(DOS_R/O) DOSR
set short_partitiontype(Extended) EXTD
set short_partitiontype(Microport) MPRT
set short_partitiontype(Amoeba) AMBA
set short_partitiontype(DOS_secondary) DOS2
set short_partitiontype(DOS_16-bit_>=32) DOS>
set short_partitiontype(GNU_HURD) HURD
set short_partitiontype(Amoeba_BBT) AMBB
set short_partitiontype(BBT) BBT
set short_partitiontype(OS/2_HPFS) OS2
set short_partitiontype(Novell) NOVL
set short_partitiontype(BSDI_fs) BSDF
set short_partitiontype(nfs) NFS
set short_partitiontype(na) NA
set short_partitiontype(unknown) UKNN

set long_partitiontype(Empty) "Empty"
set long_partitiontype(AIX) "AIX"
set long_partitiontype(PC/IX) "PC/IX"
set long_partitiontype(BSDI_swap) "BSDI swap"
set long_partitiontype(DOS_12-bit_FAT) "DOS 12-bit FAT"
set long_partitiontype(AIX_bootable) "AIX bootable"
set long_partitiontype(Old_MINIX) "Old MINIX"
set long_partitiontype(Syrinx) "Syrinx"
set long_partitiontype(XENIX_root) "XENIX root"
set long_partitiontype(OPUS) "OPUS"
set long_partitiontype(Linux/MINIX) "Linux/MINIX"
set long_partitiontype(CP/M) "CP/M"
set long_partitiontype(XENIX_usr) "XENIX usr"
set long_partitiontype(Venix_80286) "Venix 80286"
set long_partitiontype(Linux_swap) "Linux swap"
set long_partitiontype(DOS_access) "DOS access"
set long_partitiontype(DOS_16-bit_<32M) "DOS 16-bit <32M"
set long_partitiontype(Novell) "Novell"
set long_partitiontype(Linux_native) "Linux native"
set long_partitiontype(DOS_R/O) "DOS R/O"
set long_partitiontype(Extended) "Extended"
set long_partitiontype(Microport) "Microport"
set long_partitiontype(Amoeba) "Amoeba"
set long_partitiontype(DOS_secondary) "DOS secondary"
set long_partitiontype(DOS_16-bit_>=32) "DOS 16-bit >=32"
set long_partitiontype(GNU_HURD) "GNU HURD"
set long_partitiontype(Amoeba_BBT) "Amoeba BBT"
set long_partitiontype(BBT) "BBT"
set long_partitiontype(OS/2_HPFS) "OS/2 HPFS"
set long_partitiontype(Novell) "Novell"
set long_partitiontype(BSDI_fs) "BSDI fs"
set long_partitiontype(nfs) "Network File System"
set long_partitiontype(na) "Not Applicable"
set long_partitiontype(unknown) "Unknown"
set advanced 0

#########################################################
## @@ User interface

frame .menuf -relief raised -borderwidth 2
menubutton .menuf.fsm -text "FSM" -menu .menuf.fsm.menu
menu .menuf.fsm.menu
.menuf.fsm.menu add command -label "Reload" -command menu_reload
.menuf.fsm.menu add command -label "Set Options" -command menu_options
.menuf.fsm.menu add separator
#.menuf.fsm.menu add command -label "About" -command menu_about
#.menuf.fsm.menu add separator
.menuf.fsm.menu add command -label "Add Mount" -command menu_fsm_add_mount
.menuf.fsm.menu add command -label "Delete Mount" -command menu_fsm_delete_mount
.menuf.fsm.menu add separator
.menuf.fsm.menu add command -label "Quit" -command menu_quit
menubutton .menuf.nfs -text "NFS" -menu .menuf.nfs.menu
menu .menuf.nfs.menu
.menuf.nfs.menu add command -label "Add Mount" -command menu_nfs_add_mount
.menuf.nfs.menu add command -label "Delete Mount" -command menu_nfs_delete_mount
.menuf.nfs.menu add command -label "Edit Exports" -command menu_nfs_edit_exports

pack .menuf.fsm .menuf.nfs -side left -in .menuf
tk_menuBar .menuf .menuf.fsm .menuf.nfs

label .header -font fixed -text "Header stuff" -anchor w

frame .main -relief sunken -borderwidth 2
listbox .list -font fixed -yscrollcommand ".sb set" -setgrid 1 -exportselection 0 -selectmode single
scrollbar .sb -command ".list yview"
pack .list -side left -expand true -fill both -in .main
pack .sb -side left -fill y -in .main

frame .buttons
button .info -text "Info" -width 10 -command button_info
button .fsck -text "Check" -width 10 -command button_fsck
button .mount -text "Mount" -width 10 -command button_mount
button .umount -text "Unmount" -width 10 -command button_umount
button .mkfs -text "Format" -width 10 -command button_mkfs
button .edit -text "Edit" -width 10 -command button_edit
pack .info .fsck .mount .umount .mkfs .edit -side left -expand true -ipady 1 -in .buttons

pack .menuf -side top -fill x
# The following large padx is so that the header
# lines up properly with the stuff in the listbox
pack .header -side top -fill x -padx 8
pack .main -side top -expand true -fill both -padx 4
pack .buttons -side top -fill x -padx 4 -pady 4

wm minsize . 20 10
wm title . "RHS Linux File System Manager"

bind .list <Double-Button-1> "
    .list selection clear 0 end
    .list selection set \[.list nearest %y\]
    button_edit
"

## End of main user interface
###########################################################
## @@ Random Functions

proc getfsindex {fsn} {
    global fscount fsname

    for {set i 0} {$i < $fscount} {incr i} {
	if {$fsname($i) == $fsn} {
	    return $i
	}
    }

    return -1
}

proc reload {} {
    global fscount
    global fsname fspartitiontype
    global fssize fsused fsavail fscapacity
    global fsmountpoint fsopts
    global fsmounted
    global fstab fstabmountpoint fstabfstype fstabopts fstabcomment
    global fstabdump fstabfsck

    # First we load stuff from dfplus

    set fd [open "| dfplus" r]
    set fscount 0
    while {[gets $fd s] != "-1"} {
	set fsname($fscount) [lindex $s 0]
	set fspartitiontype($fscount) [lindex $s 1]
	set fssize($fscount) [lindex $s 2]
	set fsused($fscount) [lindex $s 3]
	set fsavail($fscount) [lindex $s 4]
	set fscapacity($fscount) [lindex $s 5]
	set fsmountpoint($fscount) [lindex $s 6]
	if {$fsmountpoint($fscount) != "<none>"} {
	    set fsmounted($fscount) "*"
	} else {
	    set fsmountpoint($fscount) "none"
	    set fsmounted($fscount) " "
	}
	set fsopts($fscount) [lindex $s 7]

	# Set these up for later
	set fstabdump($fscount) 0
	set fstabfsck($fscount) 0
	set fstab($fscount) " "
	set fstabmountpoint($fscount) ""
	if [regexp HPFS $fspartitiontype($fscount)] {
	    set fstabfstype($fscount) hpfs
	    set fstabopts($fscount) "defaults"
	} else {
	    set fstabfstype($fscount) ignore
	    set fstabopts($fscount) ""
	}
	set fstabcomment($fscount) ""
	incr fscount
    }
    close $fd

    # Now we read in /etc/fstab
    set fd [open "/etc/fstab" r]
    while {[gets $fd s] != "-1"} {
	if {[llength $s] == 0} {
	    continue
	}
	set cp [lindex $s 0]
	set valid_comment 0
	if {[regexp "^\#" $cp] == 1} {
	    # First check for a comment
	    set valid_comment [regexp "\#% (.*)" $s bogus comment]
	    continue
	}

	set i [getfsindex $cp]
	if {$i == -1} {
	    # Set this here so we can over-ride it
	    set i $fscount
	    set fspartitiontype($i) "nfs"

	    # This is entry was not found by dfplus
	    # It is probably an NFS partition, a partition that was deleted,
	    # or another device (like a floppy)
	    # If not NFS, ask user if we should delete it.
	    if {[lindex $s 2] != "nfs" &&
		([string match "/dev/hd??*" $cp] ||
		 [string match "/dev/sd??*" $cp])} {
		set nt "Partition $cp seems to have been deleted.\nShall I remove it from /etc/fstab ?"
		if {[rhs_dialog .r "Notice" $nt question 0 "Remove It" "Leave It"] == 0} {
		    continue
		}
		set fspartitiontype($i) "na"
	    }
	    incr fscount

	    # Fill in some stuff
	    set fsname($i) $cp
	    set fsmounted($i) " "
	    set fsmountpoint($i) "none"
	    set fstabfstype($i) "ignore"
	    set fssize($i) "na"
	    set fsused($i) "na"
	    set fsavail($i) "na"
	    set fscapacity($i) "na"
	    set fsopts($i) "na"
	}

	set fstab($i) "F"
	set fstabmountpoint($i) [lindex $s 1]
	if { $fstabfstype($i) == "ignore" } {
	    set fstabfstype($i) [lindex $s 2]
	    set fstabopts($i) [lindex $s 3]
	}
	# Set the new dump and fsck options here
	set fstabdump($i) [lindex $s 4]
	if {$fstabdump($i) == ""} {
	    set fstabdump($i) 0
	}
	set fstabfsck($i) [lindex $s 5]
	if {$fstabfsck($i) == ""} {
	    set fstabfsck($i) 0
	}
	
	# Set mount point if we don't have it already
	# From this point on, we only care about fsmountpoint
	if {$fsmountpoint($i) == "none"} {
	    set fsmountpoint($i) $fstabmountpoint($i)
	}
	
	# Fix values for proc filesystem
	if {$fstabfstype($i) == "proc"} {
	    set fspartitiontype($i) "na"
	    set fssize($i) "na"
	    set fsused($i) "na"
	    set fsavail($i) "na"
	    set fscapacity($i) "na"
	}
	
	if {$valid_comment == 1} {
	    set valid_comment 0
	    set fstabcomment($i) $comment
	} else {
	    set fstabcomment($i) ""
	}
    }
    close $fd
}

proc gen_format {} {
    global options
    global header_string
    global format_string eval_string

    set es {$fsname($i) $fsmountpoint($i) $fsmounted($i)}
    set fs "%-15s %-15s %1.1s"
    set hs [format $fs "Device" "Mount Point" M]
    if {$options(fstabfstype) == 1} {
	append es " " {$fstabfstype($i)}
	set fs "$fs %-7s"
	set hs "$hs [format "%-7s" "Type"]"
    }
    if {$options(fspartitiontype) == 1} {
	append es " " {$fspartitiontype($i)}
	set fs "$fs %-15s"
	set hs "$hs [format "%-15s" "Partition Type"]"
    }
    if {$options(fssize) == 1} {
	append es " " {$fssize($i)}
	set fs "$fs %7s"
	set hs "$hs [format "%7s" "Size"]"
    }
    if {$options(fsused) == 1} {
	append es " " {$fsused($i)}
	set fs "$fs %7s"
	set hs "$hs [format "%7s" "Used"]"
    }
    if {$options(fsavail) == 1} {
	append es " " {$fsavail($i)}
	set fs "$fs %7s"
	set hs "$hs [format "%7s" "Avail"]"
    }
    if {$options(fscapacity) == 1} {
	append es " " {$fscapacity($i)}
	set fs "$fs %3s"
	set hs "$hs [format "%3s" "%"]"
    }
    if {$options(fstabopts) == 1} {
	append es " " {$fstabopts($i)}
	set fs "$fs %-15s"
	set hs "$hs [format "%-15s" "fstab Options"]"
    }
    if {$options(fsopts) == 1} {
	append es " " {$fsopts($i)}
	set fs "$fs %-15s"
	set hs "$hs [format "%-15s" "Mounted Options"]"
    }
    if {$options(fstabcomment) == 1} {
	append es " " {$fstabcomment($i)}
	set fs "$fs %s"
	set hs "$hs [format "%s" "Comment"]"
    }

    set eval_string $es
    set format_string $fs
    set header_string $hs
}

proc redisplay {} {
    global fscount
    global fsname fspartitiontype
    global fssize fsused fsavail fscapacity
    global fsmountpoint fsopts
    global fsmounted fstab
    global fstab fstabmountpoint fstabfstype fstabopts fstabcomment
    global options header_string format_string eval_string
    global selected_hint

    .header configure -text $header_string

    set selected_name $selected_hint
#    set selected_offset [expr [lindex [.sb get] 1] * [.list]
    set cs [.list curselection]
    if {$cs != ""} {
	if {$selected_name == ""} {
	    set selected_name [lindex [.list get $cs] 0]
	}
#	set selected_offset [expr $cs - [lindex [.sb get] 2]]
    }

    .list delete 0 end
    set selected_index -1
    for {set i 0} {$i < $fscount} {incr i} {
	.list insert end [eval format \"$format_string\" $eval_string]
	if {$selected_name == $fsname($i)} {
	    set selected_index $i
	}
    }
    if {$selected_index != -1} {
	.list select set $selected_index
	.list see $selected_index
    }
    set selected_hint ""
}

proc sort_by_fsmountpoint {a b} {
    global fsmountpoint

    string compare $fsmountpoint($a) $fsmountpoint($b)
}

proc write_fstab {} {
    global fscount
    global fsname fspartitiontype
    global fssize fsused fsavail fscapacity
    global fsmountpoint fsopts
    global fsmounted fstab
    global fstab fstabmountpoint fstabfstype fstabopts fstabcomment
    global options header_string format_string eval_string
    global delete_index
    global fstabdump fstabfsck

    # First write the header
    # Then all regular filesystems
    # Then swap partitions
    # Then extended partitions
    # Then NFS mounts

    # delete_index only pertains to NFS mounts

    set fd [open "/etc/fstab" w]

    puts $fd "\#"
    puts $fd "\# /etc/fstab"
    puts $fd "\#"
    puts $fd "\# You should be using fstool (control-panel) to edit this!"
    puts $fd "\#"
    puts $fd "\# <device>    <mountpoint>   <filesystemtype> <options> <dump> <fsckorder>"

    puts $fd ""

    # We have to write fstabopts *not* fsopts
    
    # These entries need to be sorted by mount point
    set indexes ""
    for {set i 0} {$i < $fscount} {incr i} {
	if {$fstabfstype($i) != "swap" && \
		$fstabfstype($i) != "na" && \
		$fstabfstype($i) != "" && \
		$fstabfstype($i) != "proc"} {
	    catch {exec mkdir -p $fsmountpoint}
	}
	if {$fstabfstype($i) != "swap" && \
	    $fstabfstype($i) != "na" && \
	    $fstabfstype($i) != "" && \
	    $fstabfstype($i) != "proc" && \
	    $fstabfstype($i) != "nfs"} {
	    if {$i == $delete_index} {
		continue
	    }
	    set indexes "$indexes $i"
	}
    }
    
    foreach i [lsort -command sort_by_fsmountpoint $indexes] {
	    if {$fstabcomment($i) != ""} {
		puts $fd "\#% $fstabcomment($i)"
	    }
	    puts $fd [format "%-25s %-25s %-6s %s %d %d" $fsname($i) \
			  $fsmountpoint($i) $fstabfstype($i) $fstabopts($i) \
			 $fstabdump($i) $fstabfsck($i)]
    }

    puts $fd ""
    for {set i 0} {$i < $fscount} {incr i} {
	if {$fstabfstype($i) == "proc"} {
	    if {$i == $delete_index} {
		continue
	    }
	    if {$fstabcomment($i) != ""} {
		puts $fd "\#% $fstabcomment($i)"
	    }
	    puts $fd [format "%-25s %-25s proc   %s" \
		      $fsname($i) $fsmountpoint($i) \
		      $fstabopts($i)]
	}
    }
    for {set i 0} {$i < $fscount} {incr i} {
	if {$fstabfstype($i) == "swap"} {
	    if {$i == $delete_index} {
		continue
	    }
	    if {$fstabcomment($i) != ""} {
		puts $fd "\#% $fstabcomment($i)"
	    }
	    puts $fd [format "%-25s none                      swap   sw" $fsname($i)]
	}
    }
    puts $fd ""
    for {set i 0} {$i < $fscount} {incr i} {
	if {$fstabfstype($i) == "na" || $fstabfstype($i) == ""} {
	    if {$i == $delete_index} {
		continue
	    }
	    if {$fstabcomment($i) != ""} {
		puts $fd "\#% $fstabcomment($i)"
	    }
	    puts $fd [format "%-25s %-25s ignore %s" $fsname($i) $fsmountpoint($i) \
		      $fstabopts($i)]
	}
    }
    for {set i 0} {$i < $fscount} {incr i} {
	if {$fstabfstype($i) == "nfs"} {
	    if {$i == $delete_index} {
		continue
	    }
	    if {$fstabcomment($i) != ""} {
		puts $fd "\#% $fstabcomment($i)"
	    }
	    puts $fd [format "%-25s %-25s nfs    %s" $fsname($i) $fsmountpoint($i) \
		      $fstabopts($i)]
	}
    }

    close $fd
    set delete_index ""
}

proc sync {} {
    write_fstab
    reload
    write_fstab
    redisplay
}

proc get_selected_index {} {
    return [.list curselection]
}

proc bind_entries_for_traversal {l} {
    set count [llength $l]
    lappend l [lindex $l 0]
    for {set i 0} {$i < $count} {incr i} {
	set from [lindex $l $i]
	set to [lindex $l [expr $i + 1]]
	set_emacs_entry_bindings $from
	bind $from <Return> "focus $to"
	bind $from <Tab> "focus $to"
    }
}

proc info_or_edit {t i} {
    global trigger
    global long_partitiontype
    global fsname fspartitiontype
    global fssize fsused fsavail fscapacity
    global fsmountpoint fsopts
    global fsmounted fstab
    global fstab fstabmountpoint fstabfstype fstabopts fstabcomment
    global selected_hint

    # Edit -> fstabcomment fstabopts fsmountpoint
    # For mounted filessytem, can only edit fstabcomment

    # If y != "" then edit fsname fstabfstype
    set nothdsd "y"
    if {[string match "/dev/hd*" $fsname($i)] || 
	[string match "/dev/sd*" $fsname($i)]} {
	set nothdsd "n"
    }

    set mounted $fsmounted($i)
    # For swap and extended partitions can only edit comment
    # We check swap against fstabfstype because the tag on
    # the partition doesn't really mean anything.
    if {$fspartitiontype($i) == "Extended" || $fstabfstype($i) == "swap"} {
	# Just pretend it's mounted
	set mounted "*"
    }
    
    toplevel .e
    wm withdraw .e
    if {$t == "e"} {
	wm title .e "Edit Filesystem"
    } else {
	wm title .e "FS Info"
    }

    frame .e.f1
    frame .e.f2

    set traversal_list ""

    label .e.l1 -text "Device" -anchor w
    if {$mounted != "*" && $t == "e" &&
	($fstabfstype($i) == "nfs" || $nothdsd == "y")} {
	# editable device or NFS
	entry .e.v1 -font fixed -relief sunken -borderwidth 2
	lappend traversal_list .e.v1
    } else {
	label .e.v1 -text "$fsname($i)" -anchor w -font fixed
    }

    label .e.l2 -text "Partition Type" -anchor w
    label .e.v2 -text "$long_partitiontype($fspartitiontype($i))" -anchor w -font fixed

    label .e.l3 -text "FS Type" -anchor w
    if {$mounted != "*" && $nothdsd == "y" && $fstabfstype($i) != "nfs"} {
	entry .e.v3 -font fixed -relief sunken -borderwidth 2
	lappend traversal_list .e.v3
    } else {
	label .e.v3 -text "$fstabfstype($i)" -anchor w -font fixed
    }

    label .e.l4 -text "Mount Point" -anchor w
    label .e.l5 -text "Mount Options" -anchor w
    label .e.l51 -text "Current Options" -anchor w
    label .e.l6 -text "Comment" -anchor w
    label .e.l7 -text "Size" -anchor w
    label .e.l8 -text "Used" -anchor w
    label .e.l9 -text "Available" -anchor w
    label .e.l0 -text "Percent Used" -anchor w

    if {$mounted == "*" || $t != "e"} {
	label .e.v4 -font fixed -anchor w -text "$fsmountpoint($i)"
	label .e.v5 -font fixed -anchor w -text "$fstabopts($i)"
    } else {
	entry .e.v4 -font fixed -relief sunken -borderwidth 2
	entry .e.v5 -font fixed -relief sunken -borderwidth 2
	lappend traversal_list .e.v4
	lappend traversal_list .e.v5
    }
    label .e.v51 -font fixed -anchor w -text "$fsopts($i)"
    if {$t == "e"} {
	entry .e.v6 -font fixed -relief sunken -borderwidth 2
	lappend traversal_list .e.v6
    } else {
	label .e.v6 -font fixed -anchor w -text "$fstabcomment($i)"
    }
    label .e.v7 -font fixed -anchor w -text "$fssize($i)"
    label .e.v8 -font fixed -anchor w -text "$fsused($i)"
    label .e.v9 -font fixed -anchor w -text "$fsavail($i)"
    label .e.v0 -font fixed -anchor w -text "$fscapacity($i)"

    if {$t == "e"} {
	if {$mounted != "*"} {
	    .e.v4 insert 0 $fsmountpoint($i)
	    .e.v5 insert 0 $fstabopts($i)
	    if {$fstabfstype($i) == "nfs" || $nothdsd == "y"} {
		.e.v1 insert 0 $fsname($i)
	    }
	    if {$fstabfstype($i) != "nfs" && $nothdsd == "y"} {
		.e.v3 insert 0 $fstabfstype($i)
	    }
	}
	.e.v6 insert 0 $fstabcomment($i)
    }

    pack .e.l1 .e.l2 .e.l3 .e.l4 .e.l5 \
         .e.l51 .e.l6 .e.l7 .e.l8 .e.l9 .e.l0 \
	-side top -expand true -fill x -in .e.f1
    pack .e.v1 .e.v2 .e.v3 .e.v4 .e.v5 \
         .e.v51 .e.v6 .e.v7 .e.v8 .e.v9 .e.v0 \
	-side top -expand true -fill x -in .e.f2

    frame .e.buttons
    button .e.b1 -text "OK" -width 10 -command "set trigger 1"
    pack .e.b1 -side left -expand true -ipady 1 -in .e.buttons
    if {$t == "e"} {
	button .e.b2 -text "Cancel" -width 10 -command "set trigger 0"
	pack .e.b2 -side left -expand true -ipady 1 -in .e.buttons
    }

    pack .e.buttons -side bottom -expand true -fill x -padx 4 -pady 4 -in .e
    pack .e.f1 .e.f2 -side left -expand true -fill both -padx 2 -pady 1 -in .e

    if {$t == "e"} {
	bind_entries_for_traversal $traversal_list
    }
    center_dialog .e
    grab set .e
    tkwait variable trigger
    if {$t == "e" && $trigger == 1} {

	# Get values
	if {$mounted != "*"} {
	    set fsmountpoint($i) [.e.v4 get]
	    set fstabopts($i) [.e.v5 get]
	    if {$fstabfstype($i) == "nfs" || $nothdsd == "y"} {
		set fsname($i) [.e.v1 get]
	    }
	    if {$fstabfstype($i) != "nfs" && $nothdsd == "y"} {
		set fstabfstype($i) [.e.v3 get]
	    }
	}
	set fstabcomment($i) [.e.v6 get]

	destroy .e
	set selected_hint $fsname($i)
    } else {
	destroy .e
    }
    return $trigger
}

## End of random functions
##############################################
## @@ User Interface callback functions

proc menu_nfs_edit_exports {} {

    rhs_info_dialog "
Editing of /etc/exports is not supported at this time.

When editing /etc/exports remember that nfsd is accessed
through portmap, which in turn uses /etc/hosts.allow and
/etc/hosts.deny for access control.

In order for a host to mount an exported filesystem, it
must be allowed portmap access in /etc/hosts.allow, and
nfsd access in /etc/exports.
"

}

proc button_info {} {
    set i [get_selected_index]
    if {$i == ""} {
	return
    }

    info_or_edit t $i
}

proc button_edit {} {
    global fspartitiontype
    set i [get_selected_index]
    if {$i == ""} {
	return
    }

    if {$fspartitiontype($i) == "nfs"} {
	set v [nfs_info_or_edit e $i]
    } else {
	set v [info_or_edit r $i]
    }
    if {$v == 1} {
	sync
    }
}

proc button_umount {} {
    global fsname fsmounted fstabfstype selected_hint

    set i [get_selected_index]
    if {$i == ""} {
	return
    }

    if {$fstabfstype($i) != "swap"} {
	# Must be mounted
	if {$fsmounted($i) != "*"} {
	    rhs_error_dialog "$fsname($i) is not mounted"
	    return
	}
	set res [catch {exec umount $fsname($i)} error_string]
	if {$res != 0} {
	    rhs_error_dialog "umount $fsname($i)\n\nreturned the following error:\n\n$error_string"
	    update idletasks
	}
    } else {
	# swapoff
	set res [catch {exec swapoff $fsname($i)} error_string]
	if {$res != 0} {
	    rhs_error_dialog "swapoff $fsname($i)\n\nreturned the following error:\n\n$error_string\n\n(It's probably not in swap space)"
	} else {
	    rhs_info_dialog "Device $fsname($i) removed from swap space."
	}
	update idletasks
    }
    set selected_hint $fsname($i)
    sync
}

proc button_mount {} {
    global fsname fsmounted fstabfstype selected_hint

    set i [get_selected_index]
    if {$i == ""} {
	return
    }

    # Must not already be mounted
    if {$fsmounted($i) == "*"} {
	rhs_error_dialog "$fsname($i) is already mounted"
	return
    }
    
    # Must not have fstabfstype of ignore
    if {$fstabfstype($i) == "ignore"} {
	rhs_error_dialog "Can't mount $fsname($i)\nIts FS Type is $fstabfstype($i)"
	return
    }

    if {$fstabfstype($i) != "swap"} {
	set res [catch {exec mount $fsname($i)} error_string]
	if {$res != 0} {
	    rhs_error_dialog "mount $fsname($i)\n\nreturned the following error:\n\n$error_string"
	    update idletasks
	}
    } else {
	# swapon
	set res [catch {exec swapon $fsname($i)} error_string]
	if {$res != 0} {
	    rhs_error_dialog "swapon $fsname($i)\n\nreturned the following error:\n\n$error_string\n\n(It's probably already added to swap space)"
	} else {
	    rhs_info_dialog "Device $fsname($i) added to swap space."
	}
	update idletasks
    }
    set selected_hint $fsname($i)
    sync
}

proc button_fsck {} {
    global fsmounted fsopts fstabfstype fsname fsck
    # fstool.fsck -af $fsname($i)
    # display output

    set i [get_selected_index]
    if {$i == ""} {
	return
    }

    # Must be ext2 or minix
    if {$fstabfstype($i) != "ext2" && $fstabfstype($i) != "minix"} {
	rhs_error_dialog "Can only check ext2 and minix filesystems"
	return
    }

    # Must either be unmounted or mounted ro
    if {$fsmounted($i) == "*"} {
	# Check for read only
	set opts [split $fsopts($i) ","]
	if {[lsearch -exact $opts "ro"] == -1} {
	    rhs_error_dialog "You can not check a filesystem mounted read-write.\nUnmount it before checking."
	    return
	}
    }

    catch {eval exec $fsck -af $fsname($i)} output
    rhs_dialog .o "fsck output" $output info 0 "OK"
}

proc button_mkfs {} {
    global fsmounted fsname fspartitiontype fstabfstype fssize
    global selected_type trigger mkfs fstabopts

    # Offer fs types (ext2, minix)
    # mkfs -t <type> $fsname($i)

    set i [get_selected_index]
    if {$i == ""} {
	return
    }

    # Must not be mounted
    if {$fsmounted($i) == "*"} {
	rhs_error_dialog "Can not make a file system\non a mounted partition."
	return
    }

    # Must not be nfs or extended
    if {$fspartitiontype($i) == "nfs" || $fspartitiontype($i) == "Extended"} {
	rhs_error_dialog "Can not make a file system on\nnfs or extended \"partitions\""
	return
    }

    # Warn if already has fstabfstype (probably already formated)
    if {$fstabfstype($i) != "ignore"} {
	if {$fstabfstype($i) == "swap"} {
	    set res [rhs_continue_dialog "This appears to be a swap partition.\nI will attempt to \"swapoff\" this partition\nbefore continuing."]
	} else {
	    set res [rhs_continue_dialog "This partition appears to already have been\nformatted.  Do you wish to continue?"]
	}
	if {$res == 1} {
	    return
	}
    }

    if {$fstabfstype($i) == "swap"} {
	catch {exec swapoff $fsname($i)}
    }

    set selected_type ext2

    toplevel .o
    wm withdraw .o
    wm title .o "Filesystem Type"

    label .o.l -text "Please choose a filesystem type"
    pack .o.l -side top -expand true -fill both -in .o

    radiobutton .o.r1 -text "Extended 2" -variable selected_type -value ext2
    radiobutton .o.r2 -text "Minix" -variable selected_type -value minix
    radiobutton .o.r3 -text "Swap" -variable selected_type -value swap
    pack .o.r1 .o.r2 .o.r3 -side top -expand true -fill both -in .o

    button .o.ok -text "OK" -width 10 -command "set trigger 1"
    button .o.cancel -text "Cancel" -width 10 -command "set trigger 0"
    pack .o.ok .o.cancel -side left -expand true -padx 4 -pady 4 -in .o

    center_dialog .o
    grab set .o
    tkwait variable trigger
    destroy .o

    if {$trigger == 1} {
	set fstabfstype($i) $selected_type
	set res [catch {eval exec $mkfs -t $selected_type $fsname($i) $fssize($i)} error_string]
	if {$selected_type == "swap"} {
	    set fstabopts($i) "sw"
	} else {
	    set fstabopts($i) "defaults"
	}
	if {$res != 0} {
	    if {$selected_type == "swap"} {
		rhs_error_dialog "mkswap $fsname($i) $fssize($i)\n\nreturned the following error:\n\n$error_string"
	    } else {
		rhs_error_dialog "mkfs -t $selected_type $fsname($i) $fssize($i)\n\nreturned the following error:\n\n$error_string"
	    }
	    # safety value
	    set fstabfstype($i) "ignore"
	    update idletasks
	}
	sync
    }
}

proc menu_options {} {
    global options

    toplevel .o
    wm withdraw .o
    wm title .o "Display Options"

    checkbutton .o.b1 -text "File System Type" -variable options(fstabfstype) -anchor w
    checkbutton .o.b2 -text "Partition Type" -variable options(fspartitiontype) -anchor w
    checkbutton .o.b3 -text "Size" -variable options(fssize) -anchor w
    checkbutton .o.b4 -text "Used" -variable options(fsused) -anchor w
    checkbutton .o.b5 -text "Avail" -variable options(fsavail) -anchor w
    checkbutton .o.b6 -text "% Used" -variable options(fscapacity) -anchor w
    checkbutton .o.b7 -text "/etc/fstab Options" -variable options(fstabopts) -anchor w
    checkbutton .o.b8 -text "Mounted Options" -variable options(fsopts) -anchor w
    checkbutton .o.b9 -text "Comment" -variable options(fstabcomment) -anchor w
    
    button .o.ok -text "OK" -width 10 -command "destroy .o"

    pack .o.b1 .o.b2 .o.b3 .o.b4 .o.b5 .o.b6 .o.b7 .o.b8 .o.b9 -side top -expand true -fill x -padx 2 -pady 1

    pack .o.ok -expand true -padx 4 -pady 4

    center_dialog .o
    grab set .o
    tkwait window .o
    gen_format
    redisplay
}

proc entry-with-label {row path text var} {
    label $path-l -text "$text" 
    entry $path-e -text $var -width 10
    grid $path-l -row $row -column 1 -sticky w
    grid $path-e -row $row -column 2 -sticky ew
}

proc ckbutton {widget str var args} {
    if {$args == ""}  {
       checkbutton $widget -text "$str" -variable $var -pady 0
    } else {
       checkbutton $widget -text "$str" -variable $var -pady 0 -command "$args"
    }
}

proc put-advanced {frame} {
    global fsconfig

    set a [frame $frame.a]
    set b [frame $frame.b]
    set c [frame $frame.c]
    set ab [frame $frame.a-b]

    ckbutton $a.write "Write" fsconfig(write)

    # Options that apply to all the file systems
    ckbutton $a.auto  "Automatically mount at startup" fsconfig(auto)
    ckbutton $a.suid  "Allow set-user-identifier or set-group-identifier" fsconfig(suid)
    ckbutton $a.dev   "Interpret the character and block devices" fsconfig(dev)
    ckbutton $a.exec  "Allow programs to be executed from this fs" fsconfig(exec)
    ckbutton $a.user  "Allow an ordinary user to mount the fs" fsconfig(user) "option-user $a"
    pack $a.write $a.auto $a.suid $a.dev $a.dev $a.exec $a.user -side top -anchor w
    
    # Options relative to NFS mounts
    ckbutton $a.intr  "On hard timeouts, allow signals to interrupt" fsconfig(intr)
    ckbutton $a.bg    "If first mount attempt fails, retry in background" fsconfig(bg)
    ckbutton $a.retry "Retry requests until server responds" fsconfig(retry)
    pack $a.intr $a.bg $a.retry -side top -anchor w

    entry-with-label 1 $b.rsize "Read buffer size"  fsconfig(rsize)
    entry-with-label 2 $b.wsize "Write buffer size" fsconfig(wsize)
    entry-with-label 3 $b.retry "Set number of mount retries" fsconfig(retries)
    entry-with-label 4 $b.timeo "NFA timeout (1/10th sec)" fsconfig(timeo)
    entry-with-label 5 $b.retr  "NFS retransmitions" fsconfig(retr)
    entry-with-label 6 $b.mport "NFS retransmitions" fsconfig(mport)

#    pack $b.rsize $b.wsize $b.retry $b.timeo $b.retr $b.mport -side top -anchor w  -fill x

    label $c.other-l -text "Other options: "
    entry $c.other-e 
    set xxx $fsconfig(other)
    puts "pone: $xxx"
    $c.other-e insert 0 "$xxx"

    frame $c.other-s 
    grid $c.other-s -row 1 -column 1 -padx 30
    grid $c.other-l -row 1 -column 2
    grid $c.other-e -row 1 -column 3 -sticky we -ipadx 50

    grid $a  -row 1 -column 1 -sticky w
    grid $ab -row 1 -column 2 -sticky w -padx 4
    grid $b  -row 1 -column 3 -sticky en
    grid $c  -row 2 -column 1 -columnspan 3 -sticky we

# NFS missing; tcp/udp
# namlen=, port=, mountport=, mounthost=, mountprog=, mountvers=,
# nfsprog=, nfsvers
}

proc advanced-on-off {top} {
    global advanced
 
    if {$advanced} {
	set advanced 0
	destroy $top.advanced.frame
	$top.advanced configure -height 1
	$top.control.advanced configure -text "Show advanced parameters"
    } else {
        set advanced 1
        frame .e.advanced.frame
	pack .e.advanced.frame
        put-advanced .e.advanced.frame
	$top.control.advanced configure -text "Hide advanced parameters"
    }
    tkwait window $top.control.advanced
}

proc lookup-hostname {top} {
    set host [$top.system-name-e get]
    if {$host != ""} {
	set soutput ""
	set soutput [exec /usr/sbin/showmount -e $host]
	set hlist [split $soutput \n]
	set count [llength $hlist]
	$top.exported-file-systems-t.l delete 0 end
	if {$count > 1} {
	    $top.exported-file-systems-t.l activate 0
	    $top.exported-file-systems-l configure -text [format "%d Available filesystems:" [expr $count -1]]
	    for {set i 1} {$i < $count} {incr i} {
		$top.exported-file-systems-t.l insert end [lindex $hlist $i]
	    }
	}
    }
    
}

proc set-remote-path {top} {
    set entry $top.remote-path-e
    set list  $top.exported-file-systems-t.l
    $entry delete 0 end
    set a [$list get active]
    set l [split $a " \t"]
    $entry insert 0 [lindex $l 0]
    $entry icursor end
}

proc load-fs-config {i} {
    global fsconfig
    global fsopts
    set options ",$fsopts($i),"

    set l { \
	{ ",rw,"      direct  rw } \
	{ ",auto,"    direct  auto } \
	{ ",noauto,"  inverse auto } \
	{ ",suid,"    direct  suid } \
	{ ",nosuid,"  invrse  suid } \
	{ ",dev,"     direct  dev } \
	{ ",nodev,"   inverse dev } \
	{ ",exec,"    direct  exec } \
	{ ",noexec,"  inverse exec } \
	{ ",user,"    direct  user } \
	{ ",nouser,"  inverse user } \
	{ ",intr,"    direct  intr } \
	{ ",nointr,"  inverse intr } \
	{ ",bg,"      direct  bg } \
	{ ",nobg,"    inverse bg } \
	{ ",fg,"      inverse bg } \
	{ ",nofg,"    direct  bg } \
	{ ",hard,"    direct  retry } \
	{ ",nohard,"  inverse retry } \
	{ ",soft,"    inverse retry } \
	{ ",nosoft,"  direct  retry } \
	\
	{ ",rsize=\[0-9\]+," value "rsize=" rsize } \
	{ ",wsize=\[0-9\]+," value "wsize=" wsize } \
	{ ",retry=\[0-9\]+," value "retry=" retries } \
	{ ",timeo=\[0-9\]+," value "timeo=" timeo } \
	{ ",retrans=\[0-9\]+," value "retrans=" retr } \
	{ ",mountport=\[0-9\]+," value "mountport=" mport } }

    set rcount [llength $l]

    for {set j 0} {$j < $rcount} {incr j} {
	set rinfo [lindex $l $j]
	set rexp  [lindex $rinfo 0]
	
	if [regexp $rexp $options val] {
	    regsub "," $val "" nval

	    switch [lindex $l 1] {
		direct  { set fsconfig([lindex $l 2]) 1 }
		inverse { set fsconfig([lindex $l 2]) 0 }
		value   { 
			  regsub [lindex $l 2] $nval "" xval
			  regsub "," $xval "" final
			  set fsconfig([lindex $l 3]) $final
		        }
	    }
	    regsub "," $rexp "" cleanrexp
	    regsub $cleanrexp $options "" options
	}
    }
    regsub "^," $options "" options
    regsub ",$" $options "" options

    set fsconfig(other) "$options"
}

proc nfs_info_or_edit {ignored i} {
    global fsname
    global fsmountpoint

    toplevel .e

    global advanced
    global fsconfig

    # Load defaults
    set fsconfig(write) 1
    set fsconfig(auto)  1
    set fsconfig(suid)  0
    set fsconfig(dev)   0
    set fsconfig(exec)  1
    set fsconfig(user)  0
    set fsconfig(intr)  0
    set fsconfig(bg)    1
    set fsconfig(retry) 0
    
    set fsconfig(rsize)   "8192"
    set fsconfig(wsize)   "8192"
    set fsconfig(retries) "10000"
    set fsconfig(timeo)   "7"
    set fsconfig(retr)    "3"
    set fsconfig(mport)   "2049"
    set fsconfig(other)   ""

    load-fs-config $i
    set x [split $fsname($i) :]
    set host  [lindex $x 0]
    set rpath [lindex $x 1]

    set r 1

    # System name
        label  .e.system-name-l -text "Remote system name: "
        entry  .e.system-name-e 
        .e.system-name-e insert 0 "$host"
        button .e.system-name-b -text "<- Lookup" -command {lookup-hostname .e}
	grid .e.system-name-l -row $r -column 1
        grid .e.system-name-e -row $r -column 2 -sticky we
        grid .e.system-name-b -row $r -column 3
	grid configure .e.system-name-l -padx 6 -pady 2
	bind .e <Return> { lookup-hostname .e }
	

    incr r
    # Export list
        label     .e.exported-file-systems-l -text "0 Available filesystems:"
	frame     .e.exported-file-systems-t
        listbox   .e.exported-file-systems-t.l -height 6 -width 40 -yscroll \
                 	{.e.exported-file-systems-t.s set}
        scrollbar .e.exported-file-systems-t.s -command {.e.exported-file-systems-t.l yview}
	pack      .e.exported-file-systems-t.l -side left -expand y -fill x
	pack      .e.exported-file-systems-t.s -side left -expand y -fill y
	
	grid configure .e.exported-file-systems-l -padx 6
	grid  .e.exported-file-systems-l -row $r -column 1
	grid  .e.exported-file-systems-t -row $r -column 2

	bind .e.exported-file-systems-t.l <Key-Up>        {set-remote-path .e}
	bind .e.exported-file-systems-t.l <Key-Down>      {set-remote-path .e}
	bind .e.exported-file-systems-t.l <ButtonPress>   {set-remote-path .e}
	bind .e.exported-file-systems-t.l <B1-Motion>     {set-remote-path .e}

    incr r
    # File system to mount
	label .e.remote-path-l -text "Mount this filesystem: "
	entry .e.remote-path-e -text ""
	.e.remote-path-e insert 0 "$rpath"
	grid .e.remote-path-l -row $r -column 1 
	grid .e.remote-path-e -row $r -column 2 -sticky we

    incr r
    # Access filesystem from
	label .e.mount-path-l -text "Access filesystem from:"
	entry .e.mount-path-e -text "" 
	.e.mount-path-e insert 0 "$fsmountpoint($i)"
	grid .e.mount-path-l -row $r -column 1
	grid .e.mount-path-e -row $r -column 2 -sticky we

    incr r
    # Advanced configuration
    frame .e.advanced
    if {$advanced} {put-advanced .e.advanced.frame}
    grid .e.advanced -row $r -column 1 -columnspan 4

    # Control widget
    frame .e.control
	button .e.control.ok       -text "Ok"     -width 10 -command {set trigger 1}
	button .e.control.cancel   -text "Cancel" -width 10 -command {set trigger 0}
	button .e.control.advanced -text "Show advanced parameters" -command {advanced-on-off .e}

	pack .e.control.advanced -side left -padx 4
	pack .e.control.cancel  .e.control.ok -side right -padx 5 -pady 2

    incr r
    grid .e.control  -row $r -column 1 -columnspan 4 -sticky we

    bind_entries_for_traversal { \
	    .e.system-name-e \
	    .e.exported-file-systems-t.l  \
	    .e.remote-path-e \
	    .e.mount-path-e \
	    .e.control.advanced .e.control.ok .e.control.cancel \
	}
    focus .e.system-name-e
}

#reload
#nfs_info_or_edit t 7
#wm withdraw .
#while {1} {
#tkwait variable x
#}

proc menu_fsm_add_mount {} {
    global fsname fspartitiontype fscount
    global fssize fsused fsavail fscapacity
    global fsmountpoint fsopts
    global fstabmountpoint fstabfstype fstabopts fstabcomment
    global fsmounted fstab
    global fstabdump fstabfsck

    set i $fscount
    incr fscount

    set fsname($i) ""
    set fspartitiontype($i) "na"
    set fstabfstype($i) ""
    set fsmountpoint($i) "none"
    set fstabmountpoint($i) "none"
    set fstabopts($i) "defaults"
    set fsopts($i) "na"
    set fstabcomment($i) ""
    set fssize($i) "na"
    set fsused($i) "na"
    set fsavail($i) "na"
    set fscapacity($i) "na"
    set fstabdump($i) "0"
    set fstabfsck($i) "0"

    set fsmounted($i) " "
    set fstab($i) " "

    if {[info_or_edit e $i] == 1} {
	sync
    } else {
	incr fscount -1
    }
}

proc menu_nfs_add_mount {} {
    global fsname fspartitiontype fscount
    global fssize fsused fsavail fscapacity
    global fsmountpoint fsopts
    global fstabmountpoint fstabfstype fstabopts fstabcomment
    global fsmounted fstab

    set i $fscount
    incr fscount

    set fsname($i) ""
    set fspartitiontype($i) "nfs"
    set fstabfstype($i) "nfs"
    set fsmountpoint($i) ""
    set fstabmountpoint($i) "none"
    set fstabopts($i) "soft,intr,rw"
    set fsopts($i) "na"
    set fstabcomment($i) ""
    set fssize($i) "na"
    set fsused($i) "na"
    set fsavail($i) "na"
    set fscapacity($i) "na"

    set fsmounted($i) " "
    set fstab($i) " "

    if {[nfs_info_or_edit e $i] == 1} {
	sync
    } else {
	incr fscount -1
    }
}

proc menu_nfs_delete_mount {} {
    global delete_index fstabfstype fsmounted fsname

    set i [.list curselection]
    if {$i == ""} {
	rhs_error_dialog "No NFS filesystem selected"
	return
    }
    if {$fstabfstype($i) != "nfs"} {
	rhs_error_dialog "That is not an NFS filesystem"
	return
    }
    if {$fsmounted($i) == "*"} {
	rhs_error_dialog "You can't delete a mounted filesystem"
	return
    }
    set res [rhs_continue_dialog "Delete NFS filesystem $fsname($i)\nfrom /etc/fstab?"]
    if {$res == 1} {
	return
    }

    set delete_index $i

    # sync writes /etc/fstab first, so this will do
    sync
}

proc menu_fsm_delete_mount {} {
    global delete_index fstabfstype fsmounted fsname

    set i [.list curselection]
    if {$i == ""} {
	rhs_error_dialog "No filesystem selected"
	return
    }
    if {$fstabfstype($i) == "nfs" ||
	[string match "/dev/hd*" $fsname($i)] || 
	[string match "/dev/sd*" $fsname($i)]} {
	rhs_error_dialog "You can't delete a NFS filesystem or hard drive partition"
	return
    }
    if {$fsmounted($i) == "*"} {
	rhs_error_dialog "You can't delete a mounted filesystem"
	return
    }
    set res [rhs_continue_dialog "Delete filesystem $fsname($i)\nfrom /etc/fstab?"]
    if {$res == 1} {
	return
    }

    set delete_index $i
    # sync writes /etc/fstab first, so this will do
    sync
}

proc menu_about {} {
    puts "About what?"
}

proc menu_quit {} {
    # We could sync here but we should always be synced
    exit 0
}

proc menu_reload {} {
    global selected_hint

    # Maintain selection if there is one
    set i [.list curselection]
    if {$i != ""} {
	set selected_hint [lindex [.list get [.list curselection]] 0]
    }

    .list delete 0 end
    update idletasks

    # We don't need to (and should not) write out fstab
    # since we should always be synced, and the goal
    # is really to pick up any external changes

    reload
    redisplay
}

## End of user interface callback functions
#############################################
## @@ main program

set options(fstabfstype) 1
set options(fspartitiontype) 0
set options(fssize) 1
set options(fsused) 0
set options(fsavail) 1
set options(fscapacity) 0
set options(fstabopts) 0
set options(fsopts) 0
set options(fstabcomment) 1

if {[exec id -u] != 0} {
    rhs_error_dialog "You must be root to run the File System Tool."
    exit 1
}

# We can't call sync right away or /etc/fstab will get clobbered

if {[file exists /etc/fstab.bak] == 1} {
    if {[rhs_yesno_dialog "/etc/fstab.bak exists.\n\nOverwrite it?" 1] == 0} {
	exec /bin/cp -f /etc/fstab /etc/fstab.bak
    }
} else {
    exec /bin/cp -f /etc/fstab /etc/fstab.bak
}

reload
# write_fstab
gen_format
redisplay
