#!/usr/bin/perl -w
#
# mode.pl, a program for setting permission on files and directories recursively.
#
# Copyright (c) 2006 Marcus Libäck <marcus@terminal.se>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. The name of the author may not be used to endorse or promote products
#    derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#

use strict;
use Getopt::Std;

# Variables
# -------------
my (
    %opts,
    $dirmode,
    $filemode,
);

my $recursive = 1;
my $dircount = 0;
my $filecount = 0;
my $version = "1.1-051212";

# Main
# -------------
getopts('d:f:Rh', \%opts);

if (exists $opts{'h'}) {
    help();
}

# Either -f, -d or both must be present.
unless (exists $opts{'f'} or exists $opts{'d'}) {
    help();
}

# Recursive
unless (exists $opts{'R'}) {
    undef $recursive;
}

# Directory mode
if (exists $opts{'d'}) {
    if ($opts{'d'} =~ m/^[0-7]{3,4}$/ ) {
        $dirmode = oct $opts{'d'};
    } else {
        die "Invalid directory mode $opts{'d'}";
    }
}

# File mode
if (exists $opts{'f'}) {
    if ($opts{'f'} =~ m/^[0-7]{3,4}$/ ) {
        $filemode = oct $opts{'f'};
    } else {
        die "Invalid file mode $opts{'f'}";
    }
}

# Run mode() on each file specified in @ARGV.
foreach (@ARGV) {
    if (defined $recursive) {
        mode_r ("$_");
    } else {
        mode_n ("$_");
    }
}
print "Mode adjusted for $dircount directories and $filecount files\n";

# Subroutines
# -------------

# mode_r(), sets recursive permissions.
sub mode_r {
    # Recursive
    my $entry = shift;

    if (opendir (DIR, $entry)) {
        # We end up here if $entry is a dir...

        if (defined $dirmode ) {
            chmod ($dirmode, $entry) or die $!;
            $dircount++;
        }

        foreach (sort readdir (DIR)) {
            next if $_ eq "." or $_ eq "..";
            next if -l $_;

            # Recursive magic happens here.
            mode_r ("$entry/$_");
        }
    } else {
        # ...and here if it is a file.

        if (defined $filemode) {
            chmod ($filemode, $entry) or die $!;
            $filecount++;
        }
    }
    closedir (DIR);
}

# mode_n(), sets non-recursive permissions.
sub mode_n {
    # Non recursive
    my $entry = shift;

    # Regular file
    if (not -d $entry and defined $filemode) {
        chmod ($filemode, $entry) or die $!;
        $filecount++;
    # Directory, descends into $entry and chmods non-recursive.
    } elsif (-d $entry) {
        chdir "$entry";
        opendir (DIR, ".");
        foreach (sort readdir (DIR)) {
            next if $_ eq "..";
            next if -l $_;

            if (-d $_ and defined $dirmode) {
                chmod ($dirmode, $_) or die $!;
                $dircount++;
            } elsif (not -d $_ and defined $filemode) {
                chmod ($filemode, $_) or die $!;
                $filecount++;
            }
        }
        closedir (DIR);
        chdir "..";
    }
}

# Help function.
sub help {
    print "Version $version

Usage: $0 [options] [files]

Options:
 -h     display this text.

 -R     set modes recursively.
 -d NN  directory mode.
 -f NN  file mode.\n";

    exit;
}