#!/usr/bin/perl # migrate-samba-dosattrs # v0.2 # by Sean E. Millichamp # 2006-04-27 # 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::Open2; our $VERSION = '0.2'; our $GETFATTR = '/usr/bin/getfattr'; our $SETFATTR = '/usr/bin/setfattr'; our %opts; getopts('vxnrcf', \%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{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 [-vxnr] 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 " -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"; 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 = 0; if($have_extattr) { $has_DOSATTRIB = 1 if(getfattr($filename, 'user.DOSATTRIB')); } else { my ($chld_in, $chld_out); my $pid = open2($chld_out, $chld_in, $GETFATTR, $filename); waitpid($pid,0); $has_DOSATTRIB = 1 if (grep(/user.DOSATTRIB/, $chld_out)); } if($has_DOSATTRIB) { if($opts{v} && $opts{f}) { print "$filename: DOSATTRIB already set, force set so processing anyway\n"; } elsif($opts{v} && !$opts{f}) { print "$filename: DOSATTRIB already set, skipping\n"; } return unless($opts{f}); } 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 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); # group x-bit maps to 0x04, the SYSTEM attribute $attrib += 0x04 if ($mode & S_IXGRP); # other x-bit maps to 0x02, the HIDDEN attribute $attrib += 0x02 if ($mode & S_IXOTH); } # 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 no attribute has been set up to this point then return. return unless ($attrib > 0); # 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); my $pid = open2($chld_out, $chld_in, $SETFATTR, '-n', 'user.DOSATTRIB', '-v', "\"$attrhex\"", $filename); 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}) { $mode = $mode & 07666; 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 [-vnrx] 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<-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. NOTE: You probably do not want to use this after previously running this script with the -x option. =item B<-n> Make no changes to the filesystem. Useful with -v to see what would have happened. =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 BUGS 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