#!/usr/bin/perl -w
# (c) Copyright 2004-2008. CodeWeavers, Inc.
use strict;

my $MOUNT_POINT="/mnt/ipod";


# Portable which(1) implementation
sub cxwhich($$;$)
{
    my ($dirs, $app, $noexec)=@_;
    if ($app =~ /^\//)
    {
        return $app if ((-x $app or $noexec) and -f $app);
    }
    elsif ($app =~ /\//)
    {
        require Cwd;
        my $path=Cwd::cwd() . "/$app";
        return $path if ((-x $path or $noexec) and -f $path);
    }
    else
    {
        foreach my $dir (split /:/, $dirs)
        {
            return "$dir/$app" if ($dir ne "" and (-x "$dir/$app" or $noexec) and -f "$dir/$app");
        }
    }
    return undef;
}

# Fast dirname() implementation
sub _cxdirname($)
{
    my ($path)=@_;
    return undef if (!defined $path);
    return "." if ($path !~ s!/+[^/]+/*$!!s);
    return "/" if ($path eq "");
    return $path;
}

# Locate where CrossOver is installed by looking for the directory
# where this this script is located, unwinding symlinks on the way
sub locate_cx_root()
{
    if (!defined $ENV{CX_ROOT})
    {
        my $argv0=cxwhich($ENV{PATH},$0);
        $argv0=$0 if (!defined $argv0);
        if ($argv0 !~ m+^/+)
        {
            require Cwd;
            $argv0=Cwd::cwd() . "/$argv0";
        }
        my $dir=_cxdirname($argv0);
        while (!-x "$dir/cxmenu" or !-f "$dir/cxmenu")
        {
            last if (!-l $argv0);
            $argv0=readlink($argv0);
            $argv0="$dir/$argv0" if ($argv0 !~ m+^/+);
            $dir=_cxdirname($argv0);
        }
        $dir =~ s%(/\.)*$%%;
        $dir =~ s%(/\./(\./)*)%/%;
        $ENV{CX_ROOT}=_cxdirname($dir);
    }
    if (!-x "$ENV{CX_ROOT}/bin/cxmenu" or !-f "$ENV{CX_ROOT}/bin/cxmenu")
    {
        my $name0=$0;
        $name0 =~ s+^.*/++;
        print STDERR "$name0:error: could not find CrossOver in '$ENV{CX_ROOT}'\n";
        exit 1;
    }
    return $ENV{CX_ROOT};
}

BEGIN {
    unshift @INC, locate_cx_root() . "/lib/perl";
}
use CXLog;
use CXUtils;



#####
#
# iPod detection
#
#####

# ioctl definitions
my $TIMEOUT=60000;

my $SG_IO=0x2285;
my $SG_DXFER_FROM_DEV=-3;
my $SENSE_BUFF_LEN=32;
my $INQUIRY_CMD=0x12;
my $INQUIRY_CMDLEN=6;

my $SCSI_IOCTL_GET_IDLUN=0x5382;

my $ipod_brand;
my $ipod_model;
my $ipod_revision;
my $ipod_sg_device;
my $ipod_sd_device;

# This function only requires that the executing user have
# read permissions to the sg device
sub is_sg_device_an_ipod($)
{
    my ($sgfd)=@_;

    my $resp=pack "a36", "RESP";
    my $sense_b=pack "a$SENSE_BUFF_LEN", "";
    my $inqCmdBlk=pack "c$INQUIRY_CMDLEN", $INQUIRY_CMD, 0, 0, 0, 36, 0;

    my $sg_io_hdr_t="iiCCSIpppIIipCCCCSSiII";
    my $sg_io_hdr=pack $sg_io_hdr_t,
                       83,                   # interface_id
                       $SG_DXFER_FROM_DEV,   # dxfer_direction
                       $INQUIRY_CMDLEN,      # cmd_len
                       $SENSE_BUFF_LEN,      # mx_sb_len
                       0,                    # iovec_count
                       36,                   # dxfer_len
                       $resp,                # dxferp
                       $inqCmdBlk,           # cmdp
                       $sense_b,             # sbp
                       $TIMEOUT,             # timeout
                       0,                    # flags
                       0,                    # pack_id
                       undef,                # usr_ptr
                       0,                    # status
                       0,                    # masked_status
                       0,                    # msg_status
                       0,                    # sb_len_wr
                       0,                    # host_status
                       0,                    # driver_status
                       0,                    # resid
                       0,                    # duration
                       0;                    # info
    #print "resp=[",join(",",(unpack "c*",$resp)),"]\n";
    #print "inqCmdBlk=[",join(",",(unpack "c*",$inqCmdBlk)),"]\n";
    #print "sg_io_hdr=[",join(",",(unpack "c*",$sg_io_hdr)),"]\n";

    my $rc=ioctl($sgfd,$SG_IO,$sg_io_hdr);
    if ($rc eq "0 but true")
    {
        my ($brand,$model,$rev)=(unpack "a8a8a16a4",$resp)[1,2,3];
        cxlog("  brand=[",($brand || "<undef>"),"]\n");
        cxlog("  model=[",($model || "<undef>"),"]\n");
        cxlog("  rev=[",($rev || "<undef>"),"]\n");
        if ($brand =~ /^Apple/i and $model =~ /^iPod/i)
        {
            $ipod_brand=$brand;
            $ipod_model=$model;
            $ipod_revision=$rev;
            return 1;
        }
    }
    else
    {
        cxlog("rc=$rc\n");
    }
    return 0;
}

sub get_scsi_id($)
{
    my ($devfd)=@_;

    my $scsi_idlun_t="ii";
    my $scsi_idlun=pack $scsi_idlun_t,
                        0, # four_in_one
                           # 4 separate bytes of info compacted into 1 int
                        0; # host_unique_id
                           # distinguishes adapter cards from same supplier
    my $rc=ioctl($devfd,$SCSI_IOCTL_GET_IDLUN,$scsi_idlun);
    if ($rc eq "0 but true")
    {
        my $four_in_one=(unpack $scsi_idlun_t, $scsi_idlun)[0];
        cxlog("four_in_one=[",($four_in_one || "<undef>"),"]\n");
        return $four_in_one;
    }
    else
    {
        cxlog("rc=$rc\n");
    }
    return 0;
}

sub find_corresponding_sd_device($)
{
    my ($id)=@_;
    if (opendir(my $dh, "/dev"))
    {
        foreach my $dentry (readdir $dh)
        {
            next if ($dentry !~ /^sd/);
            my $sddevice="/dev/$dentry";
            cxlog("Trying $sddevice\n");
            if (open(my $sdfd, "+>", $sddevice)
            {
                my $sdid=get_scsi_id($sdfd);
                close($sdfd);
                return $sddevice if ($sdid == $id);
            }
        }
        closedir($dh);
    }
    return undef;
}

my $error_printed;
sub ipod_scan($)
{
    if (defined $ipod_sg_device and defined $ipod_sd_device)
    {
        return 1;
    }
    if (!defined $ipod_brand)
    {
        if (opendir(my $dh, "/dev"))
        {
            foreach my $dentry (readdir $dh)
            {
                next if ($dentry !~ /^sg/);
                my $sgdevice="/dev/$dentry";
                cxlog("Trying $sgdevice\n");
                if (open(my $sgfd, "+>", $sgdevice))
                {
                    if (is_sg_device_an_ipod($sgfd))
                    {
                        my $sgid=get_scsi_id($sgfd);
                        if (!defined $sgid)
                        {
                            cxerr("unable to find the iPod's SCSI Id\n");
                            exit 1;
                        }

                        my $sddevice=find_corresponding_sd_device($sgid);
                        if (!defined $sddevice)
                        {
                            cxerr("unable to find the iPod's sd device\n");
                            exit 1;
                        }
                        $ipod_sg_device=$sgdevice;
                        $ipod_sd_device="${sddevice}2";
                        close($sgfd);
                        return 1;
                    }
                    close($sgfd);
                }
            }
            closedir($dh);
        }
    }
    if ($_[0] and !$error_printed)
    {
        cxerr("unable to find the iPod\n");
        $error_printed=1;
    }
    return 0;
}



#####
#
# The tasks
#
#####

sub do_ipod_scan()
{
    ipod_scan(0);
    if (defined $ipod_brand)
    {
        print "Found an iPod:\n";
        print "  Brand    = $ipod_brand\n";
        print "  Model    = $ipod_model\n";
        print "  Revision = $ipod_revision\n";
        print "  Device   = $ipod_sg_device\n";
        print "  Partition= $ipod_sd_device\n";
    }
    else
    {
        print "Found no iPod.\n";
    }
}

sub do_ipod_setup()
{
    return if (!ipod_scan(1));

    if (!chmod 0644,$ipod_sg_device)
    {
        cxerr("unable to set permissions on '$ipod_sg_device': $!\n");
    }
    cxlog("Setting permissions on partition 2 of the ipod\n");
    if (!chmod 0666,"$ipod_sd_device")
    {
        cxerr("unable to set permissions on '$ipod_sd_device': $!\n");
    }
}

sub do_mount($)
{
    my ($mount_point)=@_;
    return if (!ipod_scan(1));

    if (!-d $mount_point and !cxmkpath($mount_point))
    {
        cxerr("unable to create '$mount_point': $@\n");
        exit 1;
    }

    cxsystem("mount","-t","vfat","-o","sync","-o","umask=0","$ipod_sd_device","$mount_point");
}

sub do_unmount($)
{
    my ($mount_point)=@_;
    return if (!ipod_scan(1));

    if (!-d $mount_point)
    {
        cxerr("ipod mount point not '$mount_point': $!\n");
        exit 1;
    }

    cxsystem("umount","$mount_point");
}

sub do_wine_setup($)
{
    my ($mount_point)=@_;

    my $drive_exists;
    my $dh;
    if (!opendir($dh, $ENV{WINEPREFIX}))
    {
        cxerr("unable to find the DOS devices\n");
        exit 1;
    }
    foreach my $dentry (readdir $dh)
    {
        next if ($dentry !~ /^[a-z]:$/);
        my $target=readlink("$ENV{WINEPREFIX}/$dentry");
        if (defined $target and $target eq $mount_point)
        {
            $drive_exists=1;
            last;
        }
    }
    closedir($dh);

    if (!$drive_exists)
    {
        my $letter="d";
        while (1)
        {
            if (!-e "$ENV{WINEPREFIX}/dosdevices/$letter:")
            {
                if (!symlink($mount_point,"$ENV{WINEPREFIX}/dosdevices/$letter:"))
                {
                    cxerr("unable to create drive '$letter:': $!\n");
                    exit 1;
                }
                last;
            }
            if ($letter eq "z")
            {
                cxerr("found no available drive letter\n");
                exit 1;
            }
            $letter=chr(ord($letter)+1);
        }
    }

    my @regfile=("REGEDIT4\n",
                 "\n",
                 "[HKEY_CLASSES_ROOT\\Interface\\{4D56B972-8259-4833-9AB2-543962F5CF5B}\\ProxyStubClsid32]\n",
                 "@=\"{00020420-0000-0000-C000-000000000046}\"\n",
                 "\n",
                 "[HKEY_CLASSES_ROOT\\Interface\\{4D56B972-8259-4833-9AB2-543962F5CF5B}\\ProxyStubClsid]\n",
                 "@=\"{00020420-0000-0000-C000-000000000046}\"\n",
                 "[HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides]\n",
                 "\"iPodService.exe\"=\"native,builtin\"\n",
                 "\"IPOD~XQQ.EXE\"=\"native,builtin\"\n"
                );
    my $cmd=shquote_string("$ENV{CX_ROOT}/bin/regedit");
    if (open(my $regedit, "| $cmd -"))
    {
        print $regedit @regfile;
        close($regedit);
    }
    else
    {
        cxerr("unable to run regedit: $!\n");
        exit 1;
    }
}



#####
#
# Main
#
#####

# Process command-line options
my $opt_ipod_scan;
my $opt_ipod_setup;
my $opt_mount;
my $opt_umount;
my $opt_mount_point=$MOUNT_POINT;
my $opt_wine_setup;
my $opt_all;
my $opt_root;
my $opt_user;
my $opt_bottle=$ENV{CX_BOTTLE} || "default";
my $opt_scope;
my $opt_verbose;
my $opt_help;
require CXOpts;
my $cxopts=CXOpts->new();
$cxopts->add_options(["ipod-scan"     => \$opt_ipod_scan,
                      "ipod-setup"    => \$opt_ipod_setup,
                      "mount"         => \$opt_mount,
                      "umount"        => \$opt_umount,
                      "mount-point=s" => \$opt_mount_point,
                      "wine-setup"    => \$opt_wine_setup,
                      "all"           => \$opt_all,
                      "root"          => \$opt_root,
                      "user"          => \$opt_user,
                      "bottle=s"      => \$opt_bottle,
                      "scope=s"       => \$opt_scope,
                      "verbose!"      => \$opt_verbose,
                      "?|h|help"      => \$opt_help
                     ]);
my $err=$cxopts->parse();
CXLog::fdopen(2) if ($opt_verbose);
$ENV{CX_BOTTLE}=$opt_bottle;


# Validate the command line options
my $usage;
if ($err)
{
    cxerr("$err\n");
    $usage=2;
}
elsif ($opt_help)
{
    $usage=0;
}
else
{
    if ($opt_all)
    {
        $opt_ipod_setup=1;
        $opt_mount=1;
        $opt_wine_setup=1;
    }
    if ($opt_root)
    {
        $opt_ipod_setup=1;
        $opt_mount=1;
    }
    if ($opt_user)
    {
        $opt_wine_setup=1;
    }
    $opt_ipod_scan=1 if (!$opt_ipod_setup and !$opt_umount and !$opt_mount and !$opt_wine_setup);
    if (defined $opt_scope)
    {
        $opt_scope=~ tr/A-Z/a-z/;
        if ($opt_scope !~ /^(managed|private)$/)
        {
            cxerr("unknown scope value '$opt_scope'\n");
            $usage=2;
        }
    }
}

# Print usage
if (defined $usage)
{
    my $name0=cxname0();
    if ($usage)
    {
        cxerr("try '$name0 --help' for more information\n");
        exit $usage;
    }
    print "Usage: $name0 [--bottle BOTTLE] [--scope SCOPE] [--ipod-scan] [--ipod-setup]\n";
    print "              [--mount] [--umount] [--mount-point DIR] [--wine-setup]\n";
    print "              [--all] [--root] [--user] [--verbose] [--help]\n";

    print "\n";
    print "Helps configuring the iPod for operation with iTunes in CrossOver.\n";

    print "\n";
    print "Options:\n";
    print "  --bottle BOTTLE Use the specified bottle. If this option is not used,\n";
    print "               fallback to \$CX_BOTTLE and then to 'default'\n";
    print "  --scope SCOPE If set to managed, the bottle will be looked up in the\n";
    print "               system-wide bottle locations, otherwise it will refer to a\n";
    print "               private bottle\n";
    print "  --ipod-scan  Scan the system for iPod devices and print out information on\n";
    print "               them. Does no configuration\n";
    print "  --ipod-setup Looks for a USB iPod device and corrects its permissions\n";
    print "               Requires proper permissions to the device. Best run as root\n";
    print "  --mount      Mount the ipod device\n";
    print "               Requires proper permissions. Best run as root\n";
    print "  --umount     Unmount the iPod device\n";
    print "               Requires proper permissions. Best run as root\n";
    print "  --mount-point DIR Use the specified Unix mount point for the iPod\n";
    print "  --wine-setup Updates the Wine registry and drives to register the iPod\n";
    print "               Must be run as the user who will be using CrossOver\n";
    print "  --all        Equivalent to '--ipod-setup --mount --wine-setup'\n";
    print "  --root       Equivalent to '--ipod-setup --mount'. Best run as root\n";
    print "  --user       Equivalent to '--wine-setup'. Best run as the user who will be\n";
    print "               using CrossOver\n";
    print "  --verbose    Output more information about what is going on\n";
    print "  --help, -h   Shows this help message\n";
    exit 0;
}


# Do some real work
do_ipod_scan() if ($opt_ipod_scan);
do_ipod_setup() if ($opt_ipod_setup);
do_mount($opt_mount_point) if ($opt_mount);
do_unmount($opt_mount_point) if ($opt_umount);
if ($opt_wine_setup)
{
    require CXBottle;
    CXBottle::get_crossover_config();

    my ($scope)=CXBottle::setup_bottle_wineprefix($opt_scope);
    if (!defined $scope)
    {
        cxerr("cannot use the '$ENV{CX_BOTTLE}' bottle: $@\n");
        $usage=1;
    }

    do_wine_setup($opt_mount_point);
}
