#!/usr/bin/perl -w
# compute the blocks used by a file
# usage:
# computeblocklists [options] <files...>
# options:
# --padstart NUM, --padend NUM, --verbose
#
# output:
# <file base name> <size> <blocksize> <block numbers...>
#
# a block is either a number or a range (start-end)
#

################################################################
#
# Copyright (c) 1995-2014 SUSE Linux Products GmbH
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or 3 as
# published by the Free Software Foundation.
#
# 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 (see the file COPYING); if not, write to the
# Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
#
################################################################

use strict;

my ($opt_padstart, $opt_padend, $opt_verbose, $opt_manifest, $opt_mani0);
$opt_verbose = 0;

while (@ARGV)  {
  if ($ARGV[0] eq '--padstart') {
    shift @ARGV;
    $opt_padstart = shift @ARGV;
    next;
  }
  if ($ARGV[0] eq '--padend') {
    shift @ARGV;
    $opt_padend = shift @ARGV;
    next;
  }
  if ($ARGV[0] eq '--verbose' || $ARGV[0] eq '-v') {
    shift @ARGV;
    $opt_verbose++;
    next;
  }
  if ($ARGV[0] eq '-0') {
    shift @ARGV;
    $opt_mani0 = 1;
    next;
  }
  if ($ARGV[0] eq '--manifest') {
    shift @ARGV;
    $opt_manifest = shift @ARGV;
    next;
  }
  last;
}

print "\n"x$opt_padstart if $opt_padstart;

if ($opt_manifest) {
  if ($opt_manifest eq '-') {
    open(MANIFEST, '<&STDIN') || die("STDIN dup: $!\n");
  } else {
    open(MANIFEST, '<', $opt_manifest) || die("$opt_manifest: $!\n");
  }
}

while (1) {
  my $file;
  if (@ARGV) {
    $file = shift @ARGV;
  } elsif ($opt_manifest) {
    if ($opt_mani0) {
      local $/ = "\0";
      $file = <MANIFEST>;
      last unless defined $file;
      $file =~ s/\0$//s;
    } else {
      $file = <MANIFEST>;
      last unless defined $file;
      chomp $file;
    }
  } else {
    last;
  }
  my $n = $file;
  $n =~ s/([\000-\037 %])/sprintf("%%%02X", ord($1))/ges;
  if (-l $file) {
    print STDERR "$file\n" if $opt_verbose && !($opt_verbose == 1 && $file =~ /^KIWI\/.*\//);
    my $c = readlink($file);
    die("$file: readlink: $!\n") unless defined $c;
    if ("/$c/" =~ /\/\.?\//s) {
      print STDERR "$file: bad symlink ($c) ignored\n";
      next;
    }
    if ("/$c/" =~ /^(\/\.\.)+\/(.*?)$/s) {
      my ($head, $tail) = ($1, $2);
      if (("/$tail/" =~ /\/\.\.\//s) || (($head =~ y!/!!) > ($file =~ y!/!!))) {
        print STDERR "$file: bad symlink ($c) ignored\n";
        next;
      }
    } else {
      if ("/$c/" =~ /\/\.\.\//s) {
        print STDERR "$file: bad symlink ($c) ignored\n";
        next;
      }
    }
    $c =~ s/([\000-\037 %])/sprintf("%%%02X", ord($1))/ges;
    print "l $n $c\n";
    next;
  } elsif (-d _) {
    print STDERR "$file\n" if $opt_verbose && $opt_verbose > 1;
    my @stat = stat(_);
    print "d $n\n";
    next;
  } elsif (!-f _) {
    next;
  }
  print STDERR "$file\n" if $opt_verbose && !($opt_verbose == 1 && $file =~ /^KIWI\/.*\//);

  if (!open(F, '<', $file)) {
    print STDERR "$file: $!";
    next;
  }

  my @stat = stat(F);
  die unless @stat;
  my $st_size = $stat[7];
  if ($st_size == 0) {
    print "f $n 0\n";
    close F;
    next;
  }

  my $bsize = 'xxxx';
  ioctl(F, 2, $bsize) || ioctl(F, 536870914, $bsize) || die("FIGETBSZ: $!\n");
  $bsize = unpack("L", $bsize);
  die("$file: empty blocksize\n") unless $bsize != 0;

  print "f $n $st_size $bsize";
  my $blocks = int(($st_size+$bsize-1)/$bsize);
  my ($firstblock, $lastblock);
  for ($b = 0; $b < $blocks; ++$b) {
    my $block = pack('I', $b);
    if (not defined ioctl(F, 1, $block)) {
      if (not defined ioctl(F, 536870913, $block)) {
	die "$file: $!\n";
      }
    }
    $block = unpack('I', $block);
    if (!$firstblock && defined($firstblock)) {
      # last block was hole
      if (!$block) {
	$lastblock++;	# count holes, 0-2 means three hole blocks
      } else {
	# switch back from 'hole mode' into normal mode
	printf "-$lastblock" if defined($firstblock) && $firstblock != $lastblock;
	print " $block";
	$firstblock = $lastblock = $block;
      }
      next;
    }
    if (!$firstblock || $lastblock + 1 != $block) {
      # start of a new run
      printf "-$lastblock" if defined($firstblock) && $firstblock != $lastblock;
      print " $block";
      $firstblock = $block;
    }
    $lastblock = $block;
  }
  # finish last run
  printf "-$lastblock" if defined($firstblock) && $firstblock != $lastblock;
  close F;
  print "\n";
}

print "\n"x$opt_padend if $opt_padend;
