#!/usr/pkg/bin/perl
#
# AppleSingle/AppleDouble dump
#
# (c) 2009-2012 by HAT <hat@fa2.so-net.ne.jp>
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU 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 General Public License for more details.
#

#
# References:
#
# Applesingle and AppleDouble format internals (version 1)
# http://users.phg-online.de/tk/netatalk/doc/Apple/v1/
#
# AppleSingle/AppleDouble Formats for Foreign Files Developer's Note (version2)
# http://users.phg-online.de/tk/netatalk/doc/Apple/v2/AppleSingle_AppleDouble.pdf
#
# Inside Macintosh: Macintosh Toolbox Essentials /
# Chapter 7 - Finder Interface / Finder Interface Reference
# http://developer.apple.com/legacy/mac/library/documentation/mac/toolbox/Toolbox-463.html
#
# Finder Interface Reference
# http://developer.apple.com/legacy/mac/library/documentation/Carbon/Reference/Finder_Interface/Reference/reference.html
#
# Technical Note TN1150  HFS Plus Volume Format
# http://developer.apple.com/mac/library/technotes/tn/tn1150.html#FinderInfo
#
# CarbonHeaders source
# http://www.opensource.apple.com/source/CarbonHeaders/CarbonHeaders-8A428/Finder.h
# http://www.opensource.apple.com/source/CarbonHeaders/CarbonHeaders-9A581/Finder.h
#
# Xcode 3.2.1
# /usr/bin/SetFile
# /usr/bin/GetFileInfo
#
# Mac OS X 10.6.2 kernel source
# http://www.opensource.apple.com/source/xnu/xnu-1486.2.11/bsd/vfs/vfs_xattr.c
#

use File::Basename;
use File::Spec;
use File::Temp qw /tempfile/;
use bigint; # require perl >= 5.8
use IPC::Open2 qw /open2/;

# check command for extended attributes -----------------------------------

if (     0 == system("which getfattr > /dev/null 2>&1")) {
    $eacommand = 1;
} elsif (0 == system("which attr > /dev/null 2>&1")) {
    $eacommand = 2;
} elsif (0 == system("which runat > /dev/null 2>&1")) {
    $eacommand = 3;
} elsif (0 == system("which getextattr > /dev/null 2>&1")) {
    $eacommand = 4;
} else {
    $eacommand = 0;
}

#printf ( "eacommand = %d\n", $eacommand );   # debug

# parse command line -----------------------------------------------

$stdinputmode = 0;
$eaoption = 0;
$finderinfo = 0;              #  0: unknown   1: file   2: directory
while ($arg = shift @ARGV)
{
    if  ($arg =~ /^(-h|-help|--help)$/ ) {
        printf ("usage: %s [-a] [FILE|DIR]\n"         ,basename($0));
        printf (" or:   %s -e FILE|DIR\n"             ,basename($0));
        printf (" or:   %s -f [FILE]\n"               ,basename($0));
        printf (" or:   %s -d [FILE]\n"               ,basename($0));
        printf (" or:   %s -h|-help|--help\n"         ,basename($0));
        printf (" or:   %s -v|-version|--version\n"   ,basename($0));
        printf ("Dump AppleSingle/AppleDouble format data.\n");
        printf ("With no FILE|DIR, or when FILE|DIR is -, read standard input.\n");
        printf ("\n");
        printf ("  -a (default)     Dump a AppleSingle/AppleDouble data for FILE or DIR\n");
        printf ("                   automatically.\n");
        printf ("                   If FILE is not AppleSingle/AppleDouble format,\n");
        printf ("                   look for extended attribute, .AppleDouble/FILE and ._FILE.\n");
        printf ("                   If DIR, look for extended attribute,\n");
        printf ("                   DIR/.AppleDouble/.Parent and ._DIR.\n");
        printf ("  -e               Dump extended attribute of FILE or DIR\n");
        printf ("  -f               Dump FILE. Assume FinderInfo to be FileInfo.\n");
        printf ("  -d               Dump FILE. Assume FinderInfo to be DirInfo.\n");
        printf ("  -h,-help,--help  Display this help and exit\n");
        printf ("  -v,-version,--version  Show version and exit\n");
        printf ("\n");
        printf ("There is no way to detect whether FinderInfo is FileInfo or DirInfo.\n");
        printf ("By default, %s examins whether file or directory,\n"   ,basename($0));
        printf ("a parent directory is .AppleDouble, filename is ._*, filename is .Parent,\n");
        printf ("and so on.\n");
        printf ("If setting option -e, -f or -d, %s assume FinderInfo and doesn't look for\n");
        printf ("another file.\n");
        exit 1;
    } elsif ($arg =~ /^(-v|-version|--version)$/ ) {
        printf ("%s \(Netatalk 2.3.2\)\n", basename($0));
        exit 1;
    } elsif ($arg eq "-a") {
        $finderinfo = 0;
    } elsif ($arg eq "-e") {
        if ($eacommand == 0) {
            printf (STDERR "%s: unsupported option -e\n", basename($0));
            printf (STDERR "because neither getfattr, attr, runat nor getextattr is found.\n");
            exit 1;
        }
        $eaoption = 1;
    } elsif ($arg eq "-f") {
        $finderinfo = 1;
    } elsif ($arg eq "-d") {
        $finderinfo = 2;
    } elsif ($arg eq "-") {
        $stdinputmode = 1;
    } elsif ($arg =~ /^-/) {
        printf (STDERR "%s: invalid option %s\n", basename($0), $arg);
        printf (STDERR "Try \`%s\ -h' for more information.\n", basename($0));
        exit 1;
    } else {
        $afile = $arg;
    }
}

if (!($afile)) {
    $stdinputmode = 1;
} elsif (!( -e $afile)) {
    printf (STDERR "\"%s\" is not found.\n", $afile);
    exit 1;
}

# detect FinderInfo, and search AppleSingle/AppleDouble file --------------

$abspath = File::Spec->rel2abs($afile);
($basename, $path, $ext) = fileparse($abspath);

if ( $stdinputmode == 1) {
    ($eatempfh, $openfile) = tempfile(UNLINK => 1);
    system("cat - > $openfile");
    close($eatempfh);
    $openmessage = "Dumping Standard Input...\n";
} elsif ( $eaoption == 1 ) {
    if ( -f $afile ) {
        $finderinfo = 1;
    } elsif ( -d $afile ) {
        $finderinfo = 2;
    } else {
        printf (STDERR "unknown error: %s\n", $afile);
        exit 1;
    }
    if ( 0 == checkea($afile) ) {
        printf (STDERR "\"%s\"'s extended attribute is not found\n",  $afile);
        exit 1;
    }
    $openfile = eaopenfile($afile);
    $openmessage = "Dumping \"$afile\"'s extended attribute...\n";
} elsif ( $finderinfo != 0 ) {
    $openfile = $afile;
    $openmessage = "Dumping \"$openfile\"...\n";
} elsif ( -f $afile ) {
    if ( $basename eq ".Parent") {
        $finderinfo = 2;
    } elsif ( $path =~ /\/.AppleDouble\/$/ ) {
        $finderinfo = 1;
    } elsif ( $basename =~ /^._/ ) {
        if ( -f $path.substr($basename, 2) ) {
            $finderinfo = 1;
        } elsif ( -d $path.substr($basename, 2) ) {
            $finderinfo = 2;
        }
    }
    if (!open(INFILE, "<$afile")) {
        printf (STDERR "cannot open %s\n",  $afile);
        exit 1;
    }
    read(INFILE,$buf,4);
    $val = unpack("N", $buf );
    close(INFILE);
    if ($val == 0x00051600 || $val == 0x00051607) {
        $openfile = $afile;
        $openmessage = "Dumping \"$openfile\"...\n";
    } else {
        printf ("\"%s\" is not AppleSingle/AppleDouble format.\n", $afile);
        $finderinfo = 1;
        $adcount = 0;
        $netatalkfile = $path.".AppleDouble/".$basename;
        $osxfile = $path."._".$basename;

        if ( 1 == checkea($afile) ) {
            printf ("\"%s\"\'s extended attribute is found.\n", $afile);
            $adcount++;
            $openfile = eaopenfile($afile);
            $openmessage = "Dumping \"$afile\"'s extended attribute...\n";
        }
        if ( -e $netatalkfile ) {
            printf ("\"%s\" is found.\n", $netatalkfile);
            $adcount++;
            $openfile = $netatalkfile;
            $openmessage = "Dumping \"$openfile\"...\n";
        }
        if ( -e $osxfile ) {
            printf ("\"%s\" is found.\n", $osxfile);
            $adcount++;
            $openfile = $osxfile;
            $openmessage = "Dumping \"$openfile\"...\n";
        }
        if ( $adcount == 0 ) {
            printf ("AppleSingle/AppleDouble data is not found.\n");
            exit 1;
        }
        if ( $adcount != 1 ) {
            printf ("Specify any one.\n");
            exit 1;
        }
    }
} elsif ( -d $afile) {
    printf ("\"%s\" is a directory.\n", $afile);
    $finderinfo = 2;
    $adcount = 0;
    $netatalkfile = $path.$basename."/.AppleDouble/.Parent";
    $osxfile = $path."._".$basename;

    if ( 1 == checkea($afile) ) {
        printf ("\"%s\"\'s extended attribute is found.\n", $afile);
        $adcount++;
        $openfile = eaopenfile($afile);
        $openmessage = "Dumping \"$afile\"'s extended attribute...\n";
    }
    if ( -e $netatalkfile ) {
        printf ("\"%s\" is found.\n", $netatalkfile);
        $adcount++;
        $openfile= $netatalkfile;
        $openmessage = "Dumping \"$openfile\"...\n";
    }
    if ( -e $osxfile ) {
        printf ("\"%s\" is found.\n", $osxfile);
        $adcount++;
        $openfile = $osxfile;
        $openmessage = "Dumping \"$openfile\"...\n";
    }
    if ( $adcount == 0 ) {
        printf ("AppleSingle/AppleDouble data is not found.\n");
        exit 1;
    }
    if ( $adcount != 1 ) {
        printf ("Specify any one.\n");
        exit 1;
    }
} else {
    printf (STDERR "unknown error: %s\n", $afile);
    exit 1;
}

if (!open(INFILE, "<$openfile")) {
    printf (STDERR "cannot open %s\n",  $openfile);
    exit 1;
}

printf ($openmessage);

#Dump --------------------------------------------------------

# Magic Number -----------------------------------------------

print "-------------------------------------------------------------------------------\n";

read(INFILE,$buf,4);
$val = unpack("N", $buf );
printf("MagicNumber: %08X", $val);
if    ( $val == 0x00051600 ) {
    printf("                                        : AppleSingle");
}
elsif ( $val == 0x00051607 ) {
    printf("                                        : AppleDouble");
}
else                         {
    printf("                                        : Unknown"    );
}
print "\n";

# Version Number ---------------------------------------------

read(INFILE,$buf,4);
$version = unpack("N", $buf );
printf("Version    : %08X", $version);
if ( $version == 0x00010000 ) {
    printf("                                        : Version 1");
} elsif ( $version == 0x00020000 ) {
    printf("                                        : Version 2");
} else {
    printf("                                        : Unknown"  );
}
print "\n";

# v1:Home file system / v2:Filler ----------------------------

read(INFILE,$buf,16);
if ( $version == 0x00010000 ) {
    print "HomeFileSys:";
} else {
    print "Filler     :";
}
hexdump($buf, 16, 16, " ");

# Number of entities -----------------------------------------

read(INFILE,$buf,2);
$entnum = unpack("n", $buf );
printf("Num. of ent: %04X    ", $entnum);
printf("                                        : %d", $entnum);
print "\n";

# data -------------------------------------------------------

for ( $num = 0 ; $num < $entnum ; $num++) {

    seek(INFILE, ($num * 12 + 26), 0);

#    Entry ---------------------------------------------------

    read(INFILE,$buf,4);
    $entid = unpack("N", $buf );
    print "\n-------------------------------------------------------------------------------\n";
    printf("Entry ID   : %08X", $entid);
    if    ( $entid ==  1 )         { printf(" : Data Fork"); }
    elsif ( $entid ==  2 )         { printf(" : Resource Fork"); }
    elsif ( $entid ==  3 )         { printf(" : Real Name"); }
    elsif ( $entid ==  4 )         { printf(" : Comment"); }
    elsif ( $entid ==  5 )         { printf(" : Icon, B&W"); }
    elsif ( $entid ==  6 )         { printf(" : Icon Color"); }
    elsif ( $entid ==  7 )         { printf(" : File Info"); }
    elsif ( $entid ==  8 )         { printf(" : File Dates Info"); }
    elsif ( $entid ==  9 )         { printf(" : Finder Info"); }
    elsif ( $entid == 10 )         { printf(" : Macintosh File Info"); }
    elsif ( $entid == 11 )         { printf(" : ProDOS File Info"); }
    elsif ( $entid == 12 )         { printf(" : MS-DOS File Info"); }
    elsif ( $entid == 13 )         { printf(" : Short Name"); }
    elsif ( $entid == 14 )         { printf(" : AFP File Info"); }
    elsif ( $entid == 15 )         { printf(" : Directory ID"); }
    elsif ( $entid == 0x8053567E ) { printf(" : CNID (Netatalk Extended)"); }
    elsif ( $entid == 0x8053594E ) { printf(" : DB stamp (Netatalk Extended)"); }
    elsif ( $entid == 0x80444556 ) { printf(" : dev (Netatalk Extended)"); }
    elsif ( $entid == 0x80494E4F ) { printf(" : inode (Netatalk Extended)"); }
    else                           { printf(" : Unknown"); }
    print "\n";

#    Offset -------------------------------------------------

    read(INFILE,$buf,4);
    $ofst = unpack("N", $buf );
    printf("Offset     : %08X", $ofst);
    printf(" : %d ", $ofst);

#    Length -------------------------------------------------

    read(INFILE,$buf,4);
    $len = unpack("N", $buf );
    printf("\nLength     : %08X", $len);
    printf(" : %d", $len);
    $quo = $len >> 4;
    $rem = $len & 0xF;
    print "\n";

#     Dump for each Entry ID --------------------------------

#    if ( $entid ==  1 ) { ; } # Data Fork
#    if ( $entid ==  2 ) { ; } # Resource Fork
#    if ( $entid ==  3 ) { ; } # Real Name
#    if ( $entid ==  4 ) { ; } # Comment
#    if ( $entid ==  5 ) { ; } # Icon, B&W
#    if ( $entid ==  6 ) { ; } # Icon Color
#    if ( $entid ==  7 ) { ; } # File Info
    if ( $entid ==  8 ) { filedatesdump($ofst,$len); }
    elsif ( $entid ==  9 ) { finderinfodump($ofst,$len); }
#    if ( $entid == 10 ) { ; } # Macintosh File Info
#    if ( $entid == 11 ) { ; } # ProDOS File Info
#    if ( $entid == 12 ) { ; } # MS-DOS File Info
#    if ( $entid == 13 ) { ; } # Short Name
#    if ( $entid == 14 ) { ; } # AFP File Info 
    elsif ( $entid == 15 ) { print "\n"; bedump($ofst,$len); } # Directory ID
    elsif ( $entid == 0x8053567E  ) { print "\n"; bedump($ofst,$len); ledump($ofst,$len); } # CNID (Netatalk Extended)
    elsif ( $entid == 0x8053594E  ) { print "\n"; bedump($ofst,$len); ledump($ofst,$len); } # DB stamp (Netatalk Extended)
    elsif ( $entid == 0x80444556  ) { print "\n"; bedump($ofst,$len); ledump($ofst,$len); } # dev (Netatalk Extended)
    elsif ( $entid == 0x80494E4F  ) { print "\n"; bedump($ofst,$len); ledump($ofst,$len); } # inode (Netatalk Extended)

#    RAW Dump ---------------------------------------------------

    if ( ($quo > 0) || ($rem > 0)) {
        print "\n";
        print "-RAW DUMP--:  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F : (ASCII)\n";
    }

    seek(INFILE, $ofst, 0);
    rawdump($quo, $rem);

}

close(INFILE);
exit 0;

#sub -----------------------------------------------------------

sub filedatesdump {
    my ($ofst, $len) = @_;
    my ($datedata);
    my ($i);
    my ($datestr);

    @datetype =('create    ', 'modify    ', 'backup    ', 'access    ');

    seek(INFILE, $ofst, 0);

    print "\n";
    printf ("-DATE------:          : (GMT)                    : (Local)\n");

    for ( $i = 0 ; $i < 4 ; $i++) {
        read(INFILE,$buf,4);
        $datedata = unpack("N", $buf );
        if ($datedata < 0x80000000) {
            $datestr = gmtime( $datedata + 946684800)
                ." : "
                .localtime( $datedata + 946684800);
        } elsif ($datedata == 0x80000000) {
            $datestr = "Unknown or Initial";
        } else {
            $datestr = gmtime( $datedata - 3348282496)
                ." : "
                .localtime( $datedata - 3348282496);
        }
        printf ("%s : %08X : %s\n",$datetype[$i], $datedata, $datestr);
    }
}

sub finderinfodump {
    my ($ofst, $len) = @_;

    seek(INFILE, $ofst, 0);

    if ($finderinfo == 0) {
        print "\n";
        print "-NOTE------: cannot detect whether FInfo or DInfo. assume FInfo.\n";
    }

    if ($finderinfo == 0 || $finderinfo == 1) {
        filefinderinfodump();
    } elsif ($finderinfo == 2) {
        dirfinderinfodump();
    } else {
        print STDERR "unknown FinderInfo type\n"
    }

    if ($len > 32) { eadump(); }
}

sub filefinderinfodump {

    print "\n";
    print "-FInfo-----:\n";

    read(INFILE,$buf,4);
    print "Type       : ";
    hexdump($buf, 4, 4, "");

    read(INFILE,$buf,4);
    print "Creator    : ";
    hexdump($buf, 4, 4, "");

    flagsdump();

    read(INFILE,$buf,2);
    $val = unpack("n", $buf );
    printf("Location v : %04X", $val);
    printf("     : %d\n", $val>0x7FFF?$val-0x10000:$val);

    read(INFILE,$buf,2);
    $val = unpack("n", $buf );
    printf("Location h : %04X", $val);
    printf("     : %d\n", $val>0x7FFF?$val-0x10000:$val);

    read(INFILE,$buf,2);
    print "Fldr       : ";
    hexdump($buf, 2, 4, "");

    print "\n";
    print "-FXInfo----:\n";

    read(INFILE,$buf,2);
    $val = unpack("n", $buf );
    printf("Rsvd|IconID: %04X", $val);
    printf("     : %d\n", $val>0x7FFF?$val-0x10000:$val);

    read(INFILE,$buf,2);
    print "Rsvd       : ";
    hexdump($buf, 2, 4, "");
    read(INFILE,$buf,2);
    print "Rsvd       : ";
    hexdump($buf, 2, 4, "");
    read(INFILE,$buf,2);
    print "Rsvd       : ";
    hexdump($buf, 2, 4, "");

    xflagsdump();

    read(INFILE,$buf,2);
    $val = unpack("n", $buf );
    printf("Rsvd|commnt: %04X", $val);
    printf("     : %d\n", $val>0x7FFF?$val-0x10000:$val);

    read(INFILE,$buf,4);
    $val = unpack("N", $buf );
    printf("PutAway    : %08X", $val);
    printf(" : %d\n", $val>0x7FFFFFFF?$val-0x100000000:$val); # Why SInt32?

}

sub dirfinderinfodump {

    print "\n";
    print "-DInfo-----:\n";

    read(INFILE,$buf,2);
    $val = unpack("n", $buf );
    printf("Rect top   : %04X", $val);
    printf("     : %d\n", $val>0x7FFF?$val-0x10000:$val);

    read(INFILE,$buf,2);
    $val = unpack("n", $buf );
    printf("Rect left  : %04X", $val);
    printf("     : %d\n", $val>0x7FFF?$val-0x10000:$val);

    read(INFILE,$buf,2);
    $val = unpack("n", $buf );
    printf("Rect bottom: %04X", $val);
    printf("     : %d\n", $val>0x7FFF?$val-0x10000:$val);

    read(INFILE,$buf,2);
    $val = unpack("n", $buf );
    printf("Rect right : %04X", $val);
    printf("     : %d\n", $val>0x7FFF?$val-0x10000:$val);

    flagsdump();

    read(INFILE,$buf,2);
    $val = unpack("n", $buf );
    printf("Location v : %04X", $val);
    printf("     : %d\n", $val>0x7FFF?$val-0x10000:$val);

    read(INFILE,$buf,2);
    $val = unpack("n", $buf );
    printf("Location h : %04X", $val);
    printf("     : %d\n", $val>0x7FFF?$val-0x10000:$val);

    read(INFILE,$buf,2);
    print "View       : ";
    hexdump($buf, 2, 4, "");

    print "\n";
    print "-DXInfo----:\n";

    read(INFILE,$buf,2);
    $val = unpack("n", $buf );
    printf("Scroll v   : %04X", $val);
    printf("     : %d\n", $val>0x7FFF?$val-0x10000:$val);

    read(INFILE,$buf,2);
    $val = unpack("n", $buf );
    printf("Scroll h   : %04X", $val);
    printf("     : %d\n", $val>0x7FFF?$val-0x10000:$val);

    read(INFILE,$buf,4);
    $val = unpack("N", $buf );
    printf("Rsvd|OpnChn: %08X", $val);
    printf(" : %d\n", $val>0x7FFFFFFF?$val-0x100000000:$val); # Why SInt32?

    xflagsdump();

    read(INFILE,$buf,2);
    print "Comment    : ";
    hexdump($buf, 2, 4, "");

    read(INFILE,$buf,4);
    $val = unpack("N", $buf );
    printf("PutAway    : %08X", $val);
    printf(" : %d\n", $val>0x7FFFFFFF?$val-0x100000000:$val); # Why SInt32?

}
sub flagsdump {

    @colortype =('none', 'gray', 'green', 'purple', 'blue', 'yellow', 'red', 'orange');

    read(INFILE,$buf,2);
    $flags = unpack("n", $buf );
    printf ("isAlias    : %d\n", ($flags >> 15) & 1);
    printf ("Invisible  : %d\n", ($flags >> 14) & 1);
    printf ("hasBundle  : %d\n", ($flags >> 13) & 1);
    printf ("nameLocked : %d\n", ($flags >> 12) & 1);
    printf ("Stationery : %d\n", ($flags >> 11) & 1);
    printf ("CustomIcon : %d\n", ($flags >> 10) & 1);
    printf ("Reserved   : %d\n", ($flags >>  9) & 1);
    printf ("Inited     : %d\n", ($flags >>  8) & 1);
    printf ("NoINITS    : %d\n", ($flags >>  7) & 1);
    printf ("Shared     : %d\n", ($flags >>  6) & 1);
    printf ("SwitchLaunc: %d\n", ($flags >>  5) & 1);
    printf ("Hidden Ext : %d\n", ($flags >>  4) & 1);
    printf ("color      : %d%d%d      : %s\n", ($flags >>  3) & 1,
            ($flags >>  2) & 1,
            ($flags >>  1) & 1,
            @colortype[($flags & 0xE)>>1]);
    printf ("isOnDesk   : %d\n", ($flags >>  0) & 1);

}

sub xflagsdump {

    read(INFILE,$buf,2);
    $flags = unpack("n", $buf );

    if (($flags >> 15) == 1) {
        print "Script     : ";
        hexdump($buf, 1, 4, "");
    } else {
        printf ("AreInvalid : %d\n", ($flags >> 15) & 1);
        printf ("unknown bit: %d\n", ($flags >> 14) & 1);
        printf ("unknown bit: %d\n", ($flags >> 13) & 1);
        printf ("unknown bit: %d\n", ($flags >> 12) & 1);
        printf ("unknown bit: %d\n", ($flags >> 11) & 1);
        printf ("unknown bit: %d\n", ($flags >> 10) & 1);
        printf ("unknown bit: %d\n", ($flags >>  9) & 1);
    }

    printf ("CustomBadge: %d\n", ($flags >>  8) & 1);
    printf ("ObjctIsBusy: %d\n", ($flags >>  7) & 1);
    printf ("unknown bit: %d\n", ($flags >>  6) & 1);
    printf ("unknown bit: %d\n", ($flags >>  5) & 1);
    printf ("unknown bit: %d\n", ($flags >>  4) & 1);
    printf ("unknown bit: %d\n", ($flags >>  3) & 1);
    printf ("RoutingInfo: %d\n", ($flags >>  2) & 1);
    printf ("unknown bit: %d\n", ($flags >>  1) & 1);
    printf ("unknown bit: %d\n", ($flags >>  0) & 1);

}

sub eadump {

    print "\n";
    print "-EA--------:\n";

    read(INFILE,$buf,2);
    print "pad        : ";
    hexdump($buf, 2, 4, "");

    read(INFILE,$buf,4);
    print "magic      : ";
    hexdump($buf, 4, 4, "");

    read(INFILE,$buf,4);
    $ea_debug_tag = unpack("N", $buf );
    printf("debug_tag  : %08X", $ea_debug_tag);
    printf(" : %d\n", $ea_debug_tag);

    read(INFILE,$buf,4);
    $ea_total_size = unpack("N", $buf );
    printf("total_size : %08X", $ea_total_size);
    printf(" : %d\n", $ea_total_size);

    read(INFILE,$buf,4);
    $ea_data_start = unpack("N", $buf );
    printf("data_start : %08X", $ea_data_start);
    printf(" : %d\n", $ea_data_start);

    read(INFILE,$buf,4);
    $ea_data_length = unpack("N", $buf );
    printf("data_length: %08X", $ea_data_length);
    printf(" : %d\n", $ea_data_length);

    read(INFILE,$buf,4);
    print "reserved[0]: ";
    hexdump($buf, 4, 4, "");

    read(INFILE,$buf,4);
    print "reserved[1]: ";
    hexdump($buf, 4, 4, "");

    read(INFILE,$buf,4);
    print "reserved[2]: ";
    hexdump($buf, 4, 4, "");

    read(INFILE,$buf,2);
    print "flags      : ";
    hexdump($buf, 2, 4, "");

    read(INFILE,$buf,2);
    $ea_num_attrs = unpack("n", $buf );
    printf("num_attrs  : %04X", $ea_num_attrs);
    printf("     : %d\n", $ea_num_attrs);

    $pos = tell(INFILE);

    for ($i = 0 ; $i < $ea_num_attrs ; $i++) {

        $pos = (($pos & 0x3) == 0) ? ($pos) : ((($pos >> 2) + 1) << 2);
        seek(INFILE, $pos, 0);

        print "-EA ENTRY--:\n";

        read(INFILE,$buf,4);
        $ea_offset = unpack("N", $buf );
        printf("offset     : %08X", $ea_offset);
        printf(" : %d\n", $ea_offset);

        read(INFILE,$buf,4);
        $ea_length = unpack("N", $buf );
        printf("length     : %08X", $ea_length);
        printf(" : %d\n", $ea_length);

        read(INFILE,$buf,2);
        print "flags      : ";
        hexdump($buf, 2, 4, "");

        read(INFILE,$buf,1);
        $ea_namelen = unpack("C", $buf );
        printf("namelen    : %02X", $ea_namelen);
        printf("       : %d\n", $ea_namelen);

        $ea_namequo = $ea_namelen >> 4;
        $ea_namerem = $ea_namelen & 0xF;
        print "-EA NAME---:  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F : (ASCII)\n";
        rawdump($ea_namequo, $ea_namerem);

        $pos = tell(INFILE);

        seek(INFILE, $ea_offset, 0);
        $ea_quo = $ea_length >> 4;
        $ea_rem = $ea_length & 0xF;
        print "-EA VALUE--:  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F : (ASCII)\n";
        rawdump($ea_quo, $ea_rem);
    }
}

sub bedump  {
    my ($ofst, $len) = @_;
    my ($i);
    my ($value);

    seek(INFILE, $ofst, 0);

    printf("%2dbit-BE   : ", $len * 8 );

    $value = 0;
    for ( $i=0 ; $i < $len ; $i++ ) {
        read(INFILE,$buf,1);
        $bytedata[$i] = unpack("C", $buf );
          $value += $bytedata[$i] << (($len - $i -1) * 8) ;
    }

    for ( $i=0 ; $i < $len ; $i++ ) {
        printf("%02X", $bytedata[$i]);
    }

    printf(" : %s", $value);
    print "\n";
}

sub ledump  {
    my ($ofst, $len) = @_;
    my ($i);
    my ($value);

    seek(INFILE, $ofst, 0);

    printf("%2dbit-LE   : ", $len * 8 );

    $value = 0;
    for ( $i=0 ; $i < $len ; $i++ ) {
        read(INFILE,$buf,1);
        $bytedata[$len - $i - 1] = unpack("C", $buf );
          $value += $bytedata[$len - $i -1] << ($i * 8) ;
    }

    for ( $i=0 ; $i < $len ; $i++ ) {
        printf("%02X", $bytedata[$i]);
    }

    printf(" : %s", $value);
    print "\n";
}

sub rawdump {
    my ($quo, $rem) = @_;
    my ($addrs, $line, $buf);

    $addrs = 0;
    for ( $line = 0 ; $line < $quo ; $line++) {
        read(INFILE, $buf, 16);
        printf ( "%08X   :", $addrs);
        hexdump($buf, 16, 16, " ");
        $addrs = $addrs + 0x10;
    }
    if ( $rem != 0 ) {
        read(INFILE, $buf, $rem);
        printf ( "%08X   :", $addrs);
        hexdump($buf, $rem, 16, " ");
    }
}

sub hexdump {
    my ($buf, $len, $col, $delimit) = @_;
    my ($i);

    $hexstr = "";
    $ascstr = "";

    for ( $i=0 ; $i < $len ; $i++ ) {
        $val = substr($buf, $i, 1);
        $ascval = ord($val);
        $hexstr .= sprintf("%s%02X", $delimit, $ascval);

        if (($ascval < 32) || (  $ascval > 126 )) {
            $val = ".";
        }
        $ascstr .= $val;
    }
    for ( ; $i < $col ; $i++) {
        $hexstr .= "  ".$delimit;
        $ascstr .= " ";
    }

    printf("%s : %s", $hexstr,$ascstr);

    print "\n";
}

sub checkea {
    my ($file) = @_;

    if ( $eacommand == 1 ) {
        open2(\*EALIST, \*EAIN, 'getfattr', $file) or die $@;
        while(<EALIST>) {
            if ( $_ eq "user.org.netatalk.Metadata\n" ) {
                close (EALIST, EAIN);
                return 1;
            }
        }
        close (EALIST, EAIN);
        return 0;
    } elsif ( $eacommand == 2 ) {
        open2(\*EALIST, \*EAIN, 'attr', '-q', '-l', $file) or die $@;
        while(<EALIST>) {
            if ( $_ eq "org.netatalk.Metadata\n" ) {
                close (EALIST, EAIN);
                return 1;
            }
        }
        close (EALIST, EAIN);
        return 0;
    } elsif ( $eacommand == 3 ) {
        open2(\*EALIST, \*EAIN, 'runat', $file, 'ls', '-1') or die $@;
        while(<EALIST>) {
            if ( $_ eq "org.netatalk.Metadata\n" ) {
                close (EALIST, EAIN);
                return 1;
            }
        }
        close (EALIST, EAIN);
        return 0;
    } elsif ( $eacommand == 4 ) {
        open2(\*EALIST, \*EAIN, 'lsextattr', '-q', 'user', $file) or die $@;
        while(<EALIST>) {
            $_ = "\t".$_;
            if ( $_ =~ /\torg\.netatalk\.Metadata[\n\t]/ ) {
                close (EALIST, EAIN);
                return 1;
            }
        }
        close (EALIST, EAIN);
        return 0;
    } else {
        return 0;
    }
}

sub eaopenfile {
    my ($file) = @_;
    my @eacommands = ();

    if ( $eacommand == 1 ) {
        @eacommands = ('getfattr', '--only-values', '-n', 'user.org.netatalk.Metadata', $file);
    } elsif ( $eacommand == 2 ) {
        @eacommands = ('attr', '-q', '-g', 'org.netatalk.Metadata', $file);
    } elsif ( $eacommand == 3 ) {
        @eacommands = ('runat', $file, 'cat', 'org.netatalk.Metadata',);
    } elsif ( $eacommand == 4 ) {
        @eacommands = ('getextattr', '-q', 'user', 'org.netatalk.Metadata', $file);
    } else {
        return "";
    }

    my ($eatempfh, $eatempfile) = tempfile(UNLINK => 1);
    open2(my $ealist, my $eain, @eacommands) or die $@;
    print $eatempfh $_ while(<$ealist>);
    close($ealist, $eain);
    close($eatempfh);
    return $eatempfile;
}

#EOF
