#!/usr/pkg/bin/perl
#
# Samba Multibyte convert tool
# copyright (c) 2000, Hiroshi MIURA 
# All rights reserved by Hiroshi MIURA
# copyright (c) 2003, MORIYAMA Masayuki
# 
# Changelog --> see README
#
# usage: commandname [-h][-d][-l][-p </where/to/some/hardlink/path/>]
#            </where/to/conv> [ </the/other/to/conv> [ <another/to/conv> .... ]]
#        smbfnconv [-h][-d][-l][-a <action>]
#            [-p </where/to/some/hardlink/path/>]
#            </where/to/conv> [ </the/other/to/conv> [ <another/to/conv> .... ]]
#        smbfnconv [-h][-d][-l][-2][-f <codeset> -t <codeset>]
#            [-p </where/to/some/hardlink/path/>]
#            </where/to/conv> [ </the/other/to/conv> [ <another/to/conv> .... ]]
#
#   -p:  default path => same path with original.
#
# Available command name is....
#  euctohex, euctocap, euctojis, euctosjis
#  jistohex, jistocap, jistoeuc, jistosjis
#  captohex, captoeuc, captojis, captosjis
#  hextoeuc, hextocap, hextojis, hextosjis
#  sjistohex, sjistocap, sjistojis, sjistoeuc
#  sjistosjis, captocap, hextohex, euctoeuc, jistojis
#
# these're 25 patterns. :-`)
#
# Available input codeset or output codeset are....
#  Samba 2: sjis, euc, euc3, utf-8, cap, hex
#  Samba 3: cp932, eucjp-ms, utf-8, cap, hex
#
require 5;
use Getopt::Std;
use File::stat;
use Text::Iconv;

# now, I use jcode.pl, does someone knows how to use Jcode.pm??
#
#
require "jcode.pl";

$version='3.0a';

getopts("dhl2p:a:f:t:");
&Show_Usage($comn) if ($opt_h);

# -----------------------------------------
# definition of important table and constants, regex.
#
@sjisconv= (
0xfa40, 0xfa49, 0xeeef,
0xfa4a, 0xfa53, 0x8754,
0xfa54, 0xfa54, 0x81ca,
0xfa55, 0xfa57, 0xeefa,
0xfa58, 0xfa58, 0x878a,
0xfa59, 0xfa59, 0x8782,
0xfa5a, 0xfa5a, 0x8784,
0xfa5b, 0xfa5b, 0x81e6,
0xfa5c, 0xfa7e, 0xed40,
0xfa80, 0xfa9b, 0xed63,
0xfa9c, 0xfafc, 0xed80,
0xfb40, 0xfb5b, 0xede1,
0xfb5c, 0xfb7e, 0xee40,
0xfb80, 0xfb9b, 0xee63,
0xfb9c, 0xfbfc, 0xee80,
0xfc40, 0xfc4b, 0xeee1,
);
$sjisconvlen=($#sjisconv+1)/3-1;

@sjisreg = (
# 0x8470, 0x847e, 0x8440,	# if Win95 compatible,
# 0x8480, 0x8491, 0x844f,	# validate these lines
0x8754, 0x875d, 0xfa4a,
0x8782, 0x8782, 0xfa59,
0x8784, 0x8784, 0xfa5a,
0x878a, 0x878a, 0xfa58,
0x8790, 0x8790, 0x81e0,
0x8791, 0x8791, 0x81df,
0x8792, 0x8792, 0x81e7,
0x8795, 0x8795, 0x81e3,
0x8796, 0x8796, 0x81db,
0x8797, 0x8797, 0x81da,
0x879a, 0x879a, 0x81e6,
0x879b, 0x879b, 0x81bf,
0x879c, 0x879c, 0x81be,
0xed40, 0xed62, 0xfa5c,
0xed63, 0xed7e, 0xfa80,
0xed80, 0xede0, 0xfa9c,
0xede1, 0xedfc, 0xfb40,
0xee40, 0xee62, 0xfb5c,
0xee63, 0xee7e, 0xfb80,
0xee80, 0xeee0, 0xfb9c,
0xeee1, 0xeeec, 0xfc40,
0xeeef, 0xeef8, 0xfa40,
0xeef9, 0xeef9, 0x81ca,
0xeefa, 0xeefc, 0xfa55,
0xfa54, 0xfa54, 0x81ca,
0xfa5b, 0xfa5b, 0x81e6,
);
$sjisreglen=($#sjisreg+1)/3-1;

@eucjpms_nec2ibm = (
0xadb5, 0xadb5, 0xf3fd,
0xadb7, 0xadbe, 0xf4a1,
0xade2, 0xade2, 0xf4ac,
0xade4, 0xade4, 0xf4ad,
0xadea, 0xadea, 0xf4ab,
);
$eucjpms_nec2ibmlen=($#eucjpms_nec2ibm+1)/3-1;
 
%match = (
 SJIS_C    => '[\x80-\x9f\xe0-\xfc][\x40-\xfc]',
 SJIS_KANA => '[\xa1-\xdf]',
 SJIS_O    => '[\x2f\x80-\xfc]',
 SJIS_S    => '[\x81-\x9f\xe0-\ec][\x40-\xfc]',
 SKOS_D    => '[\xed-\xfc][\x40-\xfc]',
 EUC3_C    => '\x8f?[\xa1-\xfe][\xa1-\xfe]'
);

$GETA=sprintf "%c%c",0x81,0xac;

#---------------------------------------------
# check command name which it called.
# 
if ($opt_a) {
    $comn=$opt_a;
} else {
    $comn=$0;
    $comn=~s|(.*/)*(.+)|$2|;
}

## who am I? :)
## definition of mb convert 

if     ($comn eq "euctohex"){
    $convmb= sub {
	local(*nname) = @_;
	my $name=$nname;
        jcode::euc2sjis(*nname);
        &sjis2rsjis(*nname);
        $nname =~ s/($match{SJIS_C})/sprintf ":%2x:%2x",ord($1),ord(substr($1,1))/geo;
        $nname =~ s/($match{SJIS_KANA})/sprintf ":%2x",ord($1)/geo; 
        ($name ne $nname);
    };

 }elsif ($comn eq "euctocap"){
    $convmb= sub {
	local(*nname) = @_;
        my $name=$nname;
        jcode::euc2sjis(*nname);
	&sjis2rsjis(*nname);
	$nname=~s/($match{SJIS_O})/sprintf ":%2x",ord($1)/geo;
	($name ne $nname);
      };

 }elsif ($comn eq "euctojis"){
    $convmb= sub {
	local(*nname) = @_;
        my $name=$nname;
	jcode::euc2jis(*nname);
        ($name ne $nname);
    };

 }elsif ($comn eq "euctosjis"){
    $convmb= sub {
	local(*nname) = @_;
        my $name=$nname;
	jcode::euc2jis(*nname);
        ($name ne $nname);
    };

 }elsif ($comn eq "hextoeuc"){
    $convmb= sub {
	local(*nname) = @_;
        my $name=$nname;
	$nname=~ s/\:([0-9a-f][0-9a-f])/sprintf "%c",hex($1)/geo;
	&sjis2csjis(*nname);
	return 0 if ($name eq $nname);
        jcode::sjis2euc(*nname);
    };

 }elsif ($comn eq "hextojis"){
    $convmb= sub {
	local(*nname) = @_;
        my $name=$nname;
	$nname=~ s/\:([0-9a-f][0-9a-f])/sprintf "%c",hex($1)/geo;
	&sjis2csjis(*nname);
	return 0 if ($name eq $nname);
	jcode::sjis2jis(*nname);
    };

 }elsif (($comn eq "hextocap")||($comn eq "captocap")){
    $convmb= sub {
	local(*nname) = @_;
        my $name=$nname;
	$nname=~ s/\:([0-9a-f][0-9a-f])/sprintf "%c",hex($1)/geo;
	return 0 if ($name eq $nname);
	&sjis2rsjis(*nname);
	$nname=~s/($match{SJIS_O})/sprintf ":%2x",ord($1)/geo;
	($name ne $nname);
    };

 }elsif ($comn eq "hextosjis"){
    $convmb= sub {
	local(*nname) = @_;
        my $name=$nname;
	$nname=~ s/\:([0-9a-f][0-9a-f])/sprintf "%c",hex($1)/geo;
	&sjis2rsjis(*nname);
	($name ne $nname);
    };

 }elsif ($comn eq "captoeuc"){
    $convmb= sub {
	local(*nname) = @_;
        my $name=$nname;
	$nname=~ s/\:([0-9a-f][0-9a-f])/sprintf "%c",hex($1)/geo;
	&sjis2csjis(*nname);
	return 0 if ($name eq $nname);
	jcode::sjis2euc(*nname);
    };

 }elsif ($comn eq "captojis"){
    $convmb= sub {
	local(*nname) = @_;
        my $name=$nname;
	$nname=~ s/\:([0-9a-f][0-9a-f])/sprintf "%c",hex($1)/geo;
	&sjis2csjis(*nname);
	return 0 if ($name eq $nname);
	jcode::sjis2jis(*nname);
    };

 }elsif ($comn eq "captosjis"){
    $convmb= sub {
	local(*nname) = @_;
        my $name=$nname;
	$nname=~ s/\:([0-9a-f][0-9a-f])/sprintf "%c",hex($1)/geo;
	return 0 if ($name eq $nname);
	&sjis2rsjis(*nname);
    };

 }elsif (($comn eq "captohex")||($comn eq "hextohex")){
    $convmb= sub {
	local(*nname) = @_;
        my $name=$nname;
	$nname=~ s/\:([0-9a-f][0-9a-f])/sprintf "%c",hex($1)/geo;
	&sjis2rsjis(*nname);
	return 0 if ($name eq $nname);
        $nname =~ s/($match{SJIS_C})/sprintf ":%2x:%2x",ord($1),ord(substr($1,1))/geo;
        $nname =~ s/($match{SJIS_KANA})/sprintf ":%2x",ord($1)/geo; 
    };

 }elsif ($comn eq "jistocap"){
    $convmb= sub {
	local(*nname) = @_;
        my $name=$nname;
	jcode::jis2jis(*nname);
	&sjis2rsjis(*nname);
	$nname=~s/($match{SJIS_O})/sprintf ":%2x",ord($1)/geo;
	($name ne $nname);
      };

 }elsif ($comn eq "jistohex"){
    $convmb= sub {
	local(*nname) = @_;
        my $name=$nname;
	jcode::jis2sjis(*nname);
	&sjis2rsjis(*nname);
        $nname =~ s/($match{SJIS_C})/sprintf ":%2x:%2x",ord($1),ord(substr($1,1))/geo;
        $nname =~ s/($match{SJIS_KANA})/sprintf ":%2x",ord($1)/geo; 
       ($name ne $nname);
      };

 }elsif ($comn eq "jistoeuc"){
    $convmb= sub {
	local(*nname) = @_;
        my $name=$nname;
	jcode::jis2euc(*nname);
        ($name ne $nname);
    };

 }elsif ($comn eq "jistosjis"){
    $convmb= sub {
	local(*nname) = @_;
        my $name=$nname;
	jcode::jis2sjis(*nname);
	&sjis2rsjis(*nname);
        ($name ne $nname);
    };

 }elsif ($comn eq "sjistocap"){
    $convmb= sub {
	local(*nname) = @_;
        my $name=$nname;
	&sjis2rsjis(*nname);
	$nname=~s/($match{SJIS_O})/sprintf ":%2x",ord($1)/geo;
	($name ne $nname);
    };

 }elsif ($comn eq "sjistohex"){
    $convmb= sub {
	local(*nname) = @_;
        my $name=$nname;
        &sjis2rsjis(*nname);
        $nname =~ s/($match{SJIS_C})/sprintf ":%2x:%2x",ord($1),ord(substr($1,1))/geo;
        $nname =~ s/($match{SJIS_KANA})/sprintf ":%2x",ord($1)/geo;
        ($name ne $nname);
    };

 }elsif ($comn eq "sjistoeuc"){
    $convmb= sub {
	local(*nname) = @_;
        my $name=$nname;
        &sjis2csjis(*nname);
	jcode::sjis2euc(*nname);
        ($name ne $nname);
    };

 }elsif ($comn eq "sjistojis"){
    $convmb= sub {
	local(*nname) = @_;
        my $name=$nname;
        &sjis2csjis(*nname);
	jcode::sjis2jis(*nname);
        ($name ne $nname);
    };

 }elsif ($comn eq "sjistosjis"){
    $convmb= sub {
	local(*nname) = @_;
        my $name=$nname;
	&sjis2rsjis(*nname);
	($name ne $nname);
    };

 }elsif ($comn eq "euctoeuc"){
    $convmb= sub {
	local(*nname) = @_;
        my $name=$nname;
	jcode::euc2sjis(*nname);
	&sjis2csjis(*nname);
        jcode::sjis2euc(*nname);
        ($name ne $nname);
    };

 }elsif ($comn eq "jistojis"){
    $convmb= sub {
	local(*nname) = @_;
        my $name=$nname;
	jcode::jis2sjis(*nname);
	&sjis2csjis(*nname);
        jcode::sjis2jis(*nname);
        ($name ne $nname);
    };

 }elsif ($opt_f && $opt_t) {
    $convmb= sub {
	local(*nname) = @_;
        my $name=$nname;
    	my ($from, $to) = ($opt_f, $opt_t);

	if ($opt_f =~ /^sjis$/) {
	    $from = "cp932";
	}elsif ($opt_f =~ /^euc$/i) {
	    jcode::euc2sjis(*nname);
	    $from = "cp932";
	}elsif ($opt_f =~ /^euc3$/i) {
	    $from = "eucjp-ms";
	}elsif ($opt_f =~ /^(hex|cap)$/i) {
	    $nname =~ s/\:([0-9a-f][0-9a-f])/sprintf "%c",hex($1)/geo;
	    $from = "cp932";
	}elsif ($opt_f =~ /^utf8$/i) {
	    $from = "utf-8";
	}

	if ($opt_t =~ /^(sjis|cap|hex|euc)$/i) {
	    $to = "cp932";
	}elsif ($opt_t =~ /^euc3$/i) {
	    $to =  "eucjp-ms";
	}elsif ($opt_t =~ /^utf8$/i) {
	    $to = "utf-8";
	}

   	my $conv = Text::Iconv->new($from, $to) or die;
	$nname = $conv->convert($nname);

	if ($opt_2 || $opt_t =~ /^(sjis|euc3)$/) {
	    if ($to =~ /^(cp932|(?:cs)?windows-?31j|sjis-(?:open|win))$/i) {
		&sjis2rsjis(*nname);
	    }elsif ($to =~ /^(euc-?jp-ms|eucjp-(?:open|win))$/) {
		&eucjpms2euc3(*nname);
	    }
	}

	if ($opt_t =~ /^euc$/i) {
	    &sjis2csjis(*nname);
	    jcode::sjis2euc(*nname);
	}elsif ($opt_t =~ /^cap$/i) {
	    $nname=~s/([\x80-\xff])/sprintf ":%2x",ord($1)/geo;
	}elsif ($opt_t =~ /^hex$/i) {
	    $nname =~ s/($match{SJIS_C})/sprintf ":%2x:%2x",ord($1),ord(substr($1,1)
)/geo;
	    $nname =~ s/($match{SJIS_KANA})/sprintf ":%2x",ord($1)/geo;
	}
	($name ne $nname);
    };

 }else {
    &Show_Usage();
    exit 1;
}

#========================================================
# main routine.
#

umask 0000;

if ($opt_l || $opt_p) { # when want to link files
       &lnconv(@ARGV);
} else {                # when rename files
   while ($arg = shift @ARGV) {
#       $arg =~ s/(\.|\.\.)\/+/\1/o;
       &fnconv($arg); 
   }
}
exit;


#===========================================================
# subroutines.
#
#-------------------------------------------
# link arguments with file name conversion.
#

sub lnconv() {

  open(FILEN, "find @_ -print |") or die "Cannot open directories\n"; 
  
  while (<FILEN>){
      chomp();
      $nname=$name=$_;
      &$convmb(*nname);
      $nname="$opt_p/$nname" if ($opt_p);
      
      if ( -l $name ) {                           # that is link
	  # only make symbolic link to original pointed.
	  # it may be convert the linked file/directory name?
	  $linked = readlink $name;
	  symlink $linked, $nname;
      } elsif ( -d $name && $name !~ /^(\.|\.\.)$/) {                
	  my $st = stat($name);
	  if ($opt_d) {
	      printf STDERR "mkdir %s %o\n", $nname, $st->mode & 07777;
	  } else {
	      if (mkdir $nname, $st->mode & 07777) {
		  utime $st->atime, $st->mtime, $nname;
		  chown $st->uid, $st->gid, $nname;
	      } else {
		  print STDERR $!;
	      }
	  }
      } elsif ( -f $name ) {
	  if ($opt_d) {
	      print STDERR "convert $name to  $nname\n";
	  } else {
	      unless (link $name, $nname) {
	          print "unless (link $name, $nname)\n";
		  print STDERR "fail link: operation not permitted.\n";
	      }
	  }
      } else {
	  # pipe or  device file
	  # ignore it
      }
  }
}


#--------------------------------------------------------
# rename argument's basename  with &$convmb
#

sub fnconv() {
    local($name)=@_; 

    if ( -l $name ) {
	#only ignore it
    } elsif ( -d $name ) {  
	$athash{"$name"}=stat($name)->atime;
	$mthash{"$name"}=stat($name)->mtime;
	local($dh)="DH_$name";
	opendir $dh, $name;
	while ($dentory=readdir $dh) {
	    next if ($dentory eq "." || $dentory eq "..");  # ignore "." and ".."
	    &fnconv("$name/$dentory");            # recursive call
	}
	closedir $dh;
	$nname = &renfn($name);
	if ($opt_d) {
	    printf "utime %d, %d, %s\n", $athash{"$name"},$mthash{"$name"},$nname unless (($name eq ".") || ($name eq ".."));
	} else {
	    utime $athash{"$name"}, $mthash{"$name"}, $nname unless (($name eq ".") || ($name eq ".."));
	}
	delete $mthash{"$name"};
	delete $athash{"$name"};

    } elsif ( -f $name ) {              # if name is file then only rename it.
	&renfn($name);
    } else {                            # another case
	# only ignore it for safety.
    }
}

#--------------------------------------------------------
# rename argument's basename  with &$convmb
#
#
sub renfn() {
    local($name)=@_;
    local($basedir, $nname)=("", $name);

    if ($name =~ m|(.*/)([^/]*)|o) {    # split dir and fname
	$basedir="$1";
	$nname  ="$2";
    } 

    return $name unless (&$convmb(*nname));     # only fname converted	
    $nname=$basedir . $nname;	

    if ($opt_d) {  
	print STDERR "rename $name to  $nname\n";
    } else {       
	unless (rename $name, $nname) {
	    print STDERR "fail rename: operation not permitted.\n";
	}
    }
    $nname;
}
#---------------------------------------------------------
# convert regular sjis to compressed sjis.
# using binary search method.
# 
sub sjis2csjis {
    local(*line) = @_;
    $line =~ s/($match{SJIS_C})/&s2c($1)/geo;
}

sub s2c {
    local($char) = @_;
    $mb=ord($char)*256+ord(substr($char,1));
    return $char if ($mb <= 0xeaa4);                  # JIS X 0208:1997
    return $char if ($mb >= 0xed40 && $mb <= 0xeefc); # NEC selection of IBM extensions
    return $GETA unless ($mb >= $sjisconv[0] &&
                         $mb <= $sjisconv[$sjisconvlen*3+1]);
    $min=0;$max=$sjisconvlen;		# OK is will be conv
    while($max >= $min){
	$j=$min+($max-$min)%2;
	if ($sjisconv[$j*3+0] > $mb){		 
	    $max=$j-1;
	} elsif ($mb > $sjisconv[$j*3+1]) {
	    $min=$j+1;
	} else {
	    $mb=$sjisconv[$j*3+2]+$mb-$sjisconv[$j*3+0];
	    $char=sprintf "%c%c",($mb >> 8) & 0xff,$mb & 0xff;
	    last;
	}
    } 
    $char;
}
#---------------------------------------------------------
#
# convert regular sjis to regular sjis.
# using binary search method.
# 
sub sjis2rsjis {
    local(*line) = @_;
    $line =~ s/($match{SJIS_C})/&s2r($1)/geo;
}

sub s2r {
    local($char) = @_;
    $mb=ord($char)*256 + ord(substr($char,1));
    return $char if ($mb < $sjisreg[0]);	# check if it is 
    if ($sjisreg[$sjisreglen*3+1] < $mb){	# target to conv?
	$char=$GETA if ($mb > 0xfc4b);
	return $char;
    };
    $min=0;$max=$sjisreglen;		# OK is will be conv
    while($max >= $min){
	    $j=$min+($max-$min)%2;
	    if ($sjisreg[$j*3+0] > $mb){		 
		$max=$j-1;
	    } elsif ($mb > $sjisreg[$j*3+1]) {
		$min=$j+1;
	    } else {					# hit it!
		$mb=$sjisreg[$j*3+2]+$mb-$sjisreg[$j*3+0];
		$char=sprintf "%c%c",($mb >> 8) & 0xff, $mb & 0xff;
		last;
	    }
	} 
$char;
}
#---------------------------------------------------------
#
# convert eucJP-ms to EUC3.
# using binary search method.
# 
sub eucjpms2euc3 {
    local(*line) = @_;
    $line =~ s/($match{EUC3_C})/&ems2e3($1)/geo;
}

sub ems2e3 {
    local($char) = @_;
    $mb=ord($char)*256 + ord(substr($char,1));
    return $char if ($char =~ /^\x8f/);
    return $char if ($mb < $eucjpms_nec2ibm[0]  # check if it is target conv?
                     || $mb > $eucjpms_nec2ibm[$eucjpms_nec2ibmlen*3+1]);
    $min=0;$max=$eucjpms_ne2ibmlen;		# OK is will be conv
    while($max >= $min){
	    $j=$min+($max-$min)%2;
	    if ($eucjpms_nec2ibm[$j*3+0] > $mb){		 
		$max=$j-1;
	    } elsif ($mb > $eucjpms_nec2ibm[$j*3+1]) {
		$min=$j+1;
	    } else {					# hit it!
		$mb=$eucjpms_nec2ibm[$j*3+2]+$mb-$eucjpms_nec2ibm[$j*3+0];
		$char=sprintf "\x8f%c%c",($mb >> 8) & 0xff, $mb & 0xff;
		last;
	    }
	} 
$char;
}


#---------------------------------------------------------

sub Show_Usage {
    printf "This is samba companion tool. smbchartool Ver.%s\n",$version;
    print <<__EOL__;
This product is distoributed under the GNU General Public License Version 2.

Usage: command [-h][-d][-l] [-p <linkpath>] <convpath>
       smbfnconv [-h][-d][-l][-a <action>] [-p <linkpath>] <convpath>
       smbfnconv [-h][-d][-l][-2] [-f <codeset> -t <codeset>]
                 [-p <linkpath>] <convpath>
  -h: show this help message
  -d: debug option. Don't actual move and removing, only display how to do
  -l: make hard link, not move it
  -p: make link to another directory
     (it may be use with -l)
  -a: point out an action
  -2: output codeset is made into compatibility with Samba 2
  -f: input codeset is set to <codeset>
  -t: output codeset is set to <codeset>

Available command names or actions are....
  euctohex, euctocap, euctojis, euctosjis
  jistohex, jistocap, jistoeuc, jistosjis
  captohex, captoeuc, captojis, captosjis
  hextoeuc, hextocap, hextojis, hextosjis
  sjistohex, sjistocap, sjistojis, sjistoeuc
  sjistosjis, captocap, hextohex, euctoeuc, jistojis

Available input codeset or output codeset are....
  Samba 2: sjis, euc, euc3, utf-8, cap, hex
  Samba 3: cp932, eucjp-ms, utf-8, cap, hex

Copyright (c) 2000, Hiroshi MIURA <miura\@samba.gr.jp>
Copyright (c) 2003, MORIYAMA Masayuki <moriyama\@miraclelinux.com>
__EOL__
exit;
}
