#!/usr/bin/perl
# cpanel10 - cpsyncifypath.pl                     Copyright(c) 2006 cPanel, Inc.
#                                                           All rights Reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited

use strict;
use warnings;
use File::Spec;
use File::Slurp;    # read_dir write_file
use Digest::MD5::File;

my $version   = '0.0.1';
my $copyright = 'Copyright(c) 2006, cPanel Inc.';

#### begin conf ##

my $user          = '';       # user who's home directory this is
my $group         = '';       # group
my $vendor        = '';    # "cPAddon Vendor Info url" default output
my $cpaddons_root = '';    # path of http://[cphost][cphuri' from your "cPAddon Vendor Info url"
my $clustersync   = '';    # options script to sync (-t sync)  file to other servers however you need, executed with same args as $0

#### end config ##

my %options = cPanelSyncUtil::_get_opts_hash('qha:n:t:u:g:d:v:s:');

if ( exists $options{'h'} || ! scalar keys %options ) {
    print <<"EOM";
$0 v $version $copyright

    This application assists in setting up a cpanelsync directory structure
suitable for usage with cPanel servers.

Usage:   $0 [option]

Command Line Options:
    Booleans Flag:
        -h
    Argument Flags:
        -c cPAddon_category
        -n cPAddon_name
        -a Action
        -u user
        -g group
        -d directory
        -v cPAddon_vendor_name
        -s sync_to

EOM
    exit;
}

my $message     = '';

# Directory
if ( exists $options{'d'} ) {
    if ( ! -d $options{'d'} ) {
        $message .= "Directory $options{'d'} is not a directory\n";
    }
    else {
        $cpaddons_root = $options{'d'};
    }
}
else {
    $message .= "Directory must be specified with the -d option\n";
}
# name
if ( exists $options{'n'} ) {
    if ( ! $options{'n'} ) {
        $message .= "cPAddon name is required\n";
    }
}
else {
    $message .= "cPAddon name must be specified with the -n option\n";
}
# action
if ( exists $options{'a'} ) {
    if ( $options{'a'} ne 'bz2'
         && $options{'a'} ne 'all'
         && $options{'a'} ne 'lock'
         && $options{'a'} ne 'unlock'
         && $options{'a'} ne 'sync' 
       ) {
        print "Option -a requires a valid argument (bz2|lock|unlock|sync|all) Note: all does bz2, unlock, then sync\n";
    }
}
else {
    $message .= "Action must be specified with the -a option (bz2|lock|unlock|sync)\n";
}

if ($message) {
    print $message;
    exit 1;
}

my $root = File::Spec->catdir( $cpaddons_root, $vendor );

chdir $cpaddons_root or die "Could not go into base dir: $!";

_raw_dir( $cpaddons_root, 'cPAddonsAvailable', 1, read_dir('cPAddonsAvailable') )
    or warn "_raw_dir _theme: $!";

my @md5files = grep { m/\.pm$/ } read_dir("$cpaddons_root/cPAddonsMD5");
_raw_dir( $cpaddons_root, 'cPAddonsMD5', 0, @md5files )
    or warn "_raw_dir $cpaddons_root: $!";

die '-t action (bz2|lock|unlock|sync) is required' if $options{'a'} eq '';
die "-c may only be one of the directories in $root"
    if $options{'c'} ne '' && ! -d "$root/$options{'c'}";

cPanelSyncUtil::_chmod_pwd_recursively( $user, $group );

if ( $clustersync && -x $clustersync ) {
    exec $clustersync, @ARGV if $options{'a'} eq 'sync';
}

ROOT:
for my $dir ( read_dir $root) {
    next ROOT if $dir =~ /\.bak/
                 || $dir =~ /^\./
                 || ! -d "$root/$dir"
                 || -l "$root/$dir"
                 || ( $options{'c'} ne '' && $dir ne $options{'c'} )
                 ;

    if ( !chdir "$root/$dir" ) {
        warn "Could not go into $root/$dir: $!";
        next ROOT;
    }

    cPanelSyncUtil::_chmod_pwd_recursively( $user, $group );

    SUBDIR:
    for my $name ( read_dir '.' ) {
        next SUBDIR if $name =~ /^\./
                       || !-d "$root/$dir/$name"
                       || -l "$root/$dir/$name"
                       || ( $options{'n'} ne '' && $name !~ /^$options{'n'}/ )
                       ;

        if ( $options{'a'} eq 'bz2' ) {
            system 'bzip2', '-k', "$name.pm";
            cPanelSyncUtil::_raw_dir( "$root/$dir", $name, 1 )
                or warn "cPanelSyncUtil::_raw_dir  $root/$dir: $!";
            cPanelSyncUtil::_sync_touchlock_pwd();
        }
        elsif ( $options{'a'} eq 'lock' ) {
            write_file( "$root/$dir/$name/.cpanelsync.lock", 'locked' );
        }
        elsif ( $options{'a'} eq 'unlock' ) {
            write_file( "$root/$dir/$name/.cpanelsync.lock", '' );
        }
        cPanelSyncUtil::_chmod_pwd_recursively( $user, $group );
    }
}

package cPanelSyncUtil;

use strict;
use warnings;

use File::Slurp;
use version; our $VERSION = qv('0.0.1');

sub _chmod_pwd_recursively {
    my ( $user, $group ) = @_;
    system 'chown', '-R', "$user:$group", '.';
}

sub _raw_dir {
    require Archive::Tar;
    require Cwd;

    my ( $base, $archive, $verbose, @files ) = @_;
    my $bz2_opt = $verbose ? '-kv' : '-k';
    my $pwd = Cwd::cwd();
    chdir $base or return;

    my $tar = Archive::Tar->new();
    for my $file ( read_dir($archive) ) {
        next if $file !~ m{\.bz2\.bz2$} || $file =~ m{\.bz2$};
        $tar->add_files("$archive/$file");
    }
    $tar->write("$archive.tar");

    system 'bzip2', $bz2_opt, "$archive.tar";
    if (@files) {
        chdir $archive or return;
        for my $file (@files) {
            system 'bzip2', $bz2_opt, $file if -f $file;
        }
    }
    cPanelSyncUtil::_sync_touchlock_pwd();

    chdir "$base/$archive" or return;
    cPanelSyncUtil::_sync_touchlock_pwd();

    chdir $pwd or return;

    1;
}

sub _get_opts_hash {
    require Getopt::Std;
    my ( $args, $opts_ref ) = @_;
    $opts_ref = {} if ref $opts_ref ne 'HASH';
    Getopt::Std::getopts( $args, $opts_ref );
    return wantarray ? %{$opts_ref} : $opts_ref;
}

sub _sync_touchlock_pwd {
    $|++;
    require Cwd;
    my $cwd = Cwd::getcwd();

    print "$0 [$> $< : $cwd] Building .cpanelsync file...";

    my @FILES = split( /\n/, `find .` );

    my %OLDMD5S;
    if ( -e '.cpanelsync' ) {
        open my $cps_fh, '<', '.cpanelsync' or die "$cwd/.cpanelsync read failed: $!";
        while (<$cps_fh>) {
            chomp;
            my ( $ftype, $rfile, $perm, $extra ) = split( /===/, $_ );
            $OLDMD5S{$rfile} = $extra if $ftype eq 'f';
        }
        close $cps_fh;
    }

    open my $cpsw_fh, '>', '.cpanelsync' or die "$cwd/.cpanelsync write failed: $!";

    FILE:
    foreach my $file (@FILES) {
        next FILE if $file =~ /===/
                     || $file =~ /\.cpanelsync/
                     || $file =~ /\.cpanelsync.lock/;

        my $tfile;

        if ( $file =~ /\.bz2$/ ) {
            $tfile = $file;
            $tfile =~ s/\.bz2$//g;
            next FILE if -e $file && -e $tfile;
        }

        my $perms = substr( sprintf( '%o', ( stat($file) )[2] ), -3, 3 );

        if ( $cwd =~ /\/bin$/ && ( $file eq './cpwrap' || $file eq './jailshell' ) ) {
            $perms = 4755;
        }

        if ( -l $file ) {
            my $point = readlink($file);
            print {$cpsw_fh} "l===$file===$perms===$point\n";
        }
        elsif ( -d $file ) {
            print {$cpsw_fh} "d===$file===$perms\n";
        }
        else {
            print "Warning: zero sized file $file.bz2\n" if -z "$file.bz2";

            my $md5sum = Digest::MD5::File::file_md5_hex($file);

            system( 'bzip2', '-kfv', $file )
                if $md5sum ne $OLDMD5S{$file}
                   || !-e "$file.bz2"
                   || -z "$file.bz2";
            print {$cpsw_fh} "f===$file===$perms===$md5sum\n";
        }
    }
    print {$cpsw_fh} ".\n";
    close $cpsw_fh;

    system qw(bzip2 -fk .cpanelsync);

    print "Done\n";

    system qw(touch .cpanelsync.lock);
}

1;
