#!/usr/bin/env perl
#-*- Mode: perl; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-

# Working with filesystems, both local and networked.
#
# Copyright (C) 2000-2001 Ximian, Inc.
#
# Authors: Hans Petter Jansson <hpj@ximian.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Library General Public License as published
# by the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# 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 Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.


$SCRIPTSDIR = "/usr/share/setup-tool-backends/scripts";
if ($SCRIPTSDIR =~ /^@scriptsdir[@]/)
{
    $SCRIPTSDIR = ".";
    $DOTIN = ".in";
}

require "$SCRIPTSDIR/file.pl$DOTIN";
require "$SCRIPTSDIR/parse.pl$DOTIN";
require "$SCRIPTSDIR/xml.pl$DOTIN";


# --- Utilities --- #

# Merge items in hash B missing in A into A.
sub merge_hashes
{
  my ($ha, $hb) = @_;
  my $key;

  foreach $key (keys %$hb)
  {
    $$ha{$key} = $$hb{$key} if !exists $$ha{$key};
  }
}

sub gst_filesys_ext2_device_to_label  # device
{
  my ($device) = @_;
  my $label;
  my $fd;

  $fd = &gst_file_run_pipe_read ("e2label $device");
  return undef if $fd eq undef;
  $label = <$fd>;
  chomp $label;
  &gst_file_close ($fd);

  return $label;
}


# --- filesys_info; information on a particular filesystem --- #


sub gst_filesys_info_new
{
  my $info = {};
  my $opthash = {};

  $$info{'options'} = $opthash;
  return ($info);
}

# Make a deep copy of a filesys_info struct.
#
# Returns a newly allocated filesys_info, identical to the argument.

sub gst_filesys_info_dup           # filesys_info
{
  my $orig = $_[0];
  my $dup;
  my $options = $$orig{'options'};

  $dup = { %$orig };
  $$dup{'options'} = { %$options };

  return $dup;
}

sub gst_filesys_info_match  # filesys_info, device, label, uuid, network_host, network_path
{
  my ($info, $device, $label, $uuid, $network_host, $network_path, $point) = @_;

  if (($label && $info->{'label'} eq $label) ||
      ($uuid  && $info->{'uuid'}  eq $uuid)  ||
      ($network_host && $network_path &&
       $info->{'network_host'} eq $network_host &&
       $info->{'network_path'} eq $network_path) ||
      ($device && $device ne "none" && $info->{'device'} eq $device) ||
      ($device eq "none" && $info->{'device'} eq "none" && $point eq $info->{'point'}))
  {
    return 1;
  }

  return 0;
}

sub gst_filesys_info_settings_to_options
{
  my ($info) = @_;

  if (&gst_filesys_info_get_mounted ($info))
  {
    &gst_filesys_info_remove_option ($info, "noauto");
  }
  else
  {
    &gst_filesys_info_set_option ($info, "noauto", "");
  }
}

sub gst_filesys_info_match_options  # filesys_info, filesys_info
{
  my ($info_a, $info_b) = @_;

  if (&gst_filesys_info_print_options ($info_a) eq &gst_filesys_info_print_options ($info_b))
  {
    return 1;
  }

  return 0;
}

sub gst_filesys_info_match_data  # filesys_info, filesys_info
{
  my ($info_a, $info_b) = @_;

  if (&gst_filesys_info_get_point    ($info_a) eq &gst_filesys_info_get_point    ($info_b) &&
      &gst_filesys_info_get_fs       ($info_a) eq &gst_filesys_info_get_fs       ($info_b) &&
      &gst_filesys_info_get_dump     ($info_a) eq &gst_filesys_info_get_dump     ($info_b) &&
      &gst_filesys_info_get_priority ($info_a) eq &gst_filesys_info_get_priority ($info_b) &&
      &gst_filesys_info_match_options ($info_a, $info_b))
  {
    return 1;
  }

  return 0;
}

# Merge options in B missing in A into A.
sub gst_filesys_info_merge_options
{
  my ($info_a, $info_b) = @_;
  my ($opt_a, $opt_b, $key);

  $opt_a = $$info_a{'options'};
  $opt_b = $$info_b{'options'};

  &merge_hashes ($opt_a, $opt_b);
}

# Merge stuff in B missing in A into A.
sub gst_filesys_info_merge
{
  my ($info_a, $info_b) = @_;
  my $key;

  &merge_hashes ($info_a, $info_b);
  &gst_filesys_info_merge_options ($info_a, $info_b);
}

# Generic set function for filesys_info properties. We need this to
# delete keys if they don't have meaningful values, otherwise an empty
# value could override a perfectly good value in a merge.

sub gst_filesys_info_set           # filesys_info, key, value
{
  my ($filesys_info, $key, $value) = @_;
  
  if ($value eq "")
  {
    delete $filesys_info->{$key};
  }
  else
  {
    $filesys_info->{$key} = $value;
  }
}

sub gst_filesys_info_get_device        # filesys_info
{
  return $_[0]->{'device'};
}

sub gst_filesys_info_set_device        # filesys_info, device
{
  &gst_filesys_info_set ($_[0], 'device', $_[1]);
}

sub gst_filesys_info_get_label         # filesys_info
{
  return $_[0]->{'label'};
}

sub gst_filesys_info_set_label         # filesys_info, label
{
  &gst_filesys_info_set ($_[0], 'label', $_[1]);
}

sub gst_filesys_info_get_network_host  # filesys_info
{
  return $_[0]->{'network_host'};
}

sub gst_filesys_info_set_network_host  # filesys_info, network_host
{
  my ($info, $host) = @_;

  $host =~ s/^\/+//;
  &gst_filesys_info_set ($info, 'network_host', $host);
}

sub gst_filesys_info_get_network_path  # filesys_info
{
  return $_[0]->{'network_path'};
}

sub gst_filesys_info_set_network_path  # filesys_info, network_path
{
  my ($info, $path) = @_;

  $path = "/" . $path if (!($path =~ /^\//));
  &gst_filesys_info_set ($info, 'network_path', $path);
}

sub gst_filesys_info_get_uuid          # filesys_info
{
  return $_[0]->{'uuid'};
}

sub gst_filesys_info_set_uuid          # filesys_info, uuid
{
  &gst_filesys_info_set ($_[0], 'uuid', $_[1]);
}

sub gst_filesys_info_get_point         # filesys_info
{
  return $_[0]->{'point'};
}

sub gst_filesys_info_set_point         # filesys_info, point
{
  &gst_filesys_info_set ($_[0], 'point', $_[1]);
}

sub gst_filesys_info_get_fs            # filesys_info
{
  return $_[0]->{'fs'};
}

sub gst_filesys_info_set_fs            # filesys_info, fs
{
  &gst_filesys_info_set ($_[0], 'fs', $_[1]);
}

sub gst_filesys_info_get_dump          # filesys_info
{
  return $_[0]->{'dump'} || "0";
}

sub gst_filesys_info_set_dump          # filesys_info, dump
{
  &gst_filesys_info_set ($_[0], 'dump', $_[1]);
}

sub gst_filesys_info_get_priority      # filesys_info
{
  return $_[0]->{'priority'} || "0";
}

sub gst_filesys_info_set_priority      # filesys_info, priority
{
  &gst_filesys_info_set ($_[0], 'priority', $_[1]);
}

sub gst_filesys_info_get_mounted       # filesys_info
{
  return $_[0]->{'mounted'};
}

sub gst_filesys_info_set_mounted       # filesys_info, boolean
{
  &gst_filesys_info_set ($_[0], 'mounted', $_[1]);
}

sub gst_filesys_info_get_permanent     # filesys_info
{
  return $_[0]->{'permanent'};
}

sub gst_filesys_info_set_permanent     # filesys_info, boolean
{
  &gst_filesys_info_set ($_[0], 'permanent', $_[1]);
}

sub gst_filesys_info_get_detected      # filesys_info
{
  return $_[0]->{'detected'};
}

sub gst_filesys_info_set_detected      # filesys_info, boolean
{
  &gst_filesys_info_set ($_[0], 'detected', $_[1]);
}

sub gst_filesys_info_get_option      # filesys_info, option
{
  return $_[0]->{'options'}{$_[1]};
}

# We can't delete keys with no values here, since most fs options don't
# have values (i.e. they key's presence constitutes a boolean). A value of
# " " (one space) indicates that this entry takes a value (is non-bool), but
# has none.

sub gst_filesys_info_set_option        # filesys_info, option, value
{
  $_[0]->{'options'}{$_[1]} = $_[2];
}

sub gst_filesys_info_remove_option     # filesys_info, option
{
  delete $_[0]->{'options'}{$_[1]};
}

# --- filesys_table; multiple instances of filesys_info --- #


sub gst_filesys_table_new
{
  my @array;
  return \@array;
}

# Make a deep copy of a filesys_table struct.
#
# Returns a newly allocated filesys_table, identical to the argument.

sub gst_filesys_table_dup          # filesys_table
{
  my $orig = $_[0];
  my $dup = &gst_filesys_table_new ();
  my $i;

  foreach $i (@$orig)
  {
    &gst_filesys_table_add ($dup, &gst_filesys_info_dup ($i));
  }

  return $dup;
}

# Add a filesys_info reference to a filesys_table. Note: This function
# does not check for uniqueness, which lets you add several references
# to the same filesys_info.

sub gst_filesys_table_add          # filesys_table, filesys_info
{
  my ($table, $info) = @_;

  push @$table, $info;
}

# Ensure that a filesys_info reference exists in a filesys_table. If it
# doesn't, it will be added. If it does, no action will be taken.

sub gst_filesys_table_ensure       # filesys_table, filesys_info
{
  my ($table, $info) = @_;
  my $i;

  foreach $i (@$table)
  {
    return if ($i eq $info);
  }

  &gst_filesys_table_add ($table, $info);
}

# Remove a filesys_info reference from a filesys_table.

sub gst_filesys_table_remove       # filesys_table, filesys_info
{
  my ($table, $info) = @_;
  my $i;

  if ($info == undef) { return; }

  for ($i = 0; $i < @$table; $i++)
  {
    if (@$table [$i] eq $info)
    {
      @$table = (@$table [0 .. $i - 1], @$table [$i + 1 .. @$table - 1]);
      return;
    }
  }

  &gst_debug_print_line ("Entry to remove [" . $info . "] not found in filesys_table.");
}

# Find and return a reference to a filesys_info in a filesys_table
# matching any of the information provided.

sub gst_filesys_table_find         # filesys_table, device, label, uuid, network_host, network_path
{
  my ($table, $device, $label, $uuid, $network_host, $network_path, $point) = @_;
  my $i;

  # Match on high-quality keys.
  
  foreach $i (@$table)
  {
    if (($label && $i->{'label'} eq $label) ||
        ($uuid  && $i->{'uuid'}  eq $uuid)  ||
        ($network_host && $network_path &&
         $i->{'network_host'} eq $network_host &&
         $i->{'network_path'} eq $network_path))
    {
      return $i;
    }
  }

  # Match on low-quality keys.

  if ($device)
  {
    foreach $i (@$table)
    {
      if (($device && $device ne "none" && $i->{'device'} eq $device) ||
          ($device eq "none" && $i->{'device'} eq "none" && $point eq $i->{'point'}))
      {
        return $i;
      }
    }
  }

  &gst_debug_print_line ("Entry [" . $device . "] not found in filesys_table.");
  return undef;
}

sub gst_filesys_table_find_info_equivalent  # filesys_table, filesys_info
{
  my ($table, $info) = @_;

  return &gst_filesys_table_find ($table, &gst_filesys_info_get_device ($info),
                                  &gst_filesys_info_get_label ($info),
                                  &gst_filesys_info_get_uuid ($info),
                                  &gst_filesys_info_get_network_host ($info),
                                  &gst_filesys_info_get_network_path ($info),
                                  &gst_filesys_info_get_point ($info));
}

# Merges filesys tables A and B, resolving conflicts by giving priority to A.
# Any entries in A not in B are preserved. This can also be described as
# "salting" one table with another.
#
# Returns a newly allocated table C, which is a superset of A and B.

sub gst_filesys_table_merge_superset  # filesys_table A, filesys_table B
{
  my ($intab_a, $intab_b) = @_;
  my ($hash_c, $hash_b, $key);
  my $outtab;

  $outtab = &gst_filesys_table_dup ($intab_a);

  foreach $info_b (@$intab_b)
  {
    my $info_c;

    if ($info_c = &gst_filesys_table_find_info_equivalent ($outtab, $info_b))
    {
      &gst_filesys_info_merge ($info_c, $info_b);
    }
    else
    {
      $info_c = &gst_filesys_info_dup ($info_b);
      &gst_filesys_table_add ($outtab, $info_c);
    }
  }

  return $outtab;
}

# Merges filesys tables A and B, resolving conflicts by giving priority to A.
# Any entries not in A are dropped.
#
# Returns a newly allocated table C, which is a subset of A and B.

sub gst_filesys_table_merge_subset    # filesys_table A, filesys_table B
{
  my ($intab_a, $intab_b) = @_;
  my ($hash_c, $hash_b, $key);
  my $outtab;

  $outtab = &gst_filesys_table_dup ($intab_a);

  foreach $info_b (@$intab_b)
  {
    my $info_c;

    if ($info_c = &gst_filesys_table_find_info_equivalent ($outtab, $info_b))
    {
      &gst_filesys_info_merge ($info_c, $info_b);
    }
  }

  return $outtab;
}

# Called to indicate that entries in a filesys table are mounted.

sub gst_filesys_table_set_mounted_true  # filesys_table
{
  my ($table) = @_;
  foreach $i (@$table)
  {
    &gst_filesys_info_set_mounted ($i, 1);
  }
}

# Called to indicate that entries in a filesys table are permanent.

sub gst_filesys_table_set_permanent_true  # filesys_table
{
  my ($table) = @_;
  foreach $i (@$table)
  {
    &gst_filesys_info_set_permanent ($i, 1);
  }
}

# Called to indicate that entries in a filesys table have been detected,
# e.g. by a network or bus scanner, and were not specified in any part of
# the user's configuration.

sub gst_filesys_table_set_detected_true  # filesys_table
{
  my ($table) = @_;
  foreach $i (@$table)
  {
    &gst_filesys_info_set_detected ($i, 1);
  }
}

# --- Parsing --- #

sub gst_filesys_entry_identify
{
  my ($device, $fs) = @_;
  my ($label, $uuid, $network_host, $network_path);

  # <device> expands to "LABEL=<label>", "UUID=<uuid>" or "<device node>".
  if    ($device =~ /^LABEL=(.*)/i) { $label = $1; $device = ""; }
  elsif ($device =~ /^UUID=(.*)/i)  { $uuid  = $1; $device = ""; }
  else
  {
    # We know only the device node. Try to get label too.
    if ($fs eq "ext2" || ($fs eq "auto" && !($device =~ /fd[0-9]$/)))
    {
      $label = &gst_filesys_ext2_device_to_label ($device);
    }
    # Network filesystem devices can be separated into remote host and remote path.
    elsif ($fs eq "nfs")
    {
      $device =~ /([^:]+):(.+)/;
      $network_host = $1;
      $network_path = $2;
      $network_path = "/" . $network_path if (!($network_path =~ /^\//));
      $device = "";
    }
    elsif ($fs eq "smbfs")
    {
      $device =~ /[\\\/]*([^\\\/]+)[\\\/]+(.+)/;
      $network_host = $1;
      $network_path = $2;
      $network_path = "/" . $network_path if (!($network_path =~ /^\//));
      $device = "";
    }
  }

  return ($device, $label, $uuid, $network_host, $network_path);
}

sub gst_filesys_entry_identify_info
{
  my ($fsi, $device, $fs) = @_;
  my ($label, $uuid, $network_host, $network_path);

  ($device, $label, $uuid, $network_host, $network_path) = &gst_filesys_entry_identify ($device, $fs);

  if ($device)       { &gst_filesys_info_set_device       ($fsi, $device);       }
  if ($label)        { &gst_filesys_info_set_label        ($fsi, $label);        }
  if ($uuid)         { &gst_filesys_info_set_uuid         ($fsi, $uuid);         }
  if ($network_host) { &gst_filesys_info_set_network_host ($fsi, $network_host); }
  if ($network_path) { &gst_filesys_info_set_network_path ($fsi, $network_path); }
}

# Get all instances from fstab-style file. Returns a filesys_table.
#
# This is not done in smaller, atomic funcs that get single options
# per disk device, due to the fact that a device is identified either
# by its label, uuid or device node, and a label can be made to look
# like a device node. For each device, we need to specify the kind of
# key(s) used, and making a special-format string for that (which could
# be passed to option readers) would be a bad hack.

sub gst_filesys_fstab_parse  # filename
{
  my ($file) = @_;
  my ($fd, $table);

  $fd = &gst_file_open_read_from_names ($file);
  return undef if !$fd;

  $table = &gst_filesys_table_new ();

  while (($_ = &gst_parse_chomp_line_hash_comment ($fd)) != -1)
  {
    # Each line is in the following format:
    # <device> <mount point> <filesystem> <options> <dump flag> <fsck priority>
    my @line = split /[ \t]+/, $$_;
    next if (@line < 6);
    my ($device, $point, $fs, $options, $dump, $fsck) = @line;

    my $fsi = &gst_filesys_info_new ();
    &gst_filesys_entry_identify_info ($fsi, $device, $fs);

    # <mount point>, <fs>, <dump flag> and <fsck priority> are verbatim.
    &gst_filesys_info_set_point    ($fsi, $point);
    &gst_filesys_info_set_fs       ($fsi, $fs);
    &gst_filesys_info_set_dump     ($fsi, $dump);
    &gst_filesys_info_set_priority ($fsi, $fsck);

    # <options> expands to "<option>[,<option>[,...]]".
    my @optlist = split /[,]/, $options;

    foreach $option (@optlist)
    {
      # <option> expands to "<key>[=<value>]". <key> == "defaults" is ignored.
      my ($key, $value) = split /[=]/, $option;
      next if ($key eq "" || $key eq "defaults");

      if ($value eq "" && $option =~ /=/) { $value = " "; }
      &gst_filesys_info_set_option ($fsi, $key, $value);
    }

    # Link it in.
    &gst_filesys_table_add ($table, $fsi);
  }

  &gst_file_close ($fd);
  return $table;
}

# Get all instances from 'mount -p' output. Returns a filesys_table.
sub gst_filesys_freebsd_mount_cmd_parse
{
  my ($table, $mount_cmd);
  my (@output, $l);

  $table = &gst_filesys_table_new ();
  $mount_cmd = &gst_file_locate_tool ("mount");
  @output = (readpipe ("$mount_cmd -p"));

  foreach $l (@output)
  {
    # Columns are separated by any number of spaces/tabs.
    chomp $l;
    my @line = split (/[ \t]+/, $l);

    # Each line is in the following format:
    # <device> <mount point> <filesystem> <options> <dump flag> <fsck priority>
    next if ($#line < 5);
    my ($device, $point, $fs, $options, $dump, $fsck) = @line;

    my $fsi = &gst_filesys_info_new ();
    &gst_filesys_entry_identify_info ($fsi, $device, $fs);

    # <device> expands to "<device node>" (unlike fstab, which has dev|label|uuid).
    # <mount point>, <fs>, <dump flag> and <fsck priority> are verbatim.
    &gst_filesys_info_set_device   ($fsi, $device);
    &gst_filesys_info_set_point    ($fsi, $point);
    &gst_filesys_info_set_fs       ($fsi, $fs);
    &gst_filesys_info_set_dump     ($fsi, $dump);
    &gst_filesys_info_set_priority ($fsi, $fsck);

    # <options> expands to "<option>[,<option>[,...]]".
    my @optlist = split (/,/, $options);

    foreach $option (@optlist)
    {
      # <option> expands to "<key>[=<value>]". <key> == "defaults" is ignored.
      my ($key, $value) = split /[=]/, $option;
      next if ($key eq "" || $key eq "defaults");

      $value = " " if ($value eq "" && $option =~ /=/);
      &gst_filesys_info_set_option ($fsi, $key, $value);
    }

    # Link it in.
    &gst_filesys_table_add ($table, $fsi);
  }

  return $table;
}

# Get all instances from mtab-style file. Returns a filesys_table.

sub gst_filesys_mtab_parse  # filename
{
  my ($file) = @_;
  my ($fd, $table);

  $fd = &gst_file_open_read_from_names ($file);
  return undef if !$fd;

  $table = &gst_filesys_table_new ();

  while (($_ = &gst_parse_chomp_line_hash_comment ($fd)) != -1)
  {
    # Columns are separated by one, and only one, space. The presence of one or
    # more blank values is indicated by a string of several spaces.
    my @line = split /[ ]/, $$_;

    # Each line is in the following format:
    # <device> <mount point> <filesystem> <options> <dump flag> <fsck priority>
    next if (@line < 6);
    my ($device, $point, $fs, $options, $dump, $fsck) = @line;

    my $fsi = &gst_filesys_info_new ();
    &gst_filesys_entry_identify_info ($fsi, $device, $fs);

    # <device> expands to "<device node>" (unlike fstab, which has dev|label|uuid).
    # <mount point>, <fs>, <dump flag> and <fsck priority> are verbatim.
    &gst_filesys_info_set_device   ($fsi, $device);
    &gst_filesys_info_set_point    ($fsi, $point);
    &gst_filesys_info_set_fs       ($fsi, $fs);
    &gst_filesys_info_set_dump     ($fsi, $dump);
    &gst_filesys_info_set_priority ($fsi, $fsck);

    # <options> expands to "<option>[,<option>[,...]]".
    my @optlist = split /[,]/, $options;

    foreach $option (@optlist)
    {
      # <option> expands to "<key>[=<value>]". <key> == "defaults" is ignored.
      my ($key, $value) = split /[=]/, $option;
      next if ($key eq "" || $key eq "defaults");

      if ($value eq "" && $option =~ /=/) { $value = " "; }
      &gst_filesys_info_set_option ($fsi, $key, $value);
    }

    # Link it in.
    &gst_filesys_table_add ($table, $fsi);
  }

  &gst_file_close ($fd);
  return $table;
}


# --- Replacing --- #


sub gst_filesys_fstab_get_next_entry_line  # $infd, $outfd
{
  my ($infd, $outfd) = @_;

  while (<$infd>)
  {
    # Each line is in the following format:
    # <device> <mount point> <filesystem> <options> <dump flag> <fsck priority>
    my @line = split /[ \t]+/, $_;
    if ($line[0] eq "") { shift @line; }
    if (@line < 6 || &gst_ignore_line (@line)) { print $outfd $_; next; }

    return $_;
  }

  return undef;
}

sub gst_filesys_fstab_get_entry_line_fields  # line
{
  my ($line) = @_;

  # Remove leading spaces.
  $line =~ s/^[ \t]*//;

  # Remove trailing spaces and comments.
  $line =~ s/[ \t]*\#.*//;

  return split /[ \t]+/, $line;
}

sub gst_filesys_fstab_get_entry_line_comments  # line
{
}

sub gst_filesys_info_print_options       # filesys_info
{
  my ($info) = @_;
  my $optstring = "";
  my $opthash;

  $opthash = $$info{'options'};

  for $option (keys (%$opthash))
  {
    if ($optstring) { $optstring .= ","; }
    $optstring .= $option;
    if ($info->{'options'}{$option})
    {
      $optstring .= "=" . $info->{'options'}{$option};
      $optstring =~ s/ //;
    }
  }

  if ($optstring eq "")
  {
    $optstring = "defaults";
  }

  return $optstring;
}

sub gst_filesys_info_print_device
{
  my ($info) = @_;
  my $device;

  if (&gst_filesys_info_get_label ($info))
  {
    $device = "LABEL=" . &gst_filesys_info_get_label ($info);
  }
  elsif (&gst_filesys_info_get_uuid ($info))
  {
    $device = "UUID=" . &gst_filesys_info_get_uuid ($info);
  }
  elsif (&gst_filesys_info_get_network_host ($info) &&
         &gst_filesys_info_get_network_path ($info))
  {
    if (&gst_filesys_info_get_fs ($info) eq "smbfs")
    {
      $device = "//" . &gst_filesys_info_get_network_host ($info)
                     . &gst_filesys_info_get_network_path ($info);
    }
    else
    {
      $device = &gst_filesys_info_get_network_host ($info) . ":" .
              &gst_filesys_info_get_network_path ($info);
    }
  }
  else
  {
    $device = &gst_filesys_info_get_device ($info);
  }

  return $device;
}

sub gst_filesys_info_print_entry
{
  my ($info) = @_;
  my $line;

  # <device>

  $line = sprintf ("%-23s", &gst_filesys_info_print_device ($info));

  # <mount point>

  if (&gst_filesys_info_get_point ($info))
  {
    $line .= sprintf ("%-24s", (" " . &gst_filesys_info_get_point ($info))) . " ";
  }
  else
  {
    $line .= sprintf ("%-24s", (" none")) . " ";
  }

  # <filesystem> <options> <dump flag> <fsck priority>

  $line .= sprintf ("%-7s", &gst_filesys_info_get_fs ($info)) . " " .
           sprintf ("%-15s", &gst_filesys_info_print_options ($info)) . " " .
           &gst_filesys_info_get_dump ($info) . " " .
           &gst_filesys_info_get_priority ($info);

  return $line;
}

sub gst_filesys_fstab_add_entry     # filename, filesys_info
{
  my ($file, $info) = @_;
  my ($infd, $outfd);
  my ($line);

  ($infd, $outfd) = &gst_file_open_filter_write_from_names ($file);
  return undef if !$outfd;

  while (<$infd>) { print $outfd $_; }
  &gst_file_close ($infd);

  print $outfd &gst_filesys_info_print_entry ($info) . "\n";
  &gst_file_close ($outfd);
}

sub gst_filesys_fstab_update_entry  # filename, filesys_info
{
  my ($file, $info) = @_;
  my ($infd, $outfd);
  my ($line);
  my $replaced = 0;

  ($infd, $outfd) = &gst_file_open_filter_write_from_names ($file);
  return undef if !$outfd;

  while ($line = &gst_filesys_fstab_get_next_entry_line ($infd, $outfd))
  {
    my ($device, $point, $fs, $options, $dump, $fsck) = &gst_filesys_fstab_get_entry_line_fields ($line);
    my ($device, $label, $uuid, $network_host, $network_path) = &gst_filesys_entry_identify ($device, $fs);

    if (!$replaced && &gst_filesys_info_match ($info, $device, $label, $uuid,
                                               $network_host, $network_path, $point))
    {
      print $outfd &gst_filesys_info_print_entry ($info) . "\n";
      $replaced = 1;
    }
    else
    {
      print $outfd $line;
    }
  }

  &gst_file_close ($infd);
  &gst_file_close ($outfd);
}

sub gst_filesys_fstab_remove_entry  # filename, filesys_info
{
  my ($file, $info) = @_;
  my ($infd, $outfd);
  my ($line);

  ($infd, $outfd) = &gst_file_open_filter_write_from_names ($file);
  return undef if !$outfd;

  while ($line = &gst_filesys_fstab_get_next_entry_line ($infd, $outfd))
  {
    my ($device, $point, $fs, $options, $dump, $fsck) = &gst_filesys_fstab_get_entry_line_fields ($line);
    my ($device, $label, $uuid, $network_host, $network_path) = &gst_filesys_entry_identify ($device, $fs);

    if (!&gst_filesys_info_match ($info, $device, $label, $uuid, $network_host, $network_path, $point))
    {
      print $outfd $line;
    }
  }

  &gst_file_close ($infd);
  &gst_file_close ($outfd);
}

# Replace instances in fstab-style file.

sub gst_filesys_fstab_replace  # filename, table
{
  my ($file, $table) = @_;
  my ($new_table, $old_table);

  $old_table = &gst_filesys_fstab_parse ($file);
  $new_table = &gst_filesys_table_dup ($table);

  for $info (@$new_table)
  {
    my $old_info = &gst_filesys_table_find_info_equivalent ($old_table, $info);

#    print "Looking for entry - ";
    if (&gst_filesys_info_get_permanent ($info) && !$old_info)
    {
#      print "adding.\n";
      &gst_filesys_fstab_add_entry ($file, $info);
    }
    elsif (!&gst_filesys_info_get_permanent ($info) && $old_info)
    {
#      print "removing.\n";
      &gst_filesys_fstab_remove_entry ($file, $info);
    }
    elsif ($old_info && !&gst_filesys_info_match_data ($old_info, $info))
    {
#      print "updating.\n";
      &gst_filesys_fstab_update_entry ($file, $info);
    }
    else
    {
#      print "found.\n";
    }
  }

#  print "---\n";

  for $old_info (@$old_table)
  {
#    print "Looking for entry - ";
    if (!&gst_filesys_table_find_info_equivalent ($new_table, $old_info))
    {
#      print "removing.\n";
      &gst_filesys_fstab_remove_entry ($file, $old_info);
    }
    else
    {
#      print "found.\n";
    }
  }
}


# --- Mounting --- #


sub gst_filesys_mount_on
{
  my ($info) = @_;
  my ($dev, $point, $fs);

  $dev   = &gst_filesys_info_print_device ($info);
  $point = &gst_filesys_info_get_point ($info);

  &gst_report_enter ();
  &gst_report ("filesys_mount", $dev, $point);

  $fs = &gst_filesys_info_get_fs ($info);
  $fs = "auto" if ($fs eq "");

  if (&gst_file_run ("mount -t " . $fs . " -o " . &gst_filesys_info_print_options ($info) . " " .
                     $dev . " " . $point))
  {
    &gst_report ("filesys_mount_failed", $dev, $point);
  }

  &gst_report_leave ();
}

sub gst_filesys_mount_off
{
  my ($info) = @_;
  my ($dev, $point);

  $dev   = &gst_filesys_info_print_device ($info);
  $point = &gst_filesys_info_get_point ($info);

  &gst_report_enter ();
  &gst_report ("filesys_unmount", $dev, $point);

  if (&gst_file_run ("umount -f " . $point))
  {
    &gst_report ("filesys_unmount_failed", $dev, $point);
  }

  &gst_report_leave ();
}

sub gst_filesys_mount_sync_all
{
  my ($fstab_file, $mtab_file, $new_table) = @_;
  my ($mount_table, $fs_table);

  $fs_table    = &gst_filesys_fstab_parse ($fstab_file);
  $mount_table = &gst_filesys_mtab_parse  ($mtab_file);

  for $info (@$new_table)
  {
    my $mounted_info = &gst_filesys_table_find_info_equivalent ($mount_table, $info);

    if ($mounted_info && !&gst_filesys_info_get_mounted ($info))
    {
      &gst_filesys_mount_off ($mounted_info);
    }
    elsif (!$mounted_info && &gst_filesys_info_get_mounted ($info))
    {
      # If the mount has an fstab entry, we prefer that over the information
      # provided by XML.

      my $fs_info = &gst_filesys_table_find_info_equivalent ($fs_table, $info);  

      if ($fs_info)
      {
        &gst_filesys_mount_on ($fs_info);
      }
      else
      {
        &gst_filesys_mount_on ($info);
      }
    }
  }
}


1;
