The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl -w
#
# Perl Power Tools - mkdir - make directories
# James Wetterau, Jr. <jwjr@panix.com>
# 1999-03-06

use strict;
use File::Basename;

use vars qw($opt_m $opt_p $user_bits $group_bits $other_bits);

my $ILLEGAL = 100000; # outside the range for any acceptable mode
my $mode = oct(777) - umask; 
my $orig_mode = $mode | oct(300); # intermediate dirs need u+wx perms
my $error = 0;
umask 0000; # umask already incorporated into mode

$0 = basename $0;

# check for @ARGV to make sure $ARGV[0] exists, required by -w
while (@ARGV && $ARGV[0] =~ /^-/) {
    my $opt = shift;
    if ($opt =~ /^-m(.*)/) {
	my $mode_opt = $1 || shift;
	if (! $mode_opt) {
	    ErrorUsage("option requires an argument -- m");
	} elsif (($mode = ParseMode($mode_opt)) == $ILLEGAL) {
	    ErrorUsage ("invalid file mode: $mode_opt");
	} else {
	    $mode = oct($mode);
	    $opt_m = 1;
	}
    } elsif ($opt =~ /^-p$/) {
	$opt_p = 1;
    } else {
	$opt =~ s/^-//;
	ErrorUsage("illegal option -- $opt");
    }
}
ErrorUsage() unless @ARGV;

foreach (@ARGV)
{
    if (MakeDirectory($_, $mode))
    {
	print STDERR "$0: $_: $!\n";
	$error = 1;
    }
	
}

exit $error;

sub ParseMode
{
    my $parse_mode = $_[0];
    my $bits = 0;
    local ($user_bits, $group_bits, $other_bits) = (7,7,7);

    if ($parse_mode =~ /^0*[0-7]{1,4}$/) {
	if ($parse_mode =~ /^[^0]/) {
	    $parse_mode = 0 . $parse_mode;
	}
	return $parse_mode;
    } else {
	my %who_bits  = ('a' => 7,  'u' => 4,  'g' => 2,  'o' => 1);
	my %perm_bits = ('r' => \4, 's' => \0, 't' => \0, 'w' => \2, 
			 'x' => \1, 'X' => \1, 'u' => *user_bits, 
			 'g' => *group_bits, 'o' => *other_bits);

	foreach (split (/,/, $parse_mode, -1)) { # don't trim trailing nulls
	    my ($who, $op, $perm) = (0,'',0);
	    if (/^([augo]*)([+-=])([rstwxXugo]*)$/) {
		foreach (split (//, $1)) { 
		    $who = $who | $who_bits{$_};
		    # use | or to create user mask $who
		}
		$op = $2;
		foreach (split (//, $3)) {
		    $perm = $perm | ${$perm_bits{$_}};
		}
	        if ($op eq "+") { # add permissions
		   if ($perm) {
		       if ($who & 4) {
			   $user_bits = $user_bits | $perm;
			   # | or operation sets unset bits, adding perms
		       }
		       if ($who & 2) {
			   $group_bits = $group_bits | $perm;
		       }
		       if ($who & 1) {
			   $other_bits = $other_bits | $perm;
		       }
		   }
	       } elsif ($op eq "-") { # subtract permissions
		   if ($perm) { # only act if there is a permission set
		       if ($who & 4) {
			   $user_bits = $user_bits & (7 - $perm);
			   # 7 - $perm = bits to leave as they are, others
			   # are zero and get cleared by & and operation
		       }
		       if ($who & 2) {
			   $group_bits = $group_bits & (7 - $perm);
		       }
		       if ($who & 1) {
			   $other_bits = $other_bits & (7 - $perm);
		       }
		   }
	       } else {
		    if (! $who) {
			$who = 7;
		    }
		    if ($who & 4) {
			$user_bits = $perm;
		    }
		    if ($who & 2) {
			$group_bits = $perm;
		    }
		    if ($who & 1) {
			$other_bits = $perm;
		    }
		}
	    } else {
		return $ILLEGAL;
	    }
	}
    }
    return (0 . $user_bits . $group_bits . $other_bits);
}

sub ErrorUsage
{ 
    if ($_[0]) {
	print STDERR "$0 (Perl Power Tools): $_[0]\n";
    }
    print STDERR "usage: $0 [-p] [-m mode] dirname ...\n";
    exit 1; 
}

sub MakeDirectory
{
    my ($dir,$mode) = @_[0,1];
    if (! basename($dir)) {
	$dir .= "x";
	$dir = dirname($dir);
    }
    if ($opt_p) {
	if (! -e dirname($dir)) {
	    if (MakeDirectory(dirname($dir),$orig_mode)) {
		return $!;
	    }
	}
    }
    mkdir ($dir, $mode) or return $!;
    return 0;
}

__END__

=pod

=head1 NAME

B<mkdir> -- make directories

=head1 SYNOPSIS

B<mkdir> [-B<p>] [-B<m> I<mode>] I<directory_name ...>

=head1 DESCRIPTION

B<mkdir> creates the directories names as operands, in the order specified, 
using mode rwxrwxrwx (0777) as modified by the current umask.

=head2 OPTIONS

B<mkdir> accepts the following options:

=over 4

=item -m

Set the file permission bits of the final created directory to 
the specified mode.  The mode argument can be in any of the
formats specified to the chmod utility, though they are I<not> all 
implemented.  (See B<BUGS>.)  If a symbolic mode 
is specified, the operation characters ``+'' and ``-'' are 
interpreted relative to an initial mode of ``a=rwx''.  

=item -p

Create intermediate directories as required.  If this option is
not specified, the full path prefix of each operand must already
exist.  Intermediate directories are created with permission bits
of rwxrwxrwx (0777) as modified by the current umask, plus write
and search permission for the owner.  Do not consider it an error
if the argument directory already exists.

=back

The user must have write permission in the parent directory.

mkdir exits 0 if successful, and >0 if an error occurred.

=head1 ENVIRONMENT

The mode used by B<mkdir> in creating directories is affected by the umask.

=head1 BUGS

B<mkdir> depends on the underlying Perl mkdir function.  On systems without
proper support for mode setting, or without POSIX modes, it does not set
the modes specified.

This B<mkdir> implementation is modelled on the B<OpenBSD> and B<NetBSD> 
variants.  It accepts permission options, as they do, which it does not 
implement: specifically it ignores the set user id, set group id, and 
sticky bit flags.  B<mkdir> collapses the ``X'' and ``x'' options.

=head1 STANDARDS

This B<mkdir> implementation is compatible with the B<NetBSD> implementation.
This implementation has also been based on the B<OpenBSD> manual description.
The B<OpenBSD mkdir> implementation is expected to be compliant with the 
B<IEEE Std1003.2-1992> specification, also known as B<POSIX.2>, so this
implementation may be, as well.

=head1 AUTHOR

The Perl implementation of B<mkdir> was written by James Wetterau, Jr., 
I<jwjr@panix.com>.

=head1 COPYRIGHT and LICENSE

This program is copyright by James Wetterau, Jr., 1999.

This program is free and open software. You may use, copy, modify, distribute
and sell this program (and any modified variants) in any way you wish,
provided you do not restrict others to do the same.

=cut