#!/usr/bin/perl
# $id$
# $crtd:  by  Derald Metzger  on  000813 $
# $cmnt:  Set a file's uid, gid, and mode to those specified in the rppmdb.
#   This file is part of the cfm pkg (GPL).
# $

use strict;

#  Load perl modules
use Cwd;
use File::Spec;
use Getopt::Long;

#  Var declarations
use vars '$opt_d';       # Show diff - rpmdb vs current params
use vars '$opt_q';       # Dump file's rpmdb params
use vars '$opt_u';       # Reset file's uid
use vars '$opt_g';       # Reset file's gid
use vars '$opt_m';       # Reset file's mode
use vars '$opt_t';       # Reset file's timestamp if size & md5 are valid

#  Id/set vars
my $l_atime;             # lstat access time
my $l_blocks;            # lstat blocks
my $l_blksize;           # lstat blocksize
my $l_ctime;             # lstat create time
my $l_dev;               # lstat dev
my $l_gid;               # lstat gid
my $l_ino;               # lstat inode
my $l_mod;               # lstat mode
my $l_mode;              # lstat mode, octal string
my $l_ts;                # lstat modify time
my $l_nlink;             # lstat number of links
my $l_rdev;              # lstat device type
my $l_sz;                # lstat file size
my $l_uid;               # lstat uid
my $l_md5;               # md5 checksum of current file
my @pkgprms;             # Rpmdb params of all files in pkg
my $rc;                  # Return code for system calls
my @r_prms;              # Rpmdb file params, can be multiple entries
my $r_fn;                # Rpmdb filename
my $r_gid;               # Rpmdb gid
my $r_md5;               # Rpmdb checksum
my $r_mode;              # Rpmdb Mode
my $r_sz;                # Rpmdb size
my $r_ts;                # Rpmdb timestamp
my $r_uid;               # Rpmdb uid
my $trash;               # trash variable
my $vblvl = 3;           # Verbosity level
my $ver = "cfmugm-0.2";  # Current version of cfmugm

my $usage = "
$ver;
Copyright (C) 1998 - 2000 Derald Metzger
This may be freely redistributed under the terms of the GNU GPL.
usage:
  cfmugm [-q] [-u] [-g] [-m] [-t] <file> [...]
    -d  display diff between rpmdb and current file params, default
        1st value is from rpmdb, 2nd from current host file
        null output means all params matched
    -q  query rpmdb for file's correct params
    -u  reset uid
    -g  reset gid
    -m  reset mode
    -t  reset timestamp if size & md5 match
  Examine file's rpmdb params and/or reset uid, gid, mode, or timestamp 
  to those in the rpmdb. The -t timestamp option suceeds only if size 
  and md5 match the rpmdb.
";

Getopt::Long::Configure("bundling", "permute");
GetOptions(
           "d",
           "q",
           "u",
           "g",
           "m",
           "t",
          )
    || die "### Invalid option! $usage";

#  File arg is rqd. If no opts set defaults.
@ARGV || die "### Insufficient args! $usage";
$opt_d|$opt_q|$opt_u|$opt_g|$opt_m|$opt_t || ($opt_d = 1);

while(my $fn = shift @ARGV) {

    #  Cannonicalize the filename
    ($fn eq '.') && ($fn = ());
    $fn =~ m;^/;o || ($fn = Cwd::cwd() . "/$fn");  # Make relative absolute
    ($fn) = File::Spec->canonpath($fn);    # Strip //, trailing /
    $fn =~ s;([^/]+/\.\.)|(/\.)($|/);;og;  # Strip `dir/..', `/.'

    #  Get the current file's params
    ($l_dev,$l_ino,$l_mod,$l_nlink,$l_uid,$l_gid,$l_rdev,$l_sz,
     $l_atime,$l_ts,$l_ctime,$l_blksize,$l_blocks)
        = lstat($fn);
	$l_mode = sprintf("%#lo", $l_mod);
	chop($l_md5 = `md5sum $fn 2>/dev/null| cut -d' ' -f1`);

    #  Get the rpmdb params
    (my @pkgprms = `rpm -qf --dump $fn 2>/dev/null`)
         || do { warn "### cfmugm: no rpmdb info for $fn\n"; next; };
    (@r_prms) = grep(/^$fn /, @pkgprms);
    ($r_fn,$r_sz,$r_ts,$r_md5,$r_mode,$r_uid,$r_gid,$trash)
        = split /\s/, @r_prms[0];

	#  Check multi-param sets to insure they match
	$#r_prms && do {
		my $setidx = 0;
		print "### ${fn}\[0] rpmdb internal conflicts follow:\n";
		while($setidx < $#r_prms) {
			$setidx++;
			my @str = ();
			($r_prms[0] eq $r_prms[$setidx]) || do {
				my ($t_fn,$t_sz,$t_ts,$t_md5,$t_mode,$t_uid,$t_gid,$trash)
					= split /\s/, @r_prms[$setidx];
				print " [$setidx]\n";
				($r_sz == $t_sz) || push @str, "  S: $r_sz $t_sz\n";
				($r_ts == $t_ts) || push @str, "  T: $r_ts $t_ts\n";
				($r_md5 == $t_md5) || push @str, "  5: $r_md5 $t_md5\n";
				($r_mode eq $t_mode) || push @str, "  M: $r_mode $t_mode\n";
				($r_uid == $t_uid) || push @str, "  U: $r_uid $t_uid\n";
				($r_gid == $t_gid) || push @str, "  G: $r_gid $t_gid\n";
				@str && print @str;
			};
		}
	};

    $opt_d && do {
		my @str = ();
		($r_sz == $l_sz) || push @str, "S: $r_sz $l_sz\n";
		($r_ts == $l_ts) || push @str, "T: $r_ts $l_ts\n";
		($r_md5 == $l_md5) || push @str, "5: $r_md5 $l_md5\n";
		($r_mode == $l_mode) || push @str, "M: $r_mode $l_mode\n";
		($r_uid == $l_uid) || push @str, "U: $r_uid $l_uid\n";
		($r_gid == $l_gid) || push @str, "G: $r_gid $l_gid\n";
        @str && print "$fn\n @str";
	};

    $opt_q && print @r_prms, "\n";
    $opt_u && do {
        `chown $r_uid $r_fn 2>/dev/null`;
        $? && print "### Failed: chown $r_uid $r_fn\n";
    };
    $opt_g && do {
        `chgrp $r_gid $r_fn 2>/dev/null`;
        $? && print "### Failed: chgrp $r_gid $r_fn\n";
    };
    $opt_m && do {
        my $mode = substr($r_mode, 3);
        chmod(oct($mode), $r_fn) == 1
            || print "### Failed: chmod $mode $r_fn\n";
    };
    $opt_t && do {
      SW: {
          ($l_sz == $r_sz) || do { print "### altered size $r_fn\n"; last };
          ($l_md5 == $r_md5) || do { print "### altered md5 $r_fn\n"; last };
          utime(time, $r_ts, $r_fn) 
			  || print "### Failed: utime(time, $r_ts, $r_fn)\n";
      }
    };
}
