# # $Header: //guest/tommy_fad/utils/user_management/updatePerforce.pl#1 $ # # Script written by Chriz Bartz and modified by Tommy Fad and Steve Lysohir. See comments below. # use Mail::Sendmail; use p4; use Getopt::Std; use SHA 1.2; use strict; sub logStr; sub vLogStr; sub logErr; sub dieOnP4Err; sub logP4Err; sub dieOnErr; sub logP4Command; # Set the following variable to the email address of who should get # messages about updates to perforce. my $emailNotify = 'licensingperson@youremail, licensingperson2@youreamil'; my $ccAddrs = 'perforceadmins@youremail'; my $fromAddr = '"P4 Agent" '; # You should not have to modify anything below this point. my $helpStr = <] [-l -v] [-a] notesData Supports all p4 usage options except -x and -V (e.g. -u ) -l - log file name -a - update the protections for //Misc/users directories -v - turns on verbose logging -s - SHA file. This is used to store a checksum of the notesData file to avoid talking to perforce if the file hasn't changed. It is put in the logDir directory. notesData - File that contains the data from the notes database in the following format: One item per line. Each item looks like: GROUP,, USER,,, DELETE ME,,, EOT ; my $delUserErrMsg = <reset(); open NOTESCHKSUM, "<$notesChecksum"; my $oldDigest = ; close NOTESCHKSUM; # Read the perforce.txt file my $lineNum = 0; open NOTESDATA, "<$ARGV[0]" or dieOnErr "Can't open $ARGV[0]: $!\n"; while () { $sha->add($_); $lineNum++; s/\n//; split ','; if ($_[0] eq "GROUP") { $_[1] =~ s/ //g; $groupsToAdd{lc $_[1]} = $_[1] unless $groupsToAdd{lc $_[1]}; if ($groupsToAdd{lc $_[1]} ne $_[1]) { my $str = "\nUser \"$_[2]\" listed in group \"$_[1]\".\n" . "\tIt probably should be \"$groupsToAdd{lc $_[1]}\".\n" . "\tUpdate this user in the PerforceUsers database\n"; $errMsg .= $str; logStr $str; } $usersByGroup{$groupsToAdd{lc $_[1]}}{lc $_[2]} = $_[2]; } elsif ($_[0] eq "USER") { $usersToAdd{lc $_[1]}{'username'} = $_[1]; $usersToAdd{lc $_[1]}{'fullname'} = $_[2]; $usersToAdd{lc $_[1]}{'emailAddr'} = $_[3]; } elsif ($_[0] eq "DELETED USER") { $usersToDelete{lc $_[1]}{'username'} = $_[1]; $usersToDelete{lc $_[1]}{'fullname'} = $_[2]; $usersToDelete{lc $_[1]}{'emailAddr'} = $_[3]; } else { dieOnErr "illegal format on line $lineNum in $ARGV[0]\n$_"; } } close NOTESDATA; my $digest = $sha->hexdigest(); if ($digest eq $oldDigest) { finish(); exit; } # build hash of currently defined users from Perforce $p4Output = logP4Command ("$p4 users"); while ($p4Output =~ /(\S+)([^\n]*)\n/g) { $p4DefinedUsers{lc $1} = $1; } # Build hash of all groups in Perforce. $p4Output = logP4Command ("$p4 groups"); while ($p4Output =~ /(.*)\n/g) { $p4DefinedGroups{lc $1} = $1; } # Get list of all open files. open FILES, "p4 $p4 opened -a|" or dieOnErr "Can't open p4 $p4 open -a pipe: $!\n"; while () { my ($file, $user, $client) = m!([^#]*#\d+).*by ([^\@]*)\@([^\s]*)!; push @{ $openFilesByClient{$client}{$user}}, $file; $clientsUsedByUser{$user}{$client} = 1; } close FILES; # Build list of all clients and who owns them from Perforce open CLIENTS, "p4 $p4 clients|" or dieOnErr "Can't p4 clients: $!"; my $clientCount = 0; while () { $clientCount++; print "$clientCount\r"; /Client (\S+)/; my $client = $1; my $clientSpec = logP4Command ("$p4 client -o $client"); my $user = ($clientSpec =~ /\nOwner:\s+(\S+)/g) ? $1 : 'noUser'; push @{ $p4ClientsByUser{lc $user} }, $client; $p4ClientHost{lc $client} = ($clientSpec =~ /\nHost:\s+(\S+)/g) ? $1 : 'noHost'; $p4ClientLocked{lc $client} = ($clientSpec =~ /\nOptions:[^\n]* (locked)/g) ? 'locked' : 'unlocked'; } close CLIENTS; # Delete all the users marked for deletion in the file my $user; foreach $user (sort keys %usersToDelete) { # Check if this user is in usersToAdd also just to make sure. # The database should not allow this but it has in the past. if ($usersToAdd{lc $user}) { logStr "Skipping delete of $user because this user is being added.\n" . "This should not happen but the script has to assume that the\n" . "add is correct and skip the deletion.\n\n"; next; } # Check if this user is in Perforce if ($p4DefinedUsers{lc $user}) { my $userName = $p4DefinedUsers{$user}; my $emailMsg = "Deleting User $userName (" . $usersToDelete{lc $user}{'fullname'} . ")\n"; logStr "Deleting user $userName\n"; open USER, ">$delUsers\\$userName"; print USER logP4Command ("$p4 user -o $userName"); close USER; # Delete clients owned by this user unless other users # have open files on those clients. my $emailErrors = 0; my $clientName; foreach $clientName (@{$p4ClientsByUser{lc $user}}) { logStr "\tDeleting client $clientName\n"; my $clientSpec = p4::p4Command("$p4 client -o $clientName"); open CLIENT, ">$delClients\\$clientName"; print CLIENT $clientSpec; close CLIENT; my $u; my $deleteClient = 1; foreach $u (keys %{$openFilesByClient{$clientName}}) { $emailErrors = 1; if (lc $user ne lc $u) { $emailMsg .= "Can not delete client '$clientName' owned by" . " '$user' because user '$u' has open files on it.\n"; $emailMsg .= "Deleting user '$user' will fail because of this.\n" if $openFilesByClient{$clientName}{$user}; $deleteClient = 0; } $emailMsg .= "Files open by '$u' on client '$clientName':\n\n\t"; $emailMsg .= join "\n\t", @{$openFilesByClient{$clientName}{$u}}; $emailMsg .= "\n\n"; } if ($deleteClient) { $p4Output = logP4Command("$p4 client -fd $clientName"); logStr "\tp4 output: $p4Output\n"; } delete $clientsUsedByUser{$user}{$clientName}; } # Delete all clients *USED* by but not owned by this user unless that # client is also being used by another user. foreach $clientName (keys %{$clientsUsedByUser{$user}}) { logStr "\tDeleting client $clientName\n"; my $clientSpec = p4::p4Command("$p4 client -o $clientName"); open CLIENT, ">$delClients\\$clientName"; print CLIENT $clientSpec; close CLIENT; my $u; my $deleteClient = 1; foreach $u (keys %{$openFilesByClient{$clientName}}) { $emailErrors = 1; if (lc $user ne lc $u) { $emailMsg .= "User '$user' is using client '$clientName'.\n" . "'$user' does NOT own this client and user '$u' also has " . "files open on this client so the client can not be " . "deleted automatically.\n" . "Deleting user '$user' will fail because of this.\n"; $deleteClient = 0; } $emailMsg .= "Files open by '$u' on client '$clientName':\n\n\t"; $emailMsg .= join "\n\t", @{$openFilesByClient{$clientName}{$u}}; $emailMsg .= "\n\n"; } if ($deleteClient) { $p4Output = logP4Command("$p4 client -fd $clientName"); logStr "\tp4 output: $p4Output\n"; } } # Actually delete the user my $p4Output = logP4Command("$p4 user -fd $userName"); logStr "p4 output: $p4Output\n"; if ($p4Output =~ /can't be deleted/) { $emailErrors = 1; $emailMsg .= $p4Output if $p4Output =~ /can't be deleted/; } logStr $emailMsg; if ($emailErrors) { $digest = 0; my %mail = (To => $emailNotify, Cc => $ccAddrs, From => $fromAddr, Subject => "Errors deleting user $userName from Perforce", Message => scalar localtime() . "\n" . $emailMsg ); if (not sendmail %mail) { logStr "Error sending mail: $Mail::Sendmail::error \n"; } logStr "Mail log:\n$Mail::Sendmail::log\n"; } } } # Process groups. For each group in the notes database, check # if the members have changed. If they have, update the group # and list the changes in the email to perforceAdmins. Also # report on new groups and deleted groups. my $group; foreach $group (keys %usersByGroup) { my %currMembers = (); my $groupChanged = 0; # # Compare to old group to find new and deleted members my $groupOutput = logP4Command ("$p4 group -o $group"); my $users = $groupOutput; $groupOutput =~ s/(^.*)\nUsers:.*$/$1/s; $groupOutput =~ s/^.*\n(Group:[^\n]*)\n//s; # Check if the group is empty before looking for users if ($users =~ /\nUsers:.?\n/) { $users =~ s/^.*\nUsers:.?\n//s; while ($users =~ /\s*([^\n]*)/g) { my $user = $1; next unless $user =~ /\S/; $currMembers{lc $user} = $user; if ($usersByGroup{$group}{lc $user} ne $user) { logStr "Removing user $user from group $group\n"; $groupChanged = 1; } } foreach $user (keys %{$usersByGroup{$group}}) { if ($currMembers{$user} ne $usersByGroup{$group}{$user}) { logStr "Adding user $user ($usersToAdd{lc $user}{'fullname'})" . " to group $group\n"; $groupChanged = 1; } } } else { logStr "Creating new group $group with the following members:\n\t"; foreach $user (keys %{$usersByGroup{$group}}) { logStr "\n\t$usersByGroup{$group}{$user}"; } logStr "\n"; $groupChanged = 1; } # Update group if it has changed if ($groupChanged) { logStr "updating group $group\n"; logP4Command("$p4 group -d $group"); open GROUP_PIPE, "|p4 $p4 group -i > $tmpFile" or dieOnErr "Can't open $p4 group -i PIPE: $!"; print GROUP_PIPE "Group:\t$group\n$groupOutput\nUsers:\n\t"; foreach $user (keys %{$usersByGroup{$group}}) { print GROUP_PIPE "\n\t$usersByGroup{$group}{$user}"; } print GROUP_PIPE "\n"; close GROUP_PIPE or dieOnErr "Can't close $p4 group -i PIPE: $!"; open TMP, "<$tmpFile"; logStr "p4 output: " . join "", ; close TMP; } } # Update the allUsers group open GROUP_PIPE, "|p4 $p4 group -i > $tmpFile" or dieOnErr "Can't open $p4 group -i PIPE: $!"; print GROUP_PIPE "Group:\tallUsers\nUsers:\n\t"; foreach $user (keys %usersToAdd) { print GROUP_PIPE "\n\t$usersToAdd{$user}{'username'}"; } print GROUP_PIPE "\n"; close GROUP_PIPE or dieOnErr "Can't close $p4 group -i PIPE: $!"; open TMP, "<$tmpFile"; my $tmpStr = ; close TMP; logStr "p4 output: $tmpStr" unless $tmpStr =~ /not/; # # Update protections for //Misc/Users/... directories # if ($options{'a'}) { open NEW_PROTECT, ">$tmpFile" or logErr "Can't open $tmpFile: $!"; open CURR_PROTECT, "p4 $p4 protect -o|" or logErr "Can't p4 $p4 protect -o: $!"; while () { if (not m!write user \S+ 130\.164\.\* //Misc/users/[^*]*?/\.\.\.!) { print NEW_PROTECT $_; } } close CURR_PROTECT; my $user; foreach $user (sort keys %usersToAdd) { print NEW_PROTECT "\twrite user $usersToAdd{$user}{'username'} 130.164.* " . "//Misc/users/$usersToAdd{$user}{'username'}/...\n"; } close NEW_PROTECT; `p4 $p4 protect -i < $tmpFile` or logErr "Can not set protections: $!\n"; unlink $tmpFile; } # Create/Update all users defined in the database my $key; foreach $key (keys %usersToAdd) { my $user; my $emailMsg = ""; my $emailSubject = ""; if (!$p4DefinedUsers{lc $key}) { $user = $usersToAdd{$key}{'username'}; logStr "Adding user $user ($usersToAdd{$key}{'fullname'})\n"; } else { $user = $p4DefinedUsers{lc $key}; } my $userSpec = logP4Command("$p4 user -o $user"); $userSpec =~ /\nUser:\s+(.*?)\n.*\nEmail:\s+(.*?)\n.*\nFullName:\s+(.*?)\n/s; my $username = $1; my $emailAddr = $2; my $fullName = $3; if ($username ne $usersToAdd{$key}{'username'} || $emailAddr ne $usersToAdd{$key}{'emailAddr'} || $fullName ne $usersToAdd{$key}{'fullname'}) { logStr "Updating user $user (now $usersToAdd{$key}{'username'}) " . "($usersToAdd{$key}{'fullname'}) <$usersToAdd{$key}{'emailAddr'}>\n"; logStr "\tIt was \"$username\" ($fullName) <$emailAddr>\n"; # if ($p4DefinedUsers{lc $key}) # { # $emailSubject = "Perforce user spec modified"; # $emailMsg = # "The Notes P4 Agent has changed your user specification on " . # "perforce server $options{'p'}. Here are the changes:\n" . # " Field\tOld Value\tNew Value\n" . # " Username:\t$username\t$usersToAdd{$key}{'username'}\n" . # " FullName:\t$fullName\t$usersToAdd{$key}{'fullname'}\n" . # " Email: \t$emailAddr\t$usersToAdd{$key}{'emailAddr'}\n\n " . # "This happened either because your entry in the Perforce Users " . # "Database was modified or because your entry in Perforce was " . # "modified.\n"; # } # else # { # $emailSubject = "You have been added to Perforce"; # $emailMsg = # "The Notes P4 Agent has added you to perforce server " . # " $options{'p'} with the following information.\n" . # " Username:\t$usersToAdd{$key}{'username'}\n" . # " FullName:\t$usersToAdd{$key}{'fullname'}\n" . # " Email: \t$usersToAdd{$key}{'emailAddr'}\n"; # } # # Send email to user being added/updated # my %mail = (To => $usersToAdd{$key}{'emailAddr'}, # From => $fromAddr, # Subject => $emailSubject, # Message => scalar localtime() . "\n" . $emailMsg # ); # # if (not sendmail %mail) # { # logStr "Error sending mail: $Mail::Sendmail::error \n"; # } $userSpec =~ s/\nUser:[^\n]*\n/\nUser:\t$usersToAdd{$key}{'username'}\n/s; $userSpec =~ s/\nEmail:[^\n]*\n/\nEmail:\t$usersToAdd{$key}{'emailAddr'}\n/s; $userSpec =~ s/\nFullName:[^\n]*\n/\nFullName:\t$usersToAdd{$key}{'fullname'}\n/s; # Change this to an open PIPE to p4 open PIPE, "|p4 $p4 user -fi > $tmpFile" or logErr "Can't open p4 $p4 user -fi pipe: $!\n"; print PIPE $userSpec; close PIPE or logErr "Can't close p4 $p4 user -fi pipe: $!\n"; open TMP, "<$tmpFile"; logStr "p4 output: " . join "", ; close TMP; } } # Check for users that are in Perforce but are not in the Notes database # at all (neither in the toAdd or toDelete lists). Send email about these. foreach $key (keys %p4DefinedUsers) { if (!$usersToAdd{lc $key} && !$usersToDelete{lc $key}) { my $str = "\nUser \"$p4DefinedUsers{$key}\" is defined on perforce " . "server $options{'p'}\n" . "\tbut is not listed in the Perforce Users database. This\n" . "\tuser should be added to the database or deleted from the\n" . "\tperforce server.\n"; $errMsg .= $str; logStr $str; } } if ($errMsg) { $digest = 0; my %mail = (To => $emailNotify, Cc => $ccAddrs, From => $fromAddr, Subject => "Errors updating perforce from Notes Database", Message => scalar localtime() . "\n" . $errMsg ); if (not sendmail %mail) { logStr "Error sending mail: $Mail::Sendmail::error \n"; } logStr "Mail log:\n$Mail::Sendmail::log\n"; } p4::p4Final(); if ($options{'s'}) { open NOTESCHKSUM, ">$notesChecksum"; print NOTESCHKSUM $digest; close NOTESCHKSUM; } finish (); ## Error checking/logging functions sub logStr { if (!$logFileOpen) { open LOG, ">>$logFile"; print LOG "\n\n------------------------------\n" . "Script started at " . scalar localtime($^T) . "\n"; print LOG "Digest: $digest\n"; $logFileOpen = 1; } print LOG @_; } sub vLogStr { logStr @_ if $options{'v'}; } sub logErr { logStr @_; } sub dieOnP4Err { dieOnErr "Perforce error on command: p4 $_\nerrMsg:\n$p4::errMsg\n" . "errNo: $p4::errNo\nCan not continue after fatal error" if ($p4::errMsg); } sub logP4Err { logErr "Perforce error on command: p4 $_\nerrMsg:\n$p4::errMsg\n" . "errNo: $p4::errNo\n" if $p4::errMsg; } sub dieOnErr { logStr @_; logStr "Can not continue after fatal error\n"; finish (); die @_; } sub logP4Command { my $command = shift; my $output = p4::p4Command($command); vLogStr "Output from p4 $command:\n$output\n"; logErr "Perforce error on command: p4 $command\nerrMsg:\n$p4::errMsg\n" . "errNo: $p4::errNo\n" if $p4::errMsg; return $output; } sub finish { unlink $tmpFile; if ($logFileOpen) { logStr "Script finished at " . scalar localtime() . "\n"; close LOG; } }