Advertisement
justin_hanekom

hometar

Mar 23rd, 2019 (edited)
297
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Perl 10.15 KB | None | 0 0
  1. #!/usr/bin/env perl
  2.  
  3. use Modern::Perl '2009';    # Enable Perl 5.10 features
  4. use strict;                 # Included so that percritic does not complain
  5. use warnings;               #                 ditto
  6. use 5.010;                  # Only works under Perl 5.10 and later
  7. use autodie qw( :all );     # Make system functions either succeed or die
  8. use strictures 2;           # Turn on strict and make most carpings fatal
  9.  
  10. use Carp;
  11. use Cwd;
  12. use English;
  13. use Getopt::Euclid;
  14. use POSIX;
  15. use Readonly;
  16.  
  17. ##############################################################################
  18. # Usage      : remove_old_tars( \%options_ref );
  19. # Purpose    : Only keeps the newest archives that match the given pattern
  20. #              if options_ref->{'remove'} is true
  21. # Returns    : Nothing
  22. # Parameters : $options_ref - a reference to a hash table containing:
  23. #                   'pattern':  the glob pattern of preexisting tar files
  24. #                   'keep':     the number of existsing tars to keep
  25. #                   'remove':   whether or not to proceed with the removal of
  26. #                               obsolete tar files
  27. #                   'verbose':  whether or not to output text describing
  28. #                               non-fatal events
  29. sub remove_old_tars {
  30.     my $options_ref = shift
  31.         or croak 'No options hash reference supplied';
  32.     foreach my $key (qw( pattern keep remove verbose )) {
  33.         if ( !defined $options_ref->{$key} ) {
  34.             croak "Required hash key '$key' not given";
  35.         }
  36.     }
  37.  
  38.     if ( $options_ref->{'remove'} ) {
  39.         my $is_verbose    = $options_ref->{'verbose'};
  40.         my @files         = glob $options_ref->{'pattern'};
  41.         my $num_to_remove = @files - $options_ref->{'keep'};
  42.  
  43.         foreach my $file ( splice( @files, 0, $num_to_remove ) ) {
  44.             eval {
  45.                 unlink $file;
  46.                 say "Removed: $file" if $is_verbose;
  47.             };
  48.             if ($EVAL_ERROR) {
  49.                 carp "Unable to remove: $file - $EVAL_ERROR";
  50.             }
  51.         }
  52.     }
  53.     return;
  54. }
  55.  
  56. ##############################################################################
  57. # Usage      : tar_src_dir( \%options );
  58. # Purpose    : Archives the source directory to the destination tar
  59. # Returns    : Nothing
  60. # Parameters : $options_ref - a reference to a hash table containing:
  61. #                   'src_dir':  the source directory to be tarred
  62. #                   'dest_tar': the tar file to create
  63. #                   'verbose':  whether or not to output text describing
  64. #                               non-fatal events
  65. sub tar_src_dir {
  66.     my $options_ref = shift
  67.         or croak 'No options hash reference supplied';
  68.     foreach my $key (qw( src_dir dest_tar verbose )) {
  69.         if ( !defined $options_ref->{$key} ) {
  70.             croak "Required hash key '$key' not given";
  71.         }
  72.     }
  73.     my $src_dir    = $options_ref->{ 'src_dir'  };
  74.     my $dest_tar   = $options_ref->{ 'dest_tar' };
  75.     my $is_verbose = $options_ref->{ 'verbose'  };
  76.  
  77.     Readonly my $TAR_FLAGS => join(
  78.         ' ', qw(
  79.             --create
  80.             --preserve-permissions
  81.             --atime-preserve
  82.             --use-compress-program='pigz'
  83.             )
  84.     );
  85.  
  86.     # Determine the home directory
  87.  
  88.     my $index = rindex($src_dir, '/');
  89.     my $base_dir = substr( $src_dir, 0, $index);
  90.     my $home_dir = substr( $src_dir, $index + 1);
  91.  
  92.     # Using cd instead of tar's -C (i.e., directory) flag because I was
  93.     # getting the unexpected result of only the Desktop directory being
  94.     # archived
  95.  
  96.     my $original_dir = getcwd;
  97.     eval { Cwd::chdir($base_dir); };
  98.     if ($EVAL_ERROR) {
  99.         croak "Unable to chdir to: $base_dir: $EVAL_ERROR";
  100.         exit 1;
  101.     }
  102.  
  103.     # Tar the source dir to $dest_tar
  104.  
  105.     eval {
  106.         if ($is_verbose) {
  107.             my $tar_cmd = "sudo tar $TAR_FLAGS --verbose";
  108.             system( qq{$tar_cmd --file = "$dest_tar" $home_dir} );
  109.         }
  110.         else {
  111.             my $tar_cmd = "sudo tar $TAR_FLAGS";
  112.             system( qq{$tar_cmd --file = "$dest_tar" $home_dir &>/dev/null} );
  113.         }
  114.     };
  115.     if ($EVAL_ERROR) {
  116.         croak "Unable to create: $dest_tar: $EVAL_ERROR";
  117.         exit 2;
  118.     }
  119.  
  120.     # And return to the original directory
  121.  
  122.     Cwd::chdir($original_dir);
  123.     return;
  124. }
  125.  
  126. ##############################################################################
  127. # Usage      : change_file_owner_group( \%options );
  128. # Purpose    : Changes the owner and group of the named file
  129. # Returns    : Nothing
  130. # Parameters : $options_ref - a reference to a hash table containing:
  131. #                   'filename': the name of the file whose ownership is to
  132. #                               be changed
  133. #                   'user':     the name of the owner and group to assign to
  134. #                               filename
  135. #                   'verbose':  whether or not to output text describing
  136. #                               non-fatal events
  137. #                   'group':    name of the group to assign to filename;
  138. #                               same as user if not given
  139. sub change_file_owner_group {
  140.     my $options_ref = shift
  141.         or croak 'No options hash reference supplied';
  142.     foreach my $key (qw( filename user verbose )) {
  143.         if ( !defined $options_ref->{$key} ) {
  144.             croak "Required hash key '$key' not given";
  145.         }
  146.     }
  147.     my $filename   = $options_ref->{ 'filename' };
  148.     my $user       = $options_ref->{ 'user'     };
  149.     my $is_verbose = $options_ref->{ 'verbose'  };
  150.     my $group      = $options_ref->{ 'group'    };
  151.     if ( !defined $group ) {
  152.         $group = $user;
  153.     }
  154.  
  155.     eval {
  156.         # Get the user id (uid) and group id (gid) of $user
  157.  
  158.         my $uid = getpwnam $user;
  159.         my $gid = getgrnam $group;
  160.  
  161.         # Change the uid and gid of the given filename
  162.  
  163.         chown $uid, $gid, $filename;
  164.  
  165.         if ($is_verbose) {
  166.             say "Changed ownership of '$filename' to $user:$group";
  167.         }
  168.     };
  169.     if ($EVAL_ERROR) {
  170.         croak "Unable to chown $filename to $user:$group - $EVAL_ERROR";
  171.         exit 3;
  172.     }
  173.     return;
  174. }
  175.  
  176. ##############################################################################
  177. # Usage      : my $fn = generate_tar_name( $dest_dir, $prefix, $suffix);
  178. # Purpose    : Generates a unique fully qualified tar filename
  179. # Returns    : Returns the generated tar name
  180. # Parameters : $dest_dir - directory name to store tar file
  181. #              $prefix   - prefix of the tar file name
  182. #              $suffix   - suffix of the tar file name
  183. sub generate_tar_name {
  184.     my ( $dest_dir, $prefix, $suffix ) = @_;
  185.     my $timestamp = POSIX::strftime( '%Y%m%d%H%M%S', localtime );
  186.     return $dest_dir . '/' . $prefix . $timestamp . $suffix;
  187. }
  188.  
  189. ##############################################################################
  190. # Usage      : chomp_dir $directory;
  191. # Purpose    : Removes any trailing slashes from the given directory
  192. # Returns    :
  193. # Parameters : $dir - directory from which to chomp terminating slashes
  194. sub chomp_dir {
  195.     my $dir = shift;
  196.     $dir =~ s{              # substitute
  197.                  /+         # any number of slashes
  198.                  $          # that occur at the end of the string
  199.              }
  200.              {}xms;         # replacing them with nothing
  201.     return $dir;
  202. }
  203.  
  204. # Retrieve the command-line arguments from %ARGV
  205. # (Getopt::Euclid has already parsed the command-line and placed the results
  206. # in %ARGV).
  207.  
  208. my $user       = $ARGV{ '--user'    };
  209. my $src_dir    = chomp_dir( $ARGV{'--srcdir'} );
  210. my $dest_dir   = chomp_dir( $ARGV{'--destdir'} );
  211. my $prefix     = $ARGV{ '--prefix'  };
  212. my $suffix     = $ARGV{ '--suffix'  };
  213. my $keep       = $ARGV{ '--keep'    };
  214. my $is_remove  = $ARGV{ '--remove'  };
  215. my $is_verbose = $ARGV{ '--verbose' };
  216.  
  217. # Keep only the last $option_of{'keep'}-1 archives if $is_remove is true
  218.  
  219. remove_old_tars(
  220.     {   'pattern' => "$dest_dir/$prefix*$suffix",
  221.         'keep'    => $keep - 1,     # subtract 1: we will create a new one
  222.         'remove'  => $is_remove,
  223.         'verbose' => $is_verbose,
  224.     }
  225. );
  226.  
  227. # Archive the source directory to a tar in the destination dir
  228.  
  229. my $dest_tar = generate_tar_name( $dest_dir, $prefix, $suffix );
  230. tar_src_dir(
  231.     {   'src_dir'  => $src_dir,
  232.         'dest_tar' => $dest_tar,
  233.         'verbose'  => $is_verbose,
  234.     }
  235. );
  236.  
  237. # Change the owner and group of the generated archive file
  238.  
  239. change_file_owner_group(
  240.     {   'filename' => $dest_tar,
  241.         'user'     => $user,
  242.         'verbose'  => $is_verbose,
  243.     }
  244. );
  245.  
  246. __END__
  247.  
  248. =head1 NAME
  249.  
  250. hometar - Archives (tars) the contents of a user's home directory
  251.  
  252. =head1 VERSION
  253.  
  254. This documentation refers to hometar version 1.0.0
  255.  
  256. =head1 USAGE
  257.  
  258. hometar [options]...
  259.  
  260. =head1 REQUIRED ARGUMENTS
  261.  
  262. =over
  263.  
  264. =item -[-]u[ser] [=] <user>
  265.  
  266. Specify the name of the owner of the archive file. The archive files user and
  267. group will be set to this value
  268.  
  269. =item -[-]s[rcdir] [=] <srcdir>
  270.  
  271. Specify the home directory to be archived
  272.  
  273. =item -[-]d[estdir] [=] <destdir>
  274.  
  275. Specify the directory into which the archive file is to be saved
  276.  
  277. =back
  278.  
  279. =head1 OPTIONS
  280.  
  281. =over
  282.  
  283. =item -[-]p[refix]  [=] <prefix>
  284.  
  285. Specify the prefix to use for the created archive file. The default is
  286. <prefix.default>
  287.  
  288. =for Euclid:
  289.     prefix.default: 'home_'
  290.  
  291. =cut
  292.  
  293. =item -x [=] <suffix> | --suffix [=] <suffix>
  294.  
  295. Specify the suffix of the created archive file. The default is
  296. 'suffix.default'
  297.  
  298. =for Euclid:
  299.  suffix.default: <.tar.gz>
  300.  
  301. =cut
  302.  
  303. =item -[-]k[eep]  [=] <keep>
  304.  
  305. Specify the number of archives to keep, including the newly created archive.
  306. The default is <keep.default>
  307.  
  308. =for Euclid:
  309.     keep.type: +int
  310.     keep.default: 5
  311.  
  312. =cut
  313.  
  314. =item -[-]r[emove]  [=] [<remove>]
  315.  
  316. Specify this to enable removal of obsolete archives
  317.  
  318. =for Euclid:
  319.     remove.type: int
  320.     remove.default: 0
  321.     remove.opt_default: 1
  322.  
  323. =cut
  324.  
  325. =item -[-]v[erbose]  [=] [<verbose>]
  326.  
  327. Specify this to get output of the archival process
  328.  
  329. =for Euclid:
  330.     verbose.type: int
  331.     verbose.default: 0
  332.     verbose.opt_default: 1
  333.  
  334. =cut
  335.  
  336. =back
  337.  
  338. =head1 COPYRIGHT
  339.  
  340. Copyright (c) 2019 Justin Hanekom <justin_hanekom@yahoo.com>
Tags: perl
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement