Advertisement
justin_hanekom

hometar

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