Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
crossover / opt / cxoffice / bin / cxbottle
Size: Mime:
#!/usr/bin/perl
# (c) Copyright 2005-2018. CodeWeavers, Inc.
use warnings;
use strict;

# 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 the cxmenu script is located, unwinding symlinks on the way
sub locate_cx_root(;$)
{
    my ($fallback)=@_;
    my $argv0=cxwhich($ENV{PATH},$0);
    $argv0=$0 if (!defined $argv0);
    if ($argv0 !~ m+^/+)
    {
        require Cwd;
        $argv0=Cwd::cwd() . "/$argv0";
    }
    my $dir=_cxdirname($argv0);
    my $bindir=$dir;
    $bindir =~ s%/lib$%/bin%;
    while (!-x "$bindir/cxmenu" or !-f "$bindir/cxmenu")
    {
        last if (!-l $argv0);
        $argv0=readlink($argv0);
        $argv0="$dir/$argv0" if ($argv0 !~ m+^/+);
        $dir=_cxdirname($argv0);
        $bindir=$dir;
        $bindir =~ s%/lib$%/bin%;
    }
    $bindir =~ s%/(?:\./)+%/%g;
    $bindir =~ s%/\.$%%;
    $ENV{CX_ROOT}=_cxdirname($bindir);
    if ((!-x "$ENV{CX_ROOT}/bin/cxmenu" or !-f "$ENV{CX_ROOT}/bin/cxmenu") and
        $fallback)
    {
        $ENV{CX_ROOT}=$fallback;
    }
    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;


# Packaging helpers
my $package_version="18.5.0";

{
    package CXBDebian;
    use CXDebian;
    use base "CXDebian";

    sub new($$$)
    {
        my ($class, $buildroot, $installroot)=@_;
        my $self=CXDebian::new($class, $buildroot);
        $self->{installroot}=$installroot if ($self);
        return $self;
    }

    sub map_directory($$$)
    {
        my ($self, $root, $dir)=@_;
        return undef if ($dir eq "desktopdata");
        return "$self->{installroot}/$dir";
    }

    sub map_file($$$)
    {
        my ($self, $root, $file)=@_;
        return undef if ($file eq "files");
        return undef if ($self->{hardcode} and $file eq "cxbottle.conf");
        return $file if ($file =~ m%^/%);
        return "$self->{installroot}/$file";
    }
}

{
    package CXBRPM;
    use CXRPM;
    use base "CXRPM";

    sub map_directory($$$$)
    {
        my ($self, $root, $dir, $mode)=@_;
        $mode|=0055;

        return undef if ($dir eq "desktopdata");
        return ($dir, $mode);
    }

    sub map_file($$$$)
    {
        my ($self, $root, $file, $mode)=@_;

        # Setuid and setgid don't make sense for bottle files and
        # are dangerous. So remove them. Allow the sticky bit though.
        $mode=($mode & 01777) | ($mode & 0100 ? 0055 : 0044);

        return undef if ($file eq "files");
        return ($file, $mode);
    }
}

{
    package CXBSunpkg;
    use CXSunpkg;
    use base "CXSunpkg";

    sub package_directory($$$$)
    {
        my ($self, $root, $dir, $mode)=@_;

        return undef if ($dir =~ /^(?:desktopdata|drive_c)$/);
        $mode|=0055;
        return $mode;
    }

    sub package_file($$$$)
    {
        my ($self, $root, $file, $mode)=@_;

        return undef if ($file eq "files");
        # Setuid and setgid don't make sense for bottle files and
        # are dangerous. So remove them. Allow the sticky bit though.
        $mode=($mode & 01777) | ($mode & 0100 ? 0055 : 0044);
        return $mode;
    }
}

sub get_tmpdir(;$)
{
    my ($tmpdir)=@_;
    foreach my $dir ($tmpdir, $ENV{TMPDIR})
    {
        return $dir if (defined $dir and -d $dir and -w _);
    }
    return "/tmp";
}


# Process command-line options
my $opt_create;
my $opt_description;
my $opt_param;
my $opt_delete;
my $opt_install;
my $opt_uninstall;
my $opt_removeall;
my $opt_ro_desktopdata;
my $opt_tar;
my $opt_cpio;
my $opt_deb;
my $opt_rpm;
my $opt_sunpkg;
my $opt_sunzip;
my $opt_release;
my $opt_hardcode;
my $opt_packager;
my $opt_productid;
my $opt_rename;
my $opt_restore;
my $opt_restored;
my $opt_set_timestamps;
my $opt_new_uuid;
my $opt_set_uuid;
my $opt_get_uuid;
my $opt_copy;
my $opt_status;
my $opt_default;
my $opt_undefault;
my $opt_utf8;
my $opt_bottle;
my $opt_pattern;
my $opt_template;
my $opt_scope;
my $opt_force;
my $opt_verbose;
my $opt_help;
require CXOpts;
my $cxopts=CXOpts->new();
$cxopts->add_options(["create"        => \$opt_create,
                      "delete"        => \$opt_delete,
                      "install"       => \$opt_install,
                      "uninstall"     => \$opt_uninstall,
                      "removeall"     => \$opt_removeall,
                      "ro-desktopdata"=> \$opt_ro_desktopdata,
                      "tar=s"         => \$opt_tar,
                      "cpio=s"        => \$opt_cpio,
                      "deb"           => \$opt_deb,
                      "rpm"           => \$opt_rpm,
                      "sunpkg"        => \$opt_sunpkg,
                      "sunzip=s"      => \$opt_sunzip,
                      "release=s"     => \$opt_release,
                      "hardcode"      => \$opt_hardcode,
                      "packager=s"    => \$opt_packager,
                      "productid=s"   => \$opt_productid,
                      "rename=s"      => \$opt_rename,
                      "restore=s"     => \$opt_restore,
                      "restored"      => \$opt_restored,
                      "set-timestamps"=> \$opt_set_timestamps,
                      "new-uuid"      => \$opt_new_uuid,
                      "set-uuid=s"    => \$opt_set_uuid,
                      "get-uuid"      => \$opt_get_uuid,
                      "copy=s"        => \$opt_copy,
                      "status"        => \$opt_status,
                      "default"       => \$opt_default,
                      "undefault"     => \$opt_undefault,
                      "description=s" => \$opt_description,
                      "param=s@"      => \$opt_param,
                      "bottle=s"      => \$opt_bottle,
                      "pattern=s"     => \$opt_pattern,
                      "template=s"    => \$opt_template,
                      "scope=s"       => \$opt_scope,
                      "force"         => \$opt_force,
                      "verbose!"      => \$opt_verbose,
                      "?|h|help"      => \$opt_help
                     ]);
my $err=$cxopts->parse();
CXLog::fdopen(2) if ($opt_verbose);


# Validate the command line options
my $usage;
my $template_dir;
my ($only_removeall, $relax_wineprefix);
my $no_update;
if ($err)
{
    cxerr("$err\n");
    $usage=2;
}
elsif ($opt_help)
{
    $usage=0;
}
else
{
    if (!$opt_utf8)
    {
        # Re-encode the parameters to UTF-8
        require CXRecode;
        $opt_description=CXRecode::from_sys("UTF-8", $opt_description);
    }

    my $cmd_count=0;
    $cmd_count++ if (defined $opt_create);
    $cmd_count++ if (defined $opt_restore);
    $cmd_count++ if (defined $opt_restored);
    $cmd_count++ if (defined $opt_copy);
    $cmd_count++ if (defined $opt_status);
    if ($cmd_count > 1)
    {
        cxerr("--create, --restore, --copy, --restored and --status are mutually incompatible\n");
        $usage=2;
    }
    $cmd_count++ if (defined $opt_delete);
    $cmd_count++ if (defined $opt_removeall);
    $cmd_count++ if (defined $opt_set_timestamps);

    my $cmd_count_all=$cmd_count;
    $cmd_count_all++ if (defined $opt_new_uuid);
    $cmd_count_all++ if (defined $opt_set_uuid);
    $cmd_count_all++ if (defined $opt_get_uuid);
    $cmd_count_all++ if (defined $opt_install);
    $cmd_count_all++ if (defined $opt_uninstall);
    my $archive_count=0;
    $archive_count++ if (defined $opt_tar);
    $archive_count++ if (defined $opt_cpio);
    $archive_count++ if (defined $opt_deb);
    $archive_count++ if (defined $opt_rpm);
    $archive_count++ if (defined $opt_sunpkg);
    $cmd_count_all+=$archive_count;
    $cmd_count_all++ if (defined $opt_description);
    $cmd_count_all++ if (defined $opt_default);
    $cmd_count_all++ if (defined $opt_undefault);
    if ($opt_status and $cmd_count_all > 1)
    {
        cxerr("--status is incompatible with all other commands\n");
        $usage=2;
    }

    if ($opt_undefault and $cmd_count_all > 1)
    {
        cxerr("--undefault is incompatible with all other commands\n");
        $usage=2;
    }

    $only_removeall=1 if ($cmd_count_all == 1 and defined $opt_removeall);
    $relax_wineprefix=1 if ($cmd_count_all == $archive_count or $opt_status or $opt_get_uuid);
    if ($cmd_count_all == 0)
    {
        cxerr("you must specify the operation to perform\n");
        $usage=2;
    }
    elsif ($cmd_count_all == $cmd_count)
    {
        $no_update=1;
    }
    if (defined $opt_set_uuid)
    {
        if (defined $opt_new_uuid)
        {
            cxerr("--new-uuid and --set-uuid are mutually incompatible\n");
            $usage=2;
        }
        if ($opt_set_uuid !~ /^[0-9A-Za-z-]{2,}$/)
        {
            cxerr("the specified unique id is not valid\n");
            $usage=2;
        }
    }
    if (defined $opt_ro_desktopdata and !defined $opt_install and
        !defined $opt_uninstall and !defined $opt_removeall)
    {
        cxerr("--ro-desktopdata must be used with one of --install, --uninstall or --removeall\n");
        $usage=2;
    }
    if (defined $opt_pattern)
    {
        if (!defined $opt_removeall)
        {
            cxerr("--pattern can only be used with --removeall\n");
            $usage=2;
        }
        if (length($opt_pattern) < 4)
        {
            cxerr("the pattern must be at least 4 characters long\n");
            $usage=2;
        }
    }

    $ENV{CX_BOTTLE}=$opt_bottle if (defined $opt_bottle);
}

if (!defined $usage)
{
    if ($opt_create)
    {
        if (!defined $ENV{CX_BOTTLE})
        {
            cxerr("you must specify a bottle name for the --create option\n");
            $usage=2;
        }

        $opt_template="win98" if (!defined $opt_template);
        if ($opt_template =~ m%/%)
        {
            $template_dir=$opt_template;
        }
        else
        {
            $template_dir="$ENV{CX_ROOT}/share/crossover/bottle_templates/$opt_template";
        }
        if (!-d $template_dir)
        {
            cxerr("unable to find the '$opt_template' template\n");
            $usage=1;
        }
    }
    else
    {
        $ENV{CX_BOTTLE}="default" if (!defined $ENV{CX_BOTTLE} and !defined $opt_restore and !($only_removeall and defined $opt_pattern));
        if (defined $opt_template or defined $opt_param)
        {
            cxerr("--template and --param can only be used with --create\n");
            $usage=2;
        }
        if (defined $ENV{CX_BOTTLE} and defined $opt_pattern)
        {
            cxerr("--pattern cannot be used on a bottle\n");
            $usage=2;
        }
    }
    if (lc($ENV{CX_BOTTLE} || "") eq "default" and ($opt_create or defined $opt_restore))
    {
        cxerr("invalid bottle name '$ENV{CX_BOTTLE}'. Use the --default option instead\n");
        $usage=2;
    }

    if (!defined $opt_scope and ($opt_create or $opt_restore or defined $opt_copy))
    {
        $opt_scope="private";
    }
    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;
        }
    }

    if ($opt_deb or $opt_rpm or $opt_sunpkg)
    {
        $opt_release=1 if (($opt_release || "") eq "");
        $opt_packager=CXUtils::get_product_name() . " <info\@codeweavers.com>" if (!defined $opt_packager);
        if (defined $opt_productid)
        {
            my $res=CXUtils::check_product_id($opt_productid);
            if ($res)
            {
                cxerr("the specified product id is not valid: $res\n");
                $usage=2;
            }
        }
        else
        {
            $opt_productid=CXUtils::get_product_id();
            my $pkginfo=cxwhich($ENV{PATH}, "pkginfo");
            if (defined $pkginfo)
            {
                # On Solaris the productid may have been changed just because
                # we're not the first instance of this package. If so, use
                # the original productid (which happens to be the Solaris
                # package name).
                my $instance=cxbasename($ENV{CX_ROOT});
                if (cxsystem("pkginfo", "-q", $instance)==0)
                {
                    my $instanceid=$instance;
                    $instanceid =~ s/\.//;
                    if ($instanceid eq $opt_productid)
                    {
                        $opt_productid=$instance;
                        $opt_productid=~s/\.\d+$//;
                    }
                }
            }
        }
    }
    elsif (defined $opt_release or defined $opt_packager or defined $opt_productid or defined $opt_rename)
    {
        cxerr("the --release, --packager, --productid and --rename options can only be used with the --deb, --rpm or --sunpkg options\n");
        $usage=2;
    }
    if (defined $opt_hardcode and !defined $opt_deb)
    {
        cxerr("the --hardcode option can only be used with the --deb option\n");
        $usage=2;
    }
    if (defined $opt_sunzip)
    {
        if (!$opt_sunpkg)
        {
            cxerr("the --sunzip option can only be used with the --sunpkg option\n");
            $usage=2;
        }
        require CXSunpkg;
        if (!CXSunpkg::is_valid_zip_format($opt_sunzip))
        {
            cxerr("unknown compression format '$opt_sunzip' for the --sunzip option\n");
            $usage=2;
        }
    }
    $opt_sunzip||="bzip2";
    if (($only_removeall or $opt_delete) and defined $ENV{RPM_INSTALL_PREFIX})
    {
        # This is an awful kludge to make it possible to upgrade the buggy
        # 5.0.0 and 5.0.1 bottle RPM packages
        my $cmd="cat /proc/" . getppid() . "/cmdline 2>/dev/null";
        my @args=split /\0/, cxbackquote($cmd, 1);
        cxlog("parent args=[", join("|", @args), "]\n");
        if (@args == 3 and $args[1] =~ /rpm-tmp/ and $args[2] eq "1")
        {
            cxlog("called by a buggy pre or post uninstall RPM script -> quitting\n");
            exit 0;
        }
    }
    if ($opt_force and !$opt_delete)
    {
        cxerr("--force can only be used with --delete\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]\n";
    print "                [--create [create-options]] [--copy SOURCE] [--default]\n";
    print "                [--undefault]\n";
    print "                [[--deb] [--rpm] [--sunpkg [--sunzip FMT]] [packaging-options]]\n";
    print "                [--tar FILE] [--cpio FILE] [--restore ARCHIVE] [--restored]\n";
    print "                [--new-uuid|--set-uuid UUID] [--get-uuid]\n";
    print "                [--install] [--uninstall] [--removeall [--pattern pattern]]\n";
    print "                [--status] [--delete [--force]] [--help] [--verbose]\n";

    print "\n";
    print "Provides a command-line interface for managing the CrossOver bottles.\n";

    print "\n";
    print "Options:\n";
    print "  --bottle BOTTLE Operate on the specified bottle\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 "  --ro-desktopdata Treat the desktopdata / bottle directory as read-only\n";
    print "  --create        Creates a new bottle\n";
    print "    --description DESCRIPTION A description for the bottle\n";
    print "    --template TEMPLATE Identifies the type of bottle to create. The 'win98',\n";
    print "                    'win2000' and 'winxp' types create bottles that claim to\n";
    print "                    be Windows 98, 2000 and XP respectively\n";
    print "    --param PARAM   Additional parameters of the form 'NAME=VALUE' for the\n";
    print "                    bottle template or 'SECTION:KEY=VALUE' for the bottle\n";
    print "                    configuration\n";
    print "  --copy SOURCE   Makes a copy of the SOURCE bottle\n";
    print "  --default       Selects the bottle as the default bottle\n";
    print "  --undefault     Unsets the default bottle\n";
    print "  --deb           Creates a Debian package containing the specified bottle.\n";
    print "  --rpm           Creates an RPM package containing the specified bottle.\n";
    print "    --description DESCRIPTION A description for the Debian or RPM package\n";
    print "  --sunpkg        Creates a Solaris package containing the specified bottle.\n";
    print "    --sunzip FMT    The compression format to use. The default is bzip2, the\n";
    print "                    others are none, gzip and 7z\n";
    print "    --release RELEASE Specifies the bottle package's release number\n";
    print "    --packager PACKAGER Specifies the name and email address of the bottle\n";
    print "                    packager\n";
    print "    --productid PRODUCTID Specifies the product id of CrossOver on the target\n";
    print "                    system\n";
    print "  --tar FILE      Creates a tar archive of the specified bottle\n";
    print "                  If FILE ends in '.cxarchive', '.tgz' or '.gz' it will be\n";
    print "                  compressed with gzip. Similarly, it will be compressed with\n";
    print "                  bzip2 or compress if it ends in '.bz2' or '.Z' respectively\n";
    print "  --cpio FILE     Creates a cpio archive of the specified bottle\n";
    print "                  If FILE ends in '.cxarchive' or '.gz' it will be compressed\n";
    print "                  with gzip. Similarly, it will be compressed with bzip2 or\n";
    print "                  compress if it ends in '.bz2' or '.Z' respectively\n";
    print "  --restore ARCHIVE Restores the bottle contained in the specified archive\n";
    print "                  Note that this does not install the bottle. See --install\n";
    print "  --restored      Updates a freshly, manually restored bottle. This is an\n";
    print "                  advanced option and is unnecessary if --restore was used\n";
    print "  --new-uuid      Changes the bottle's unique identifier\n";
    print "  --set-uuid UUID Sets the bottle's unique identifier to UUID\n";
    print "  --get-uuid      Prints the bottle's unique identifier to stdout\n";
    print "  --install       Exports the bottle's menus, associations and plugins\n";
    print "                  so they can be used from the supported desktop environments\n";
    print "                  (e.g. GNOME, KDE) and web browsers\n";
    print "  --uninstall     Removes the bottle's known menus, associations and plugins\n";
    print "                  from the desktop environments and browsers\n";
    print "  --removeall     Each bottle has a unique id which is used to tag its menus,\n";
    print "                  associations and plugins. This option removes anything\n";
    print "                  bearing the bottle's unique id\n";
    print "    --pattern PATTERN Specifies that --removeall should delete anything that\n";
    print "                    bears an id matching the specified regular expression\n";
    print "  --status        Prints the bottle status on standard output\n";
    print "  --delete        Deletes the specified bottle, that is everything contained\n";
    print "                  in the bottle's directory, including its virtual c: drive\n";
    print "                  Note that this does not uninstall the bottle.\n";
    print "                  See also --uninstall and --removeall\n";
    print "    --force         If set, no confirmation is asked before deleting the bottle\n";
    print "  --verbose       Output more information about what is going on\n";
    print "  --help, -h      Shows this help message\n";
    exit 0;
}


# Determine and check wineprefix
require CXBottle;
my $cxconfig=CXBottle::get_crossover_config();
my $raw_wineprefix;

# Create / Restore / Copy bottles
my $bottle_lock;
sub create_and_lock_wineprefix($)
{
    my ($wineprefix)=@_;
    if (!cxmkpath($wineprefix))
    {
        cxerr("unable to create the '$wineprefix' directory: $@\n");
        exit 1;
    }
    $bottle_lock=CXBottle::lock_bottle($wineprefix);
}

sub is_wineserver_running($$)
{
    my ($bottle, $scope)=@_;
    return !cxsystem("$ENV{CX_ROOT}/bin/wine", "--no-lock",
                     "--bottle", $bottle, "--scope", $scope,
                     "--ux-app", "wineserver", "-k0");
}

sub lock_and_shutdown_bottle($$$$)
{
    my ($bottle, $scope, $wineprefix, $msg)=@_;

    # Lock the bottle to prevent new processes from starting
    my $lock=CXBottle::lock_bottle($wineprefix);

    # Stop all windows applications in this bottle. Note that there is nothing
    # we can do if the bottle does not belong to us.
    if (-o $wineprefix and is_wineserver_running($bottle, $scope))
    {
        my $pid=cxwait(1, "--no-focus", "--title", "Bottle shutdown", $msg);

        cxsystem("$ENV{CX_ROOT}/bin/wine", "--no-lock",
                 "--bottle", $bottle, "--scope", $scope,
                 "--wl-app", "wineboot.exe",
                 "--", "--end-session", "--shutdown", "--force", "--kill");

        my $stopped=5;
        while ($stopped)
        {
            sleep(1);
            last if (!is_wineserver_running($bottle, $scope));
            $stopped--;
        }
        kill(15, $pid) if (defined $pid);
        if (!$stopped)
        {
            # The bottle shutdown failed or has been canceled so unlock it
            CXUtils::cxunlock($lock);
            cxmessage("-title", "CrossOver Error",
                      "-buttons", "OK",
                      "-default", "OK",
                      "-image", "crossover",
                      "There are still applications running in the \%s bottle. Aborting the current operation.", $bottle);
            return undef;
        }
    }
    return $lock;
}

if ($opt_create)
{
    $raw_wineprefix=CXBottle::compute_new_wineprefix($ENV{CX_BOTTLE}, $opt_scope);
    if (!defined $raw_wineprefix)
    {
        cxerr("$@\n");
        exit 1;
    }
    if (-d $raw_wineprefix)
    {
        cxerr("the '$ENV{CX_BOTTLE}' bottle already exists\n");
        exit 1;
    }

    create_and_lock_wineprefix($raw_wineprefix);

    my @cmd;
    $ENV{WINEPREFIX}=CXUtils::cxrealpath($raw_wineprefix);
    # FIXME: We should pass $opt_template to setup because it may be a full path
    push @cmd, "$template_dir/setup", "--create";
    if ($opt_scope eq "managed")
    {
        push @cmd, "--updater", "wineprefixcreate";
        umask(umask() & ~0055) if ($> == 0);
    }
    if (defined $opt_description)
    {
        push @cmd, "--description", $opt_description;
        $opt_description=undef; # so we don't set it again
    }
    foreach my $param (@{$opt_param})
    {
        if ($param !~ /^[^:=]+:[^=]+=/)
        {
            push @cmd, "--param", $param;
        }
    }
    if (cxsystem(@cmd))
    {
        cxerr("unable to create the '$ENV{CX_BOTTLE}' bottle in '$raw_wineprefix'\n");
        CXUtils::cxunlock($bottle_lock);
        exit 1;
    }
    if (defined $opt_param)
    {
        require CXRWConfig;
        my $filename="$ENV{WINEPREFIX}/cxbottle.conf";
        my $cxbottle=CXRWConfig->new($filename);
        foreach my $param (@{$opt_param})
        {
	    if ($param =~ /^([^:=]+):([^=]+)=(.*)$/)
	    {
		my ($section, $key, $value) = ($1, $2, $3);
		$cxbottle->set($section, $key, $value);
	    }
        }
        cxwarn("unable to save '$filename': $!\n") if (!$cxbottle->save());
    }
}

elsif (defined $opt_restore)
{
    my $tmpdir;
    if (defined $ENV{CX_BOTTLE})
    {
        $raw_wineprefix=CXBottle::compute_new_wineprefix($ENV{CX_BOTTLE}, $opt_scope);
        if (!defined $raw_wineprefix)
        {
            cxerr("$@\n");
            exit 1;
        }
        $ENV{WINEPREFIX}=CXUtils::cxrealpath($raw_wineprefix);
        $tmpdir=cxdirname($ENV{WINEPREFIX}) ."/tmp-cxbottle-$$";
    }
    else
    {
        $tmpdir=CXBottle::compute_new_wineprefix("tmp-cxbottle-$$", $opt_scope);
        if (!defined $tmpdir)
        {
            cxerr("$@\n");
            exit 1;
        }
        $tmpdir=CXUtils::cxrealpath($tmpdir);
    }
    umask(umask() & ~0055) if ($opt_scope eq "managed" and $> == 0);

    # Create a temporary directory where we can safely extract the archive
    if (!cxmkpath($tmpdir, 0700))
    {
        cxerr("unable to create the '$tmpdir' temporary directory: $@\n");
        exit 1;
    }
    # Lock it in case the archive does not contain the leading directory
    $bottle_lock=CXBottle::lock_bottle($tmpdir);

    # Use a pseudo-loop to simplify error handling
    my $rc;
    while (1)
    {
        if ($opt_restore ne "-" and $opt_restore !~ m%^/%)
        {
            require Cwd;
            $opt_restore=Cwd::cwd() . "/$opt_restore";
        }
        my ($uncompress, $is_cpio, $capture_stderr);
        if ($opt_restore =~ /\.cxarchive$/)
        {
            if (open(my $fh, "<", $opt_restore))
            {
                my $head;
                read($fh, $head, 6);
                if ($head =~ /^\037\213/)
                {
                    $uncompress=[CXUtils::get_gzip(), "-d", "-c", $opt_restore];
                }
                elsif ($head =~ /^BZh/)
                {
                    $uncompress=[CXUtils::get_bzip2(), "-d", "-c", $opt_restore];
                }
                elsif ($head =~ /^7z\274\257\047\034/)
                {
                    $uncompress=["7za", "x", $opt_restore, "-so"];
                    # 7za pollutes stderr
                    $capture_stderr=1;
                }
                else
                {
                    $is_cpio=1 if ($head =~ /^07070[127]/);
                }
                close($fh);

                if ($uncompress)
                {
                    my $cmd=join(" ", (map { shquote_string($_) } @$uncompress));
                    $cmd .= " 2>/dev/null" if ($capture_stderr);
                    if (open(my $fh, "$cmd |"))
                    {
                        read($fh, $head, 6);
                        $is_cpio=1 if ($head =~ /^07070[127]/);
                        close($fh);
                    }
                }
            }
            else
            {
                # By default assume this is in gzip format
                $uncompress=[CXUtils::get_gzip(), "-d", "-c", $opt_restore];
            }
        }
        elsif ($opt_restore =~ /\.(?:gz|tgz)$/)
        {
            $uncompress=[CXUtils::get_gzip(), "-d", "-c", $opt_restore];
        }
        elsif ($opt_restore =~ /\.bz2$/)
        {
            $uncompress=[CXUtils::get_bzip2(), "-d", "-c", $opt_restore];
        }
        elsif ($opt_restore =~ /\.7z$/)
        {
            $uncompress=["7za", "x", $opt_restore, "-so"];
            # 7za pollutes stderr
            $capture_stderr=1;
        }
        elsif ($opt_restore =~ /\.Z$/)
        {
            $uncompress=["uncompress", "-c", $opt_restore];
        }

        my $cmd;
        if ($is_cpio or $opt_restore =~ /\.cpio(?:\.7z|\.bz2|\.gz|\.Z)?$/)
        {
            $cmd=["cpio", "-i", "-d", "-m"];
            # cpio pollutes stderr
            $capture_stderr=1;
        }
        else
        {
            $cmd=[CXUtils::get_tar(), "xf", "-"];
        }
        $cmd=[["cd", $tmpdir], "&&", $cmd];
        $cmd=[$cmd, "2>"] if ($capture_stderr);

        if ($uncompress)
        {
            $cmd=[$uncompress, "|", $cmd];
        }
        elsif ($opt_restore ne "-")
        {
            $cmd=[$cmd, "<", $opt_restore];
        }

        my $shcmd=new_shell_command({cmd => $cmd, capture_output => 1});
        if ($shcmd->run())
        {
            print STDERR $shcmd->get_error_report();
            cxerr("unable to extract the bottle from '$opt_restore'\n");
            $rc=1;
            last;
        }

        my $dstdir=$ENV{WINEPREFIX};
        my $srcdir;
        if (-f "$tmpdir/cxbottle.conf")
        {
            # The archive did not contain any leading directory which is not
            # really standard. But we'll deal with it anyway...
            $srcdir=$tmpdir;
            chmod(0777 & ~umask(), $tmpdir);

            # Keep the lock on $tmpdir as it will become the wineprefix
            # This relies on the fact that bottle locks are independent of
            # the bottle name
        }
        else
        {
            my $count=0;
            if (opendir(my $dh, $tmpdir))
            {
                foreach my $dentry (readdir $dh)
                {
                    next if ($dentry =~ /^\.\.?$/);
                    $count++;
                    last if ($count > 1);
                    $srcdir="$tmpdir/$dentry";
                }
                closedir($dh);
            }
            if ($count != 1 or !-d $srcdir)
            {
                cxerr("'$opt_restore' does not contain a bottle\n");
                $rc=1;
                last;
            }
            if (!defined $dstdir)
            {
                $dstdir=cxdirname($tmpdir) . "/" . cxbasename($srcdir);
            }

            # Lock the directory that will become the wineprefix
            # This relies on the fact that bottle locks are independent of
            # the bottle name
            CXUtils::cxunlock($bottle_lock);
            $bottle_lock=CXBottle::lock_bottle($srcdir);
        }
        $dstdir=cxdirname($tmpdir) . "/win98" if (!defined $dstdir);
        if (-d $dstdir)
        {
            cxerr("'$dstdir' already exists\n");
            $rc=1;
            last;
        }
        if (!rename $srcdir, $dstdir)
        {
            cxerr("unable to move the bottle to '$dstdir': $!\n");
            $rc=1;
            last;
        }

        # Set CX_BOTTLE and WINEPREFIX to match this bottle
        $ENV{CX_BOTTLE}=cxbasename($dstdir) if (!defined $ENV{CX_BOTTLE});
        $ENV{WINEPREFIX}=$dstdir;

        if (cxbasename($srcdir) ne $ENV{CX_BOTTLE})
        {
            # The bottle is being renamed. So change the bottle id to
            # avoid trouble if the same archive is restored multiple times
            $opt_new_uuid=1;
        }
        $opt_restored=1;
        last;
    }

    # Cleanup
    if (-d $tmpdir)
    {
        require File::Path;
        if (!File::Path::rmtree($tmpdir))
        {
            cxwarn("unable to delete the '$tmpdir' directory: $!\n");
        }
    }

    if ($rc)
    {
        CXUtils::cxunlock($bottle_lock);
        exit $rc;
    }
}

elsif (defined $opt_copy)
{
    my $srcdir=CXBottle::find_bottle($opt_copy, "private");
    if (defined $srcdir)
    {
        require CXConfig;
        my $cxbottle=CXConfig->new("$srcdir/cxbottle.conf");
        my $mode=CXBottle::get_bottle_mode($cxbottle, "private");
        $srcdir=undef if (($mode || "managed") eq "stub");
    }
    $srcdir=CXBottle::find_bottle($opt_copy, "managed") if (!defined $srcdir);
    if (!defined $srcdir)
    {
        cxerr("unable to find the '$opt_copy' bottle\n");
        exit 1;
    }
    my $srclock;
    if (-o $srcdir)
    {
        my $msg=cxgettext("The applications in the \%s bottle must be shut down for the copy to proceed.", $opt_copy);
        $srclock=lock_and_shutdown_bottle($opt_copy, "private", $srcdir, $msg);
    }
    else
    {
        cxlog("not shutting down the source bottle processes because it does not belong to the current user\n");
    }


    $raw_wineprefix=CXBottle::compute_new_wineprefix($ENV{CX_BOTTLE}, $opt_scope);
    if (!defined $raw_wineprefix)
    {
        cxerr("$@\n");
        CXUtils::cxunlock($srclock);
        exit 1;
    }
    if (-e $raw_wineprefix)
    {
        cxerr("'$ENV{CX_BOTTLE}' already exists\n");
        CXUtils::cxunlock($srclock);
        exit 1;
    }
    umask(umask() & ~0055) if ($opt_scope eq "managed" and $> == 0);
    create_and_lock_wineprefix($raw_wineprefix);
    $ENV{WINEPREFIX}=CXUtils::cxrealpath($raw_wineprefix);

    my $exclude="$ENV{WINEPREFIX}.$$.exclude";
    my $fh;
    if (!open($fh, ">", $exclude))
    {
        cxerr("unable to open '$exclude' for writing: $!\n");
        CXUtils::cxunlock($bottle_lock);
        CXUtils::cxunlock($srclock);
        exit 1;
    }
    print $fh "/files\n";
    print $fh "/desktopdata\n";
    close($fh);

    my $tar=CXUtils::get_tar();
    my $cmd=[[["cd", $srcdir], "&&", [$tar, "cfX", "-", $exclude, "."]],
             "|",
             [["cd", $ENV{WINEPREFIX}], "&&", [$tar, "xf", "-"]]
            ];
    my $shcmd=new_shell_command({cmd => $cmd, capture_output => 1});
    my $failed=$shcmd->run();
    unlink $exclude;
    if ($failed)
    {
        CXUtils::cxunlock($srclock);
        print STDERR $shcmd->get_error_report();
        cxerr("unable to copy '$opt_copy' to '$ENV{CX_BOTTLE}'\n");
        require File::Path;
        if (!File::Path::rmtree($ENV{WINEPREFIX}))
        {
            cxwarn("unable to delete the '$ENV{WINEPREFIX}' directory: $!\n");
        }
        CXUtils::cxunlock($bottle_lock);
        exit 1;
    }

    # And unlock the source bottle
    if (defined $srclock and !CXUtils::cxunlock($srclock))
    {
        cxwarn("unable to release the source bottle lock\n");
    }

    $opt_new_uuid=1;
    $opt_restored=1;
}

elsif (defined $ENV{CX_BOTTLE})
{
    my $scope;
    ($scope, $raw_wineprefix)=CXBottle::setup_bottle_wineprefix($opt_scope);
    if (defined $scope and !defined $opt_scope and !defined $opt_status)
    {
        # Arbitrate between stub and managed bottles
        # if the scope was not specified explicitly
        ($scope, $raw_wineprefix)=CXBottle::bottle_stub2managed($scope, $raw_wineprefix);
    }
    if (defined $scope and !CXBottle::is_wineprefix_valid($ENV{WINEPREFIX}, $relax_wineprefix))
    {
        $scope=undef;
    }
    if (!defined $scope)
    {
        cxerr($@);
        exit 1;
    }
    $opt_scope=$scope;
    umask(umask() & ~0055) if ($opt_scope eq "managed" and $> == 0);
}

if ($opt_new_uuid)
{
    $opt_set_uuid=CXUtils::get_unique_id($ENV{WINEPREFIX});
}

if ($opt_set_timestamps)
{
    if (open(my $fh, "<", "$ENV{WINEPREFIX}/files"))
    {
        my $err;
        while (my $line=<$fh>)
        {
            next if ($line !~ /^(\d+) (.*)$/);
            my ($mtime, $filename)=($1, $2);

            # We may have timestamps for files that no longer exist
            # (typically files in Temp).
            next if (!-f "$ENV{WINEPREFIX}/$filename");

            if (!utime $mtime, $mtime, "$ENV{WINEPREFIX}/$filename")
            {
                cxerr("unable to restore the '$filename' timestamp: $!\n");
                $err=1;
            }
        }
        close($fh);
        exit(1) if ($err);
    }
    else
    {
        cxerr("unable to open '$ENV{WINEPREFIX}/files' for reading: $!\n");
        exit(1);
    }
}

if ($opt_restored)
{
    require CXRWConfig;
    my $filename="$ENV{WINEPREFIX}/cxbottle.conf";
    my $cxbottle=CXRWConfig->new($filename);
    my $settings=$cxbottle->append_section("Bottle");

    if ($settings->get("MenuMode", "") ne "frozen" and
        $settings->get("AssocMode", "") ne "frozen" and
        $settings->get("NSPluginMode", "") ne "frozen" and
        -d "$ENV{WINEPREFIX}/desktopdata")
    {
        # A freshly restored bottle is not installed and thus should not
        # have a desktopdata directory, unless some menus, associations or
        # plugins are frozen.
        require File::Path;
        File::Path::rmtree("$ENV{WINEPREFIX}/desktopdata");
    }
    CXBottle::fix_ownership_permissions($ENV{WINEPREFIX}, $opt_scope);

    # Check the bottle's character encoding
    my $system_encoding=CXUtils::get_system_encoding(1);
    my $bottle_encoding=$settings->get("Encoding", "ANSI_X3.4-1968");
    if ($bottle_encoding ne "ANSI_X3.4-1968" and
        $bottle_encoding ne $system_encoding)
    {
        cxwarn("The current character encoding ($system_encoding) may not " .
               "be compatible with the encoding of the bottle " .
               "($bottle_encoding). This may cause applications to not " .
               "find their files and thus lead to malfunctions.\n");
    }

    if (defined $opt_set_uuid)
    {
        $settings->set("BottleID", $opt_set_uuid);
        $opt_set_uuid=undef; # so we don't set it again
    }

    my $updater=expand_string($settings->get("Updater"));
    if ($opt_scope eq "private")
    {
        $settings->comment_out("Updater");
    }
    elsif (!$updater)
    {
        $updater="wineprefixcreate";
        $settings->set("Updater", $updater);
    }
    if (defined $opt_description)
    {
        $settings->set("Description", $opt_description);
        $opt_description=undef; # so we don't set it again
    }
    $settings->set("MenuMode", "ignore") if ($settings->get("MenuMode", "") ne "frozen");
    $settings->set("AssocMode", "ignore") if ($settings->get("AssocMode", "") ne "frozen");
    $settings->set("NSPluginMode", "ignore") if ($settings->get("NSPluginMode", "") ne "frozen");

    cxwarn("unable to save '$filename': $!\n") if (!$cxbottle->save());

    # 'files' is obsolete and must be rebuilt, possibly in the next step
    unlink "$ENV{WINEPREFIX}/files";

    # Update the special folder symbolic links
    # (this may also trigger a bottle update)
    cxsystem("$ENV{CX_ROOT}/bin/wine", "--scope", $opt_scope,
             "--dll", "shell32=b", "--wl-app", "rundll32.exe",
             "shell32.dll,wine_update_symbolic_links");

    CXBottle::run_bottle_hooks(["restore"]);

    if ($opt_scope eq "managed" and !-f "$ENV{WINEPREFIX}/files")
    {
        # and if it was already up to date, take a snapshot ourselves
        cxsystem("$ENV{CX_ROOT}/bin/wine", "--scope", $opt_scope,
                 "--ux-app", $updater, "--snapshot");
    }
}

if (defined $opt_set_uuid or defined $opt_description)
{
    # The --create, --copy and --restored (and thus --restore) operations
    # lock the bottle and handle the --set-uuid and --description options
    # themselves. So if we get get we know the bottle has not been locked yet.
    $bottle_lock=CXBottle::lock_bottle($ENV{WINEPREFIX});
    require CXRWConfig;
    my $filename="$ENV{WINEPREFIX}/cxbottle.conf";
    my $cxbottle=CXRWConfig->new($filename);
    $cxbottle->set("Bottle", "BottleID", $opt_set_uuid) if (defined $opt_set_uuid);
    $cxbottle->set("Bottle", "Description", $opt_description) if (defined $opt_description);
    if (!$cxbottle->save())
    {
        cxwarn("unable to save '$filename': $!\n");
    }
}

# The bottle is ready for use and we won't modify cxbottle.conf anymore.
# We may relock it later if needed.
CXUtils::cxunlock($bottle_lock);


# Now the bottle has been created / restored / copied, we can read its
# configuration file & co (note that there is no bottle in the
# --removeall --pattern case).
if (defined $ENV{WINEPREFIX})
{
    if (!-d $ENV{WINEPREFIX})
    {
        cxerr("unable to find the '$ENV{CX_BOTTLE}' bottle\n");
        exit 1;
    }
    # Read the bottle configuration file
    CXBottle::setup_bottle_environment($cxconfig, $ENV{WINEPREFIX});
    # Update / Upgrade the bottle before trying to use it
    if (!$no_update and !CXBottle::update_bottle($cxconfig, $ENV{WINEPREFIX}, $opt_scope))
    {
        cxerr("unable to update the '$ENV{CX_BOTTLE}' bottle\n");
        exit 1;
    }
}

if ($opt_status)
{
    my ($mode, $status)=CXBottle::get_bottle_status($cxconfig, $ENV{WINEPREFIX}, $opt_scope);
    if (!defined $mode)
    {
        cxerr("$@");
        exit 1;
    }
    print "Mode=$mode\n";
    print "Status=$status\n";
    exit 0;
}

if ($opt_default or $opt_undefault)
{
    # FIXME: This won't work if $CX_BOTTLE is an absolute path, not found
    #        inside $CX_BOTTLE_PATH.
    # FIXME: The default bottle should be a cxoffice.conf setting. Otherwise
    #        there will always be an ambiguity wrt. where to create the symlink
    my $default_link=cxdirname($raw_wineprefix) . "/default";
    my $base_wineprefix=cxbasename($raw_wineprefix);
    if ($opt_undefault and (readlink $default_link) ne $base_wineprefix)
    {
        cxwarn("'$ENV{CX_BOTTLE}' is not the default bottle\n");
    }
    elsif (-l $default_link and !unlink $default_link)
    {
        cxwarn("unable to delete '$default_link': $!\n");
    }
    elsif ($opt_default)
    {
        # Create a relative symbolic link
        cxlog("Symlinking '$default_link' to '$base_wineprefix'\n");
        if (!symlink($base_wineprefix, $default_link))
        {
            cxwarn("unable to create the 'default' bottle symlink: $!\n");
        }
    }
}

if ($opt_get_uuid)
{
    my $uuid=$cxconfig->get("Bottle", "BottleID", "");
    print "$uuid\n";
}

my $rc;
if ($opt_removeall or $opt_uninstall or $opt_install)
{
    my @args=();
    if ($opt_removeall)
    {
        push @args, "--removeall";
        push @args, "--pattern", $opt_pattern if (defined $opt_pattern);
    }
    if ($opt_uninstall)
    {
        push @args, "--uninstall";
    }
    if ($opt_install)
    {
        push @args, "--install";
    }
    if ($opt_ro_desktopdata)
    {
        push @args, "--ro-desktopdata";
    }
    push @args, "--scope", $opt_scope if (defined $opt_scope);
    if ($cxconfig->get("Bottle", "MenuMode", "") ne "frozen" and
        cxsystem("$ENV{CX_ROOT}/bin/cxmenu", @args))
    {
        cxerr("unable to install, uninstall or remove all the menus\n");
        $rc=1;
    }
    if ($cxconfig->get("Bottle", "AssocMode", "") ne "frozen" and
        cxsystem("$ENV{CX_ROOT}/bin/cxassoc", @args))
    {
        cxerr("unable to install, uninstall or remove all the associations\n");
        $rc=1;
    }
    if ($cxconfig->get("Bottle", "NSPluginMode", "") ne "frozen" and
        cxsystem("$ENV{CX_ROOT}/bin/cxnsplugin", @args))
    {
        cxerr("unable to install, uninstall or remove all the plugins\n");
        $rc=1;
    }
}


# Shutdown all processes in the bottle
if (defined $opt_tar or defined $opt_cpio or defined $opt_deb or
    defined $opt_rpm or defined $opt_sunpkg)
{
    my $msg=cxgettext("The applications in the \%s bottle must be shut down for the archiving or packaging operation to proceed.", $ENV{CX_BOTTLE});
    $bottle_lock=lock_and_shutdown_bottle($ENV{CX_BOTTLE}, $opt_scope, $ENV{WINEPREFIX}, $msg);
    exit(1) if (!$bottle_lock);
}

if (defined $opt_tar)
{
    my $exclude;
    # Use a pseudo-loop to simplify error handling
    while (1)
    {
        my $tmpdir=get_tmpdir(cxdirname($ENV{WINEPREFIX}));
        $exclude="$tmpdir/cxbottle.$$.exclude";
        my $fh;
        if (!open($fh, ">", $exclude))
        {
            cxerr("unable to open '$exclude' for writing: $!\n");
            $rc=1;
            last;
        }
        my $bottle=cxbasename($ENV{WINEPREFIX});
        print $fh "$bottle/files\n";
        print $fh "$bottle/desktopdata\n";
        close($fh);

        my $cmd=[ ["cd", cxdirname($ENV{WINEPREFIX})], "&&",
                  [CXUtils::get_tar(), "cfX", "-", $exclude, $bottle]
                ];
        if ($opt_tar =~ /\.(?:cxarchive|t?gz)$/)
        {
            $cmd=[$cmd, "|", [[CXUtils::get_gzip(), "-9"], ">", $opt_tar]];
        }
        elsif ($opt_tar =~ /\.bz2$/)
        {
            $cmd=[$cmd, "|", [[CXUtils::get_bzip2(), "-9"], ">", $opt_tar]];
        }
        elsif ($opt_tar =~ /\.7z$/)
        {
            # 7za pollutes stderr
            $cmd=[$cmd, "|", [["7za", "a", $opt_tar, "-mx=9", "-si"], "2>"]];
        }
        elsif ($opt_tar =~ /\.Z$/)
        {
            $cmd=[$cmd, "|", [["compress"], ">", $opt_tar]];
        }
        elsif ($opt_tar eq "-")
        {
            # Nothing to do, the tar output goes straight to stdout.
            # Note that capture_output must not be set when running the
            # command.
        }
        else
        {
            $cmd=[$cmd, ">", $opt_tar];
        }
        my $shcmd=new_shell_command({cmd => $cmd, tmpdir => $tmpdir});
        if ($shcmd->run())
        {
            print STDERR $shcmd->get_error_report();
            cxerr("unable to archive '$ENV{CX_BOTTLE}' from '$ENV{WINEPREFIX}'\n");
            $rc=1;
        }

        last;
    }
    unlink $exclude if (defined $exclude);
}

if (defined $opt_cpio)
{
    my $bottledir=cxdirname($ENV{WINEPREFIX});
    my $cmd=[["cd", $bottledir], "&&",
             [[CXUtils::get_cpio_o()], "2>"]]; # cpio pollutes stderr
    if ($opt_cpio =~ /\.(?:cxarchive|gz)$/)
    {
        $cmd=[$cmd, "|", [[CXUtils::get_gzip(), "-9"], ">", $opt_cpio]];
    }
    elsif ($opt_cpio =~ /\.bz2$/)
    {
        $cmd=[$cmd, "|", [[CXUtils::get_bzip2(), "-9"], ">", $opt_cpio]];
    }
    elsif ($opt_cpio =~ /\.7z$/)
    {
        # 7za pollutes stderr
        $cmd=[$cmd, "|", [["7za", "a", $opt_cpio, "-mx=9", "-si"], "2>"]];
    }
    elsif ($opt_cpio =~ /\.Z$/)
    {
        $cmd=[$cmd, "|", [["compress"], ">", $opt_cpio]];
    }
    elsif ($opt_cpio eq "-")
    {
        # Nothing to do, the cpio output goes straight to stdout.
        # Note that capture_output must not be set when running the
        # command.
    }
    else
    {
        $cmd=[$cmd, ">", $opt_cpio];
    }
    my $tmpdir=get_tmpdir(cxdirname($ENV{WINEPREFIX}));
    my $shcmd=new_shell_command({cmd => $cmd, tmpdir => $tmpdir});
    my $cmdline=$shcmd->get_command_line();
    cxlog("Running '$cmdline'\n");
    if (open(my $fh, "| $cmdline"))
    {
        my $bottle=cxbasename($ENV{WINEPREFIX});
        print $fh "$bottle\n";
        my @dirs=($bottle);
      error: while (@dirs)
        {
            my $dh;
            my $dir=shift @dirs;
            if (!opendir($dh, "$bottledir/$dir"))
            {
                cxerr("unable to open '$bottledir/$dir': $!\n");
                $rc=1;
                last;
            }
            foreach my $dentry (readdir $dh)
            {
                next if ($dentry =~ /^(?:\.\.?|files|desktopdata)$/);
                print $fh "$dir/$dentry\n";
                if (-l "$bottledir/$dir/$dentry")
                {
                    # Nothing to do
                }
                elsif (!-r _)
                {
                    cxerr("unable to read '$bottledir/$dir/$dentry'\n");
                    $rc=1;
                    closedir($dh);
                    last error;
                }
                elsif (-d _)
                {
                    push @dirs, "$dir/$dentry";
                }
            }
            closedir($dh);
        }
        if (!close($fh) or $shcmd->has_errors())
        {
            print STDERR $shcmd->get_output();
            cxerr("$cmdline returned $?\n");
            cxerr("unable to create a cpio archive from '$ENV{WINEPREFIX}'\n");
            $rc=1;
        }
    }
    else
    {
        cxerr("unable to create a cpio archive from '$ENV{WINEPREFIX}'\n");
        $rc=1;
    }
}

if (defined $opt_deb)
{
    # Use a pseudo-loop to simplify error handling
    my ($buildroot, $lrc);
  error: while (1)
    {
        if ($opt_hardcode)
        {
            my $mode=CXBottle::get_bottle_mode($cxconfig, $opt_scope);
            if ($mode ne "managed")
            {
                cxerr("--hardcode can only be used on managed bottles\n");
                $lrc=1;
                last error;
            }
        }
        my $bottle=$opt_rename || cxbasename($ENV{CX_BOTTLE});
        my $description;
        if (defined $opt_description)
        {
            $description=$opt_description;
        }
        else
        {
            $description="The $bottle CrossOver Bottle.\n";
            $description.=$cxconfig->get("Bottle", "Description", "");
        }
        $description =~ s/\n(?=\S)/\n /g;
        $description =~ s/\n\n/\n .\n/g;
        my $today=CXUtils::rfc822time(time);
        my $installroot="/opt/$opt_productid/support/$bottle";
        require CXDebian;
        my $package_name=($opt_productid eq CXUtils::get_builtin_product_id() ?
                         "cxbottle" :
                         "$opt_productid-bottle");
        $package_name=CXDebian::compute_package_name("$package_name-$bottle");

        my $tmpdir=get_tmpdir(cxdirname($ENV{WINEPREFIX}));
        $buildroot="$tmpdir/cxbottle.$$.buildroot";
        my $cxdebian=CXBDebian->new($buildroot, $installroot);
        if (!$cxdebian)
        {
            $lrc=1;
            last error;
        }
        $cxdebian->{hardcode}=$opt_hardcode;

        if (!$cxdebian->add_tree($ENV{WINEPREFIX}))
        {
            $lrc=1;
            last error;
        }
        if ($opt_hardcode)
        {
            # Grab the extra files we need to package
            my @tools=("cxmenu", "cxassoc");
            push @tools, "cxnsplugin";
            my $bottle_id=$cxconfig->get("Bottle", "BottleID");
            if (!defined $bottle_id)
            {
                cxerr("the bottle must have a unique id for --hardcode\n");
                $lrc=1;
                last error;
            }
            foreach my $tool (@tools)
            {
                my $start=CXLog::cxtime();
                my $cmd=shquote_string("$ENV{CX_ROOT}/bin/$tool") .
                    " --bottle " . shquote_string($ENV{CX_BOTTLE}) .
                    " --scope managed --list-files |";
                require CXConfig;
                my $cxfiles=CXConfig->new($cmd);
                cxlog("-> rc=$?  (took ", CXLog::cxtime()-$start, " seconds)\n");
                if ($? != 0)
                {
                    cxerr("could not get the list of $tool files to package\n");
                    exit 1;
                }
                $rc=1;

                foreach my $menuing_system (sort $cxfiles->get_section_keys())
                {
                    foreach my $file (@{$cxfiles->get_section($menuing_system)->get_field_list()})
                    {
                        # Don't package files that will cause conflicts with
                        # other bottle packages
                        if (($file =~ m%^\Q$ENV{WINEPREFIX}\E/% or
                             $file =~ /$bottle_id/) and
                            !$cxdebian->add_file("", $file))
                        {
                            $lrc=1;
                            last error;
                        }
                    }
                }
            }

            # Package a modified cxbottle.conf file to freeze the menus & co.
            require CXRWConfig;
            my $cxbottle=CXRWConfig->new("$ENV{WINEPREFIX}/cxbottle.conf");
            $cxbottle->set("Bottle", "MenuMode", "frozen");
            $cxbottle->set("Bottle", "AssocMode", "frozen");
            $cxbottle->set("Bottle", "NSPluginMode", "frozen");
            if (!$cxbottle->write("$buildroot/cxbottle.conf"))
            {
                cxerr("unable to save '$buildroot/cxbottle.conf': $!\n");
                last error;
            }
            delete $cxdebian->{hardcode};
            if (!$cxdebian->add_file($buildroot, "cxbottle.conf"))
            {
                $lrc=1;
                last error;
            }
        }

        my $template="$ENV{CX_ROOT}/share/crossover/cxbottle/deb.";
        foreach my $file ("changelog",
                          "compat",
                          "control",
                          "copyright",
                          "postinst",
                          "postrm",
                          "preinst",
                          "prerm",
                          "build")
        {
            $lrc=CXUtils::generate_from_template("$buildroot/debian/$file", "$template$file",
                {bottle          => $bottle,
                 description     => $description,
                 product_id      => $opt_productid,
                 package_name    => $package_name,
                 package_version => $package_version,
                 release         => $opt_release,
                 packager        => $opt_packager,
                 date            => $today
                });
            last error if ($lrc);
        }

        $lrc=$cxdebian->build();
        if ($lrc)
        {
            if ($ENV{CX_BOTTLE} =~ / /)
            {
                cxerr("try renaming the bottle to remove spaces\n");
            }
            last error;
        }

        # Move the Debian package to the current directory
        my @debs=CXUtils::cxglob($buildroot, "*.deb");
        if (!@debs)
        {
            cxerr("unable to find the Debian package for the $bottle bottle\n");
        }
        elsif (@debs > 1)
        {
            cxerr("found more than one Debian package for the $bottle bottle: @debs\n");
        }
        else
        {
            my $deb=cxbasename($debs[0]);
            if (!defined cxmv($debs[0], $deb))
            {
                cxerr("unable to move '$debs[0]' to the current directory: $!\n");
            }
            rmdir cxdirname($debs[0]);
            print "deb: $deb\n";
        }

        last;
    }

    if (defined $buildroot)
    {
        require File::Path;
        File::Path::rmtree($buildroot);
    }
    $rc=1 if ($lrc);
}

if (defined $opt_rpm)
{
    # Use a pseudo-loop to simplify error handling
    my ($tmpdir, $lrc);
  error: while (1)
    {
        my $linuxrpm=CXBRPM->new();
        if (!defined $linuxrpm->{rpmbuild})
        {
            $lrc=1;
            last error;
        }

        # Make sure the generated package will work out-of-the-box with a
        # standard install of CrossOver
        my $product_name=CXUtils::get_product_name();
        my $bottle=$opt_rename || cxbasename($ENV{CX_BOTTLE});
        my $description;
        if (defined $opt_description)
        {
            $description=$opt_description;
        }
        else
        {
            $description="The $bottle CrossOver Bottle.\n";
            $description.=$cxconfig->get("Bottle", "Description", "");
        }
        my $rpm_prefix="/opt/$opt_productid/support/$bottle";
        require CXRPM;
        my $package_name=($opt_productid eq CXUtils::get_builtin_product_id() ?
                         "cxbottle" :
                         "$opt_productid-bottle");
        $package_name=CXRPM::compute_package_name("$package_name-$bottle");

        # Copy the bottle so it has the expected directory structure
        $tmpdir=get_tmpdir(cxdirname($ENV{WINEPREFIX}));
        $tmpdir=get_tmpdir() if ($tmpdir =~ / /);
        if ($tmpdir =~ / /)
        {
            # RPM won't allow us to use a buildroot that contains a space.
            # So hope we can use /tmp.
            $tmpdir="/tmp";
        }
        $tmpdir.="/cxbottle.$$.buildrpm";

        my $imagedir="$tmpdir/image";
        if (!$linuxrpm->create_image($ENV{WINEPREFIX}, "$imagedir$rpm_prefix"))
        {
            $lrc=1;
            last error;
        }

        my $template="$ENV{CX_ROOT}/share/crossover/cxbottle/rpm.spec";
        my $specfile="$tmpdir/rpm.spec";
        $lrc=CXUtils::generate_from_template($specfile, $template,
            {bottle             => $bottle,
             description        => $description,
             product_id         => $opt_productid,
             product_name       => $product_name,
             package_name       => $package_name,
             package_version    => $package_version,
             release            => $opt_release,
             packager           => $opt_packager,
             buildroot          => $imagedir,
             file_list          => CXRPM::escape_file_path($rpm_prefix)
            });
        last error if ($lrc);

        $lrc=$linuxrpm->build($tmpdir);
        last error if ($lrc);

        # Move the rpm package to the current directory
        my @rpms=CXUtils::cxglob($tmpdir, "noarch/*.rpm");
        if (!@rpms)
        {
            cxerr("unable to find the RPM package for the $bottle bottle\n");
        }
        elsif (@rpms > 1)
        {
            cxerr("found more than one RPM package for the $bottle bottle: @rpms\n");
        }
        else
        {
            my $rpm=cxbasename($rpms[0]);
            if (!defined cxmv($rpms[0], $rpm))
            {
                cxerr("unable to move '$rpms[0]' to the current directory: $!\n");
            }
            rmdir cxdirname($rpms[0]);
            print "rpm: $rpm\n";
        }

        last;
    }

    if (defined $tmpdir)
    {
        require File::Path;
        File::Path::rmtree($tmpdir);
    }
    $rc=1 if ($lrc);
}

if (defined $opt_sunpkg)
{
    my ($pkgdir, $lrc);

    # Use a pseudo-loop to simplify error handling
  error: while (1)
    {
        require CXSunpkg;
        my $bottle=$opt_rename || cxbasename($ENV{CX_BOTTLE});
        my $package_name=($opt_productid eq CXUtils::get_builtin_product_id() ?
                          "" :
                          "$opt_productid-");
        $package_name=CXSunpkg::compute_package_name("cxb", "$package_name$bottle");
        cxlog("Creating solaris tree for $package_name\n");

        $pkgdir=$package_name;
        if (-d $pkgdir)
        {
            require File::Path;
            if (!File::Path::rmtree($pkgdir))
            {
                cxerr("unable to delete the '$pkgdir' directory\n");
                $lrc=1;
                last error;
            }
        }

        my $pkg=CXBSunpkg->new($pkgdir, $opt_sunzip);
        if (!$pkg)
        {
            $lrc=1;
            last error;
        }

        my $description=$cxconfig->get("Bottle", "Description", "");
        my ($mday, $mon, $year)=(localtime(time))[3,4,5];
        $mon++;
        $year+=1900;
        my $today=sprintf("%d/%02d/%02d", $year, $mon, $mday);

        if (!$pkg->generate_install_files("$ENV{CX_ROOT}/share/crossover/cxbottle/sunpkg.",
            ["pkginfo",
             "install/checkinstall",
             "install/depend",
             "install/postinstall",
             "install/postremove",
             "install/preremove"],
            {package_name    => $package_name,
             bottle          => $bottle,
             description     => $description,
             product_id      => $opt_productid,
             product_name    => CXUtils::get_product_name(),
             package_version => $package_version,
             release         => $opt_release,
             packager        => $opt_packager,
             date            => $today,
             noverify        => ($opt_sunzip eq "none" ? "" : "none")
            }))
        {
            $lrc=1;
            last error;
        }

        if (!$pkg->add_tree("reloc/\$CX_PKGINST/support/$bottle", $ENV{WINEPREFIX}, 0755) or
            !$pkg->finalize("\$CX_PKGINST"))
        {
            $lrc=1;
            last error;
        }

        my $archive="$pkgdir/archive";
        if (!cxmkpath($archive, 0755))
        {
            cxerr("unable to create the '$archive' directory: $@\n");
            $lrc=1;
            last error;
        }
        # The permissions of the files in cpio may well be unsuitable for a
        # managed bottle. So this depends on --restored to fix them.
        my $cmd=[["cd", $ENV{WINEPREFIX}], "&&",
                 [["find", "drive_c", "-print"],
                  "|",
                  [[CXUtils::get_cpio_o()], "2>"] # cpio pollutes stderr
                 ]];
        $archive.="/extra";
        if ($opt_sunzip eq "gzip")
        {
            $cmd=[$cmd, "|", [[CXUtils::get_gzip(), "-9"], ">", "$archive.gz"]];
        }
        elsif ($opt_sunzip eq "bzip2")
        {
            $cmd=[$cmd, "|", [[CXUtils::get_bzip2(), "-9"], ">", "$archive.bz2"]];
        }
        elsif ($opt_sunzip eq "7z")
        {
            # 7za pollutes stderr
            $cmd=[$cmd, "|", [["7za", "a", "$archive.7z", "-mx=9", "-si"], "2>"]];
        }
        else
        {
            $cmd=[$cmd, ">", "$archive.cpio"];
        }
        my $shcmd=new_shell_command({cmd => $cmd, capture_output => 1});
        if ($shcmd->run())
        {
            print STDERR $shcmd->get_error_report();
            cxerr("unable to archive the c: drive\n");
            $lrc=1;
            last error;
        }

        print "sunpkg: $package_name\n";
        last;
    }

    if ($lrc and defined $pkgdir)
    {
        require File::Path;
        File::Path::rmtree($pkgdir);
    }
    $rc=1 if ($lrc);
}


if ($opt_delete)
{
    if (!$rc or $opt_force)
    {
        if (!$opt_force)
        {
            my $msg=cxgettext("You are about to delete the " .
                "'\%s' bottle. This operation will remove all the " .
                "Windows applications it contains, as well as any document " .
                "stored in that bottle's C: drive.", $ENV{CX_BOTTLE});
            if (!$opt_uninstall)
            {
                $msg=cxgettext("\%s\nNote also that you should " .
                    "always uninstall a bottle before deleting it.", $msg);
            }
            my $choice=cxmessage(
                "-title", "CrossOver Warning",
                "-buttons", "Continue:0,Abort:2",
                "-default", "Abort",
                "-image", "crossover",
                "\%s", $msg);
            if ($choice != 0)
            {
                if (defined $bottle_lock and !CXUtils::cxunlock($bottle_lock))
                {
                    cxwarn("unable to release the bottle lock\n");
                }
                exit(2);
            }
        }

        # Stop all windows applications in this bottle before deleting it
        if (!$bottle_lock)
        {
            my $msg=cxgettext("The applications in the \%s bottle must be shut down for it to be deleted.", $ENV{CX_BOTTLE});
            $bottle_lock=lock_and_shutdown_bottle($ENV{CX_BOTTLE}, $opt_scope, $ENV{WINEPREFIX}, $msg);
            exit(1) if (!$bottle_lock);
        }

        # If this is the default bottle, then make it not so
        my $default_link=cxdirname($raw_wineprefix) . "/default";
        if (CXUtils::same_inode($default_link, $ENV{WINEPREFIX}))
        {
            unlink $default_link;
        }

        # Delete the bottle
        require File::Path;
        if (!File::Path::rmtree($ENV{WINEPREFIX}))
        {
            cxerr("unable to delete '$ENV{WINEPREFIX}': $!\n");
            $rc=1;
        }

        # And unlock it
        if (defined $bottle_lock and !CXUtils::cxunlock($bottle_lock))
        {
            cxwarn("unable to release the bottle lock\n");
        }
    }
    else
    {
        cxwarn("ignoring the --delete option due to a previous error\n");
    }
}


exit($rc || 0);