#!/usr/bin/perl
#============================================================= -*-perl-*-
#
# BackupPC_ls: emulate a simple subset of ls to make it easy to
# navigate stored backups
#
# DESCRIPTION
#
#   Usage: BackupPC_ls path
#
# AUTHOR
#   Craig Barratt  <cbarratt@users.sourceforge.net>
#
# COPYRIGHT
#   Copyright (C) 2005-2020  Craig Barratt
#
#   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 3 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.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
#========================================================================
#
# Version 4.4.0, released 20 Jun 2020.
#
# See http://backuppc.sourceforge.net.
#
#========================================================================

use strict;
no utf8;

use lib "/usr/share/backuppc/lib";

use BackupPC::Lib;
use BackupPC::XS qw ( :all );
use BackupPC::View;
use Data::Dumper;
use Getopt::Std;

die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) );

my $TopDir = $bpc->TopDir();
my $BinDir = $bpc->BinDir();
my %Conf   = $bpc->Conf();

my($Host, $Num, $Share, $View, @Backups);

my %opts;

if ( !getopts("Rih:n:s:", \%opts) || @ARGV < 1 ) {
    usageAndExit();
}

while ( my $dir = shift(@ARGV) ) {
    my $i;
    if ( $dir =~ m{^\Q$TopDir\E/pc/([^/]*)/(\d+)/(.*)} ) {
        $Host = $1;
        $Num  = $2;
        $dir  = $3;
        if ( $dir =~ m{(.*?)(/.*)} ) {
            $Share = $1;
            $dir   = $2;
        } else {
            $Share = $dir;
            $dir   = "/";
        }
        $dir   = $bpc->fileNameUnmangle($dir)   if ( $dir   !~ m{/[^f]} && -d "$TopDir/pc/$Host/$Num/$Share/$dir" );
        $Share = $bpc->fileNameUnmangle($Share) if ( $Share =~ /^f/     && -d "$TopDir/pc/$Host/$Num/$Share" );
    } else {
        if ( !defined($opts{h}) || !defined($opts{n}) || !defined($opts{s}) ) {
            print(STDERR "Can't determine host or backup number from path - need to specific -h, -n and -s\n");
            usageAndExit();
        }
        $Host  = $opts{h};
        $Num   = $opts{n};
        $Share = $opts{s};
    }
    @Backups = $bpc->BackupInfoRead($Host);
    $Num     = $Backups[@Backups + $Num]{num} if ( -@Backups <= $Num && $Num < 0 );
    for ( $i = 0 ; $i < @Backups ; $i++ ) {
        last if ( $Backups[$i]{num} == $Num );
    }
    if ( $i >= @Backups ) {
        print(STDERR "Backup #$Num doesn't exist for host $Host\n");
        exit(1);
    }
    $View = BackupPC::View->new($bpc, $Host, \@Backups);
    printDir($dir);
}

sub usageAndExit
{
    print(STDERR "usage: $0 [-iR] [-h host] [-n bkupNum] [-s shareName] dirs/files...\n");
    exit(1);
}

sub printDir
{
    my($dir) = @_;
    my($fileName, $info, $dirList);

    $dir =~ s{/+$}{};
    $dir =~ s{^/+}{/};
    $dir = "/" if ( $dir eq "" );

    my $info = $View->dirAttrib($Num, $Share, $dir);

    #print("Num = $Num, Share = $Share, dir = $dir\n");
    #print Dumper($info);

    print("$dir:\n");
    foreach my $file ( sort(keys(%$info)) ) {
        my($str, $s, $fileData);
        my @type2modeChar   = ("-", "-", "l", "c", "b", "d", "p", "s", "?", "x");
        my @type2suffixChar = ("",  "",  "@", "%", "%", "/", "|", "=", "",  "");
        my $attr            = $info->{$file};

        if (
            (
                   $attr->{type} == BPC_FTYPE_SYMLINK
                || $attr->{type} == BPC_FTYPE_CHARDEV
                || $attr->{type} == BPC_FTYPE_BLOCKDEV
                || $attr->{type} == BPC_FTYPE_HARDLINK
            )
            && $attr->{digest} ne ""
        ) {
            #
            # read the file
            # TODO - handle v3 backups
            #
            my $f = $bpc->MD52Path($attr->{digest}, $attr->{compress});
            if ( defined(my $fd = BackupPC::XS::FileZIO::open($f, 0, $attr->{compress})) ) {
                my $target;
                $fd->read(\$fileData, 65536);
                $fd->close();
            } else {
                $fileData = "???";
            }
        }

        $str = "";
        if ( $opts{i} ) {
            $str .= sprintf("%6d/%-3d ", $attr->{inode}, $attr->{nlinks} > 0 ? $attr->{nlinks} : 1);
        }

        if ( $attr->{type} == BPC_FTYPE_DELETED ) {
            $str .= "*deleted--";
        } else {
            $str .= $type2modeChar[$attr->{type}] || "-";
            for ( my $i = 6 ; $i >= 0 ; $i -= 3 ) {
                $str .= ($attr->{mode} & (1 << ($i + 2))) ? "r" : '-';
                $str .= ($attr->{mode} & (1 << ($i + 1))) ? "w" : '-';
                $str .= ($attr->{mode} & (1 << ($i + 0))) ? "x" : '-';
            }
        }
        $str .= defined($attr->{xattr}) ? "+" : " ";
        $s = sprintf("%d/%d", $attr->{uid}, $attr->{gid});
        $str .= sprintf(" %8s", $s);
        if ( ($attr->{type} == BPC_FTYPE_CHARDEV || $attr->{type} == BPC_FTYPE_BLOCKDEV) && $fileData ne "" ) {
            $str .= sprintf(" %10s", $fileData);
        } else {
            $str .= sprintf(" %10llu", $attr->{size});
        }
        $str .= " " . $bpc->timeStamp($attr->{mtime});
        my $nameStr = "$dir/$attr->{name}";
        $nameStr =~ s{//+}{/}g;
        $str .= " " . $nameStr;
        $str .= $type2suffixChar[$attr->{type}];
        if ( $attr->{type} == BPC_FTYPE_SYMLINK && $fileData ne "" ) {
            $str .= " -> " . $fileData;
        }
        if ( $attr->{type} == BPC_FTYPE_HARDLINK && $fileData =~ m{(.*)/(.*)} ) {
            #
            # Update digest to the hardlink target file
            #
            my $d = $1;
            my $f = $2;
            my $v = $View->dirAttrib($Num, $Share, $d);
            $attr->{digest} = $v->{$f}{digest} if ( $v->{$f} && length($v->{$f}{digest}) );
        }
        if ( length($attr->{digest}) ) {
            $str .= " (" . unpack("H*", $attr->{digest}) . ")";
        }
        print($str, "\n");
        push(@$dirList, $file) if ( $attr->{type} == BPC_FTYPE_DIR );
    }
    if ( $opts{R} ) {
        foreach my $file ( @$dirList ) {
            printDir("$dir/$file");
        }
    }
    $bpc->flushXSLibMesgs();
}
