#!/usr/bin/perl # migrate-samba-dosattrs # v0.5 # by Sean E. Millichamp # 2011-05-24 # Notes: # This program has been unmaintained since v0.4 in 2006. However, I # expect that it should still work as intended. Patches welcome. # ChangeLog # v0.5: Update contact information # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. use warnings; use strict; use Getopt::Std; use Fcntl ':mode'; use File::Find; use IPC::Open3; our $VERSION = '0.5'; our $GETFATTR = '/usr/bin/getfattr'; our $SETFATTR = '/usr/bin/setfattr'; our %opts; getopts('vxnrcfoAHSD', \%opts); our $have_extattr = 0; if(!$opts{'c'}) { eval { require File::ExtAttr; if(defined $File::ExtAttr::VERSION) { import File::ExtAttr qw(getfattr setfattr); $have_extattr = 1; if($opts{v}) { print "Using File::ExtAttr\n"; } } }; } if(!$have_extattr) { unless(-x $GETFATTR && -x $SETFATTR) { print "Can't find getfattr/setfattr\n"; exit(1); } } if(! ($opts{A} || $opts{S} || $opts{H} || $opts{D})) { print "Need at least one of: -A, -S, -H, or -D\n\n"; HELP_MESSAGE(); } if($opts{f} && $opts{o}) { print "Options -f and -o are mutually exclusive\n\n"; HELP_MESSAGE(); } if ($opts{r}) { find({ wanted=> \&process_file, no_chdir=>1 }, @ARGV); } else { foreach my $filename (@ARGV) { process_file($filename); } } sub HELP_MESSAGE { print "migrate-samba-dosattrs $VERSION -- migrate Samba DOS attributes\n"; print "Usage: migrate-samba-dosattrs [-vxnrASHDfoc] path...\n"; print " -v Be verbose and display actions taken\n"; print " -n Make no changes. Use with -v to see what would happen\n"; print " -x Reset the x-bits after processing\n"; print " -r Recurse through any directories found\n"; print " -A Process the archive (user) x-bit if found\n"; print " -S Process the system (group) x-bit if found\n"; print " -H Process the hidden (other) x-bit if found\n"; print " -D Mark directories with the directory DOSATTRIB\n"; print " -f Force overwriting of existing DOSATTRIB entries\n"; print " -o Perform logical OR with existing DOSATTRIB entries\n"; print " -c Use command-line versions of getfattr and setfattr, even if\n"; print " the File::ExtAttr module is found\n"; print " --help Display this help\n"; print " --version Display version information\n"; print "\n"; print "This program also contains embedded Perl Pod documentation.\n"; print "Run 'perldoc $0' to view it.\n"; exit(0); } sub VERSION_INFORMATION { print "migrate-samba-dosattrs $VERSION\n"; exit(0); } sub process_file { # find passes the filename as $_ my $filename = $_ || shift; if (! -e $filename) { print "Error: file \"$filename\" does not exist\n"; return; } if (! -w $filename) { print "Error: \"$filename\" is not writable for the current user\n"; return; } # If we aren't forcing the operation, check and see if there is already # a user.DOSATTRIB attribute set. If so, skip. my $has_DOSATTRIB = undef; if($have_extattr) { my $attr = getfattr($filename, 'user.DOSATTRIB'); if($attr) { $has_DOSATTRIB = hex($attr); } } else { my ($chld_in, $chld_out, $chld_err); my $pid = open3($chld_in, $chld_out, $chld_err, $GETFATTR, '-n', 'user.DOSATTRIB', $filename); while(<$chld_out>) { if( /user.DOSATTRIB=\"(0x[0-9A-Fa-f]{1,2})\"/ ) { $has_DOSATTRIB = hex($1); } } close($chld_err) if ($chld_err); close($chld_in); close($chld_out); waitpid($pid,0); } if($has_DOSATTRIB && !($opts{f} || $opts{o})) { if($opts{v}) { printf("$filename: DOSATTRIB already set to 0x%x, skipping\n",$has_DOSATTRIB); } return; } my $attrib = 0; my $mode; # If it is a directory, set the directory attribute and nothing else if (-d $filename) { # 0x10 is DIR attribute $attrib += 0x10 if ($opts{'D'}); } # If it is a regular file, check the x-bits elsif (-f $filename) { $mode = (stat($filename))[2]; # user x-bit maps to 0x20, the ARCHIVE attribute $attrib += 0x20 if (($mode & S_IXUSR) && $opts{'A'}); # group x-bit maps to 0x04, the SYSTEM attribute $attrib += 0x04 if (($mode & S_IXGRP) && $opts{'S'}); # other x-bit maps to 0x02, the HIDDEN attribute $attrib += 0x02 if (($mode & S_IXOTH) && $opts{'H'}); } # If it is neither a file nor a directory then skip it else { print "$filename: not a directory or regular file, skipping\n"; return; } # If -o is set then we need to clear the bits we are processing on # before we OR with the new attrib to make sure things get set right if($opts{o} && $has_DOSATTRIB) { my $attribmask = 0xFF; $attribmask &= ~(0xFF & 0x10) if ($opts{'D'}); $attribmask &= ~(0xFF & 0x20) if ($opts{'A'}); $attribmask &= ~(0xFF & 0x04) if ($opts{'S'}); $attribmask &= ~(0xFF & 0x02) if ($opts{'H'}); $attrib = (($has_DOSATTRIB & $attribmask) | $attrib); # If the existing one matches the calculated one then return if($has_DOSATTRIB == $attrib) { print "$filename: New DOSATTRIB matches old DOSATTRIB, skipping\n" if ($opts{v}); return; } } # If no attribute has been set up to this point then return unless # The file already has a DOSATTRIB and force is set. return unless ($attrib > 0 || ($has_DOSATTRIB && $opts{f})); # Samba needs user.DOSATTRIB stored as a string: my $attrhex = sprintf("0x%x", $attrib); if ($opts{v}) { print "$filename: setting user.DOSATTRIB to $attrhex\n"; } # Note: the setfattr command line tool needs to see the string # quoted or else it will assume you meant to set the value # 0xNN as opposed to the string "0xNN" into the attr if (!$opts{n}) { if($have_extattr) { setfattr($filename, 'user.DOSATTRIB', $attrhex); } else { my ($chld_in, $chld_out, $chld_err); my $pid = open3($chld_in, $chld_out, $chld_err, $SETFATTR, '-n', 'user.DOSATTRIB', '-v', "\"$attrhex\"", $filename); close($chld_in); close($chld_out); close($chld_err); waitpid($pid,0); if($? ne 0) { warn "$SETFATTR returned non-zero error code"; return; } } } # If this is a Samba-only share, or a share without any Unix executable # programs, then you very likely want the x-bits to be cleared after the # user.DOSATTRIB EA has been set. if (-f $filename && $opts{x}) { # Set a mask to clear all x-bits my $mask = 07666; # Add them back in though if we weren't processing on them $mask |= 00100 if (!$opts{'A'}); $mask |= 00010 if (!$opts{'S'}); $mask |= 00001 if (!$opts{'H'}); $mode = $mode & $mask; if ($opts{v}) { printf("$filename: resetting mode to %04o\n",$mode); } if (!$opts{n}) { chmod $mode, $filename; } } } =head1 NAME migrate-samba-dosattrs =head1 SYNOPSIS migrate-samba-dosattrs [-AHSDvnrxcfo] path [path...] =head1 DESCRIPTION migrate-samba-dosattrs is a utility program designed to help system administrators migrate from Samba shares which use the old 'map archive', 'map hidden', and 'map system' smb.conf parameters to map DOS attributes onto the corresponding execute-bits onto the new Extended Attribute-based system used by the 'store dos attributes' smb.conf option. It also tags directories it finds with the DOS 'directory' attribute. As Samba could not map onto execute bits for directories it does not attempt any conversion of other attributes for any directory. It depends on either the setfattr and getfattr command line utilities OR the Perl module File::ExtAttr. Using File::ExtAttr is faster and recommended but if you don't have File::ExtAttr installed (and don't wish to install it) then it will fall back on using the command-line tools. =head1 OPTIONS =over =item B<-A> Processes any Archive (user) executable bits found on a file. You should probably set this if your share used 'map archive = yes' in smb.conf. =item B<-S> Processes any System (group) executable bits found on a file. You should probably set this if your share used 'map system = yes' in smb.conf. =item B<-H> Processes any Hidden (other) executable bits found on a file. You should probably set this if your share used 'map hidden = yes' in smb.conf. =item B<-D> Adds a DOS 'Directory' attribute to any directory found. =item B<-c> Use command-line versions of getfattr and setfattr, even if the File::ExtAttr module is found. You probably don't want to use this unless you are testing something. =item B<-f> Normally a file is checked and it is skipped if a user.DOSATTRIB already exists. If you are sure that you want to re-process any files with existing user.DOSATTRIB EAs then you can use this option to do so. This will REPLACE the existing user.DOSATTRIB EA. If you want to perform a logical OR with an existing user.DOSATTRIB see B<-o>. NOTE: You probably do not want to use this after previously running this script with the B<-x> option. =item B<-n> Make no changes to the filesystem. Useful with -v to see what would have happened. =item B<-o> Normally a file is checked and it is skipped if a user.DOSATTRIB EA already exists. If you want to perform a logical OR with existing values you can use this switch. Internally, if it finds an existing DOSATTRIB EA the program will take 0xFF, mask out the bits corresponding to the attributes being set during this run, then AND that with the existing EA, and then OR that with the attributes determined by the x-bits selected with the -[AHSD] command line options. =item B<-r> Recurse through any directories found and process their contents. =item B<-v> Be verbose and display any actions taken to STDOUT =item B<-x> Clear the execute bits on all regular files after processing. This leaves the execute bits intact on directories. =item B<--help> Displays a usage summary. =item B<--version> Displays the current version of this program. =back =head1 EXAMPLE If you have a Samba Windows profile share using 'map archive = yes', 'map system = yes', and 'map hidden = yes' in smb.conf rooted at the path: /home/samba/profiles. You want to convert all mapped x-bits and tag directories with the DOS directory attribute and then clear all the x-bits from regular files and verbosely watch what it is doing: First, run to test: # migrate-samba-dosattrs -AHSD -r -n -v -x /home/samba/profiles View the output and make sure that it looks like you will be happy with the results. When you are ready to actually make the changes: # migrate-samba-dosattrs -AHSD -r -v -x /home/samba/profiles =head1 BUGS This tool does no explicit checking to make sure that you have extended attributes enabled for your filesystem. It is up to you to make sure this is working first. The paths for the getfattr and setfattr command-line tools are hard-coded into the script. I decided that this was maybe a better default then letting it find whatever might be in the user's path. This program has only been tested on Red Hat Enterprise Linux 4 and only with the ext3 filesystem extended attributes. The behavior is unknown on any other platform. =head1 AUTHOR =over =item Sean E. Millichamp =back