/
install
executable file
·587 lines (458 loc) · 17.1 KB
/
install
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
#!/usr/bin/perl
=begin metadata
Name: install
Description: install files and directories
Author: Greg Bacon, gbacon@itsc.uah.edu
License: perl
=end metadata
=cut
use strict;
use File::Basename qw(basename dirname);
use Getopt::Std qw(getopts);
my ($VERSION) = '1.3';
my $Program = basename($0);
sub usage {
print <<EOUsage;
$Program (Perl bin utils) $VERSION
Usage: $Program [-CcDps] [-g group] [-m mode] [-o owner] file1 file2
$Program [-CcDps] [-g group] [-m mode] [-o owner] file ... directory
$Program -d [-g group] [-m mode] [-o owner] directory ...
EOUsage
exit 1;
}
$SIG{__DIE__} = sub {
warn @_;
exit 1;
};
# still in Kansas, Toto?
my $Unix = $^O !~ /Win|VMS|DOS|MacOS|OS2/i;
my $Debug = 0;
my $Errors = 0;
# process options
my %opt;
getopts('CcDdf:g:Mm:o:ps', \%opt) or usage();
usage() unless @ARGV;
if ($opt{d} and grep($_, @opt{qw/ C c D p /}) > 0) {
warn "$Program: -d not allowed with -[CcDp]\n";
usage();
}
$opt{C}++ if $opt{p};
$Debug = 1 if $opt{D};
# these probably won't make sense elsewhere
if ($Unix) {
if ($opt{g} and $opt{g} !~ /^\d+$/) {
if (my $gid = getgrnam $opt{g}) {
$opt{g} = $gid;
}
else {
die "$Program: unknown group `$opt{g}'\n";
}
}
if ($opt{o} and $opt{o} !~ /^\d+$/) {
if (my $uid = getpwnam $opt{o}) {
$opt{o} = $uid;
}
else {
die "$Program: unknown user `$opt{o}'\n";
}
}
}
# do stuff
if ($opt{d}) {
install_dirs();
}
else {
install_files();
}
exit($Errors == 0 ? 0 : 1);
sub modify_file {
my($path,$mode,$st) = @_;
if ($mode) {
unless (chmod $mode, $path) {
printf STDERR "$Program: chmod %o $path: $!\n", $mode;
$Errors++;
}
}
if ($opt{o} || $opt{g}) {
my @st = stat $path;
my $uid = $opt{o} || $st[4];
my $gid = $opt{g} || $st[5];
unless (chown $uid, $gid => $path) {
warn "$Program: chown $uid.$gid $path: $!\n";
$Errors++;
}
}
if ($opt{p}) {
unless (utime @{$st}[8,9] => $path) {
warn "$Program: utime $path: $!\n";
$Errors++;
}
}
if ($opt{s} and -B $path) {
if (system "strip", $path) {
warn "$Program: strip $path exited " . ($? >> 8) . "\n";
$Errors++;
}
}
}
sub install_dirs {
my $mode = $opt{m} || '755';
my $symbolic = $mode =~ /^[0-7]{1,4}$/ ? 0 : 1;
# credit Abigail
my @dirs;
my %min;
while (@ARGV) {
my $dir = pop @ARGV;
my $intermediate = 0;
while ($dir ne dirname($dir)) {
my $val = $intermediate++;
push @dirs => [$dir, $val];
if ($val == 0 || !defined($min{$dir}) || $val < $min{$dir}) {
$min{$dir} = $val;
}
$dir = dirname($dir);
}
}
my %seen;
for (reverse @dirs) {
next if $seen{ $_->[0] }++;
$_->[1] = $min{ $_->[0] };
push @ARGV, $_;
}
foreach my $directory (@ARGV) {
my($dir,$implied) = @$directory;
next if $implied && -d $dir;
mkdir $dir, 0755 or die "$Program: mkdir $dir: $!\n";
}
foreach my $directory (@ARGV) {
my($dir,$implied) = @$directory;
next if $implied;
my $bits;
if ($symbolic) {
unless ( $bits = mod($mode, $dir) ) {
die "$Program: invalid mode: $mode\n";
}
$bits = oct $bits;
}
else {
$bits = oct $mode;
}
# do these make sense elsewhere?
modify_file $dir, $bits if !$implied && $Unix;
}
}
sub install_files {
my $dst = pop @ARGV;
my $dir = -d $dst;
if (scalar(@ARGV) == 0) {
die "$Program: missing destination file operand after '$dst'\n";
}
if (!$dir and @ARGV > 1) {
warn "$Program: '$dst' is not a directory\n";
usage();
}
my $mode = $opt{m} || '755';
my $symbolic = ($mode =~ /^[0-7]{1,4}$/) ? 0 : 1;
require File::Copy;
require File::Spec;
foreach my $file (@ARGV) {
my $targ = $dir ? File::Spec->catfile($dst, basename($file)) : $dst;
my @st;
if ($opt{p}) {
unless (@st = stat $file) {
warn "$Program: stat $file: $!\n";
$Errors++;
next;
}
}
if (-d $file) {
warn "$Program: ignoring directory '$file'\n";
$Errors++;
next;
}
if ($opt{C}) {
if (system "cmp", "-s", $file, $targ) {
warn "$Program: copy $file $targ\n" if $Debug;
unless ( File::Copy::copy($file, $targ) ) {
warn "$Program: copy $file $targ: $!\n";
$Errors++;
next;
}
}
else {
warn("$Program: $file not copied to $targ\n") if $Debug;
next;
}
}
else { # default -c
warn "$Program: copy $file $targ\n" if $Debug;
unless ( File::Copy::copy($file, $targ) ) {
warn "$Program: copy $file $targ: $!\n";
$Errors++;
next;
}
}
my $bits;
if ($symbolic) {
unless ( $bits = mod($mode, $targ) ) {
die "$Program: invalid mode: $mode\n";
}
$bits = oct $bits;
}
else {
$bits = oct $mode;
}
modify_file $targ, $bits, \@st if $Unix;
}
}
#
# $Id: SymbolicMode.pm,v 1.1 2004/07/23 20:10:01 cwest Exp $
#
# $Log: SymbolicMode.pm,v $
# Revision 1.1 2004/07/23 20:10:01 cwest
# initial import
#
# Revision 1.1 1999/03/07 12:03:54 abigail
# Initial revision
#
#
sub mod ($$) {
my $symbolic = shift;
my $file = shift;
# Initialization.
# The 'user', 'group' and 'other' groups.
my @ugo = qw /u g o/;
# Bit masks for '[sg]uid', 'sticky', 'read', 'write' and 'execute'.
# Can't use qw // cause silly Perl doesn't know '2' is a number
# when dealing with &= ~$bit.
my %bits = (s => 8, t => 8, r => 4, w => 2, x => 1);
# For parsing.
my $who_re = '[augo]*';
my $action_re = '[-+=][rstwxXugo]*';
# Find the current permissions. This is what we start with.
my $mode = sprintf "%04o" => (stat $file) [2] || 0;
my $current = substr $mode => -3; # rwx permissions for ugo.
my %perms;
@perms {@ugo} = split // => $current;
# Handle the suid, guid and sticky bits.
# It looks like permission are 4 groups of 3 bits, groups for user,
# group and others, and a group for the special flags, but they are
# really 3 groups of 4 bits. Or maybe not.
# Anyway, this function is greatly simplified by treating them as
# 3 4-bit groups. The highest bit will be "special" one. suid for
# the users group, guid for the group group, and the sticky bit
# for the others group.
my $special = substr $mode => -4, 1;
my $bit = 1;
foreach my $c (reverse @ugo) {
$perms {$c} |= 8 if $special & $bit;
$bit <<= 1;
}
# Keep track of the original permissions.
my %orig = %perms;
# Find the umask setting, and store the bits for each group
# in a hash.
my %umask; # umask bits.
@umask {@ugo} = split // => sprintf "%03o" => umask;
# Time to parse...
foreach my $clause (split /,/ => $symbolic) {
# Perhaps we should die if we can't parse it?
return undef unless
my ($who, $actions) =
$clause =~ /^($who_re)((?:$action_re)+)$/o;
# We would rather split the different actions out here,
# but there doesn't seem to be a way to collect them.
# /^($who_re)($action_re)+/ only gets the last one.
# Now, we have to reparse in later.
my %who;
if ($who) {
$who =~ s/a/ugo/; # Ignore multiple 'a's.
@who {split // => $who} = undef;
}
# @who will contain who these settings applies to.
# if who isn't set, it might be masked with the umask,
# hence, this isn't the final decision.
# Maybe we don't need this.
my @who = $who ? keys %who : @ugo;
foreach my $action (split /(?=$action_re)/o => $actions) {
# The first character has to be the operator.
my $operator = substr $action, 0, 1;
# And the rest are the permissions.
my $perms = substr $action, 1;
# BSD documentation says 'X' is to be ignored unless
# the operator is '-'. GNU, HP, SunOS and Solaris handle
# '-' and '=', while OpenBSD ignores only '-'.
# Solaris, HP and OpenBSD all turn a file with permission
# 666 to a file with permission 000 if chmod =X is
# is applied on it. SunOS and GNU act as if chmod = was
# applied to it. I cannot find out what the reasoning
# behind the choices of Solaris, HP and OpenBSD is.
# GNU and SunOS seem to ignore the 'X', which, after
# careful studying of the documentation seems to be
# the right choice.
# Therefore, remove any 'X' if the operator ain't '+';
$perms =~ s/X+//g unless $operator eq '+';
# If there are no permissions, things are simple.
unless ($perms) {
# Things like u+ and go- are ignored; only = makes sense.
next unless $operator eq '=';
# Clear permissions on u= and go=.
if ($who) {@perms {keys %who} = (0) x 3;}
# '=' is special. Sets permissions to the umask.
else {%perms = %umask;}
next;
}
if ($operator eq '=') {
%perms = ( 'u' => 0, 'g' => 0, 'o' => 0);
}
# If we arrive here, $perms is a string.
# We can iterate over the characters.
foreach (split // => $perms) {
if ($_ eq 'X') {
# We know the operator eq '+'.
# Permission of `X' is special. If used on a regular file,
# the execution bit will only be turned on if any of the
# execution bits of the _unmodified_ file are turned on.
# That is,
# chmod 600 file; chmod u+x,a+X file;
# should result in the file having permission 700, not 711.
# GNU and SunOS get this wrong;
# Solaris, HP and OpenBSD get it right.
next unless -d $file || grep {$orig {$_} & 1} @ugo;
# Now, do as if it's an x.
$_ = 'x';
}
if (/[st]/) {
# BSD man page says operations on 's' and 't' are to
# be ignored if they operate only on the "other" group.
# GNU and HP happely accept 'o+t'. Sun rejects 'o+t',
# but also rejects 'g+t', accepting only 'u+t'.
# OpenBSD acceps both 'u+t' and 'g+t', ignoring 'o+t'.
# We do too.
# OpenBSD however, accepts 'o=t', clearing all the bits
# of the "other" group.
# We don't, as that doesn't make any sense, and doesn't
# confirm to the documentation.
next if $who =~ /^o+$/;
}
# Determine the $bit for the mask.
my $bit = /[ugo]/ ? $orig {$_} & ~8 : $bits {$_};
die "Weird permission `$_' found\n" unless defined $bit;
# Should not happen.
# Determine the set on which to operate.
my @set = $who ? @who : grep {!($umask {$_} & $bit)} @ugo;
# If the permission is 's', don't operate on the other group.
# Unless the operator was '='. But in that case, don't set
# the 8 bit for 'other'.
my $equal_s;
if (/s/) {
if ($operator eq '=') {$equal_s = 1;}
else {@set = grep {!/o/} @set or next;}
}
# If the permission is 't', only operate on the other group;
# regardless what the 'who' settings are.
# Note that for a directory with permissions 1777, and a
# umask of 002, a chmod =t on HP and Solaris turn the
# permissions to 1000, GNU and SunOS turn the permissiosn
# to 1020, while OpenBSD keeps 1777.
/t/ and @set = qw /o/;
# Apply.
foreach my $s (@set) {
do {$perms {$s} |= $bit; next} if ($operator eq '+' || $operator eq '=') ;
do {$perms {$s} &= ~$bit; next} if $operator eq '-';
die "Weird operator `$operator' found\n";
# Should not happen.
}
# Special case '=s'.
$perms {o} &= ~$bit if $equal_s;
}
}
}
# Now, translate @perms to an *octal* number.
# First, deal with the suid, guid, and sticky bits by collecting
# the high bits of the ugo permissions.
my $first = 0;
$bit = 1;
for my $c (reverse @ugo) {
if ($perms {$c} & 8) {$first |= $bit; $perms {$c} &= ~8;}
$bit <<= 1;
}
join "" => $first, @perms {@ugo};
}
__END__
=pod
=head1 NAME
install - install files and directories
=head1 SYNOPSIS
B<install> [B<-CcDps>] [B<-g> I<group>] [B<-m> I<mode>] [B<-o> I<owner>] I<file1> I<file2>
B<install> [B<-CcDps>] [B<-g> I<group>] [B<-m> I<mode>] [B<-o> I<owner>] I<file> ... I<directory>
B<install> B<-d> [B<-g> I<group>] [B<-m> I<mode>] [B<-o> I<owner>] I<directory> ...
=head1 DESCRIPTION
B<install> copies files to the
target path specified by I<file2> or I<directory>. Alternatively, if
B<-d> is specified, B<install> creates directories (also creating missing
parent directories as necessary, similar to B<mkdir -p>).
B<install> accepts these options:
=over 4
=item B<-C>
Copy the file only if it differs from the target (according to
B<cmp>).
=item B<-c>
Copy the file. This option is provided for compatibility and is the
default.
=item B<-D>
Give debugging information. If specified once, B<install> will warn
about impending copies or moves. If specified more than once, B<install>
will warn when it does not install files due to B<-C>.
=item B<-d>
Create directories (creating missing parent directories as needed,
similar to B<mkdir -p>). When creating parent directories, the implied
directories are created with the default creation mask 0755 (modified
by your umask). Only those directories explicitly provided on the
command line take the permissions specified by B<-m>. This behavior
imitates that of BSD install(1).
=item B<-f>
Specify the target's file flags, i.e. B<-f> I<flags>. This option is
only provided for compatibility and does not affect the execution of
B<install>.
=item B<-g>
Specify the group to which the target file should belong. Both numeric
and mnemonic group IDs are acceptable.
=item B<-M>
Do not use mmap(2). This option is only provided for compatibility and
does not affect the execution of B<install>.
=item B<-m>
Specify the target file's mode. Either octal modes or symbolic modes
are acceptable. See the documentation for the I<PerlPowerTools::SymbolicMode> module
for details on acceptable symbolic modes. The default mode (used in
absence of B<-m> is 0755). When specifying a symbolic mode, keep in
mind that all directories are created with the default creation mask
0755 (as modified by your umask), so it is probably best to use
absolute symbolic permissions (e.g. C<u=rwx,g=rx,o=rx>) as opposed
to relative symbolic permissions (e.g. C<ugo+x>).
=item B<-o>
Specify the owner to whom the target should belong. Both numeric and
mnemonic user IDs are acceptable.
=item B<-p>
Preserve modification time. This option implies B<-C>.
=item B<-s>
Invoke strip(1) on installed binaries.
=back
=head1 ENVIRONMENT
No environment variables affect the execution of B<install>.
=head1 CAVEATS
The combination of creation of and setting permissions for files and
directories is not atomic, so there are lots of possibilities for
race conditions. If you are really concerned about this, use a umask
of 77.
=head1 AUTHOR
The Perl implementation of B<install> was written by Greg Bacon
E<lt>I<gbacon@itsc.uah.edu>E<gt> as part of the ADaM Project.
=head1 COPYRIGHT and LICENSE
Copyright 1999 UAH Information Technology and Systems Center.
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 from doing the same.
=head1 SEE ALSO
umask(2), chmod(1), mkdir(1), chown(8), chgrp(8), strip(1)
=cut