#!/usr/bin/perl
##
## Samba: Script for 'add/change/delete share command' in the smb.conf
## Copyright (c) 2007-2008 SATOH Fumiyas @ OSS Technology Co., Japan
##                    <http://www.osstech.co.jp/>
##
## Date: 2008-02-22, since 2007-02-21
## License: GNU General Public License version 2
##

use strict;
use warnings;
use English;
use IO::File;

umask(0022);
$ENV{'LC_ALL'} = 'C';
$ENV{'LANG'} = 'C';

## ======================================================================

sub pwarn {
  chomp(my $msg = $_[0]);
  print STDERR "$0: WARNING: $msg\n";
}

sub perr {
  chomp(my $msg = $_[0]);
  print STDERR "$0: ERROR: $msg\n";
}

sub pdie {
  perr($_[0]);
  exit(defined($_[1]) ? $_[1] : 1);
}

sub abort_eval {
  chomp(my $msg = $_[0]);
  die "$msg\n";
}

## Run a command without /bin/sh and die on error
sub run {
  my $cmd = $_[0];
  if (my $status = system {$cmd} @_) {
    if ($status == -1) {
      abort_eval "exec or wait failed: $cmd: $!";
    }
    else {
      my $status = $status >> 8;
      abort_eval "command failed ($status): @_"
    }
  }
}

## Run a command and ignore error
sub runi {
  eval {run(@_)};
}

## Options
## ======================================================================

my $cmd_usage = <<EOT_USAGE;
Usage: $0 {add|modify} SMB.CONF NAME PATH COMMENT MAXCONNECTION
Usage: $0 delete SMB.CONF NAME
EOT_USAGE

my $op_name = shift(@ARGV);
unless (defined($op_name)) {
  print $cmd_usage;
  exit(0);
}
unless ($op_name =~ /(add|modify|delete)$/) {
  pdie "invalid operation name: $op_name";
}
if ($op_name eq 'modify') {
  $op_name = 'add';
}

if ($op_name eq 'delete') {
  if (@ARGV != 2) {
    print $cmd_usage;
    exit(0);
  }
}
else {
  if (@ARGV != 5) {
    print $cmd_usage;
    exit(0);
  }
}

my (
  $smbconf_file,
  $share_name,
  $share_path,
  $share_comment,
  $share_maxconn,
) = @ARGV;

$share_name =~ s/^\s+//;
$share_name =~ s/\s+$//;
if ($share_name =~ /^(global|homes|printers)$/i) {
  pdie "cannot treat the special share name: $share_name";
}
if ($share_name =~ /[\/\\\[\]]/) {
  pdie "invalid character in the share name: $share_name";
}

my $smbconf_old = "$smbconf_file.old";
my $smbconf_old_tmp = "$smbconf_file.old.$$.tmp";
my $smbconf_new_tmp = "$smbconf_file.new.$$.tmp";

eval {
  $SIG{'INT'} = $SIG{'TERM'} = $SIG{'PIPE'} = sub {abort_eval "interrupted by signal"};

  ## Backup
  run('cp', '-p', $smbconf_file, $smbconf_old_tmp);
  ## Save original owner, group and permissions
  run('cp', '-p', $smbconf_file, $smbconf_new_tmp);

  my $old_fh = IO::File->new($smbconf_old_tmp, 'r')
    || abort_eval "cannot open file: $smbconf_old_tmp: $!";
  my $new_fh = IO::File->new($smbconf_new_tmp, 'w')
    || abort_eval "cannot open file: $smbconf_new_tmp: $!";

  ## Passthrough while reaching to '[$share_name]' or EOF
  my $line_before = '';
  while (defined(my $line = $old_fh->getline)) {
    if ($line =~ /^\s*\[\s*\Q$share_name\E\s*\].*$/io) {
      last;
    }
    $new_fh->print($line);
    $line_before = $line;
  }

  ## Create the [$share_name] if $op_name is 'add' (or 'modify')
  if ($op_name eq 'add') {
    $new_fh->print("\n") unless ($line_before =~ /^\s*$/);
    $new_fh->print("[$share_name]\n");
    $new_fh->print("\tavailable = yes\n");
    $new_fh->print("\tpath = $share_path\n");
    $new_fh->print("\tcomment = $share_comment\n");
    $new_fh->print("\tmax connections = $share_maxconn\n");
    $new_fh->print("\twriteable = yes\n");
  }

  ## Process the remains of part in the [$share_name]
  while (defined(my $line = $old_fh->getline)) {
    ## Next share?
    if ($line =~ /^\s*\[/) {
      $new_fh->print($line);
      last;
    }
    ## Ignore parameters if $op_name is 'delete'
    if ($op_name eq 'delete') {
      next;
    }
    ## Ignore old overridden parameters (if any)
    if ($line =~ /^([^=]+)=/) {
      my $pname = lc($1); ## Case insensitive
      $pname =~ s/\s+//g; ## Space insensitive
      if ($pname =~ /^(available|path|comment|maxconnections|write?able|readonly)$/i) {
	next;
      }
    }

    ## Passthrough
    $new_fh->print($line);
  }

  ## Passthrough the remains
  $new_fh->print($old_fh->getlines);

  if (defined($ENV{'SMBSHAREADM_TEST'})) {
    ## Test mode
    run('cat', $smbconf_new_tmp);
    runi('diff', '-u', $smbconf_file, $smbconf_new_tmp);
  }
  else {
    ## Update config
    run('mv', $smbconf_new_tmp, $smbconf_file);
    ## Old config
    rename($smbconf_old_tmp, $smbconf_old);
  }
};

## Cleanup before exiting
unlink($smbconf_old_tmp, $smbconf_new_tmp);

if ($EVAL_ERROR) {
  pdie "$EVAL_ERROR";
}

exit(0);

