Firmware Updates Redux

Mac firmware updates generally need some sort of user intervention in order to apply them.

This makes it very difficult to automate the process. I did manage at one point to automate SMC updates, but EFI updates and other hardware (keyboards, trackpads, graphics) each have their own issues.

So I finally decided to just punt on the issue. Here’s what I do now: a script runs at login and checks softwareupdate, looking for available firmware updates. If there are any, the user is notified to call the help desk.
Continue reading “Firmware Updates Redux”

Firmware Updates Redux

Radmind: converting to case-insensitive transcripts


Radmind, being a set of UNIX tools, originally supported only case-sensitive transcripts. Mac OS X’s HFS+ filesystem, developed by Apple pre-NeXT purchase, is a case-preserving, case-insensitive filesystem.

Support for case-insensitive transcripts was later added to the radmind tools.

As it turns out, it is perfectly possible to use radmind with case-sensitive transcripts to manage an OS X HFS+ filesystem. There are sometimes a few annoyances, but it generally works OK. Worst case, you might have to run the radmind tools twice to get the filesystem update when there is a case change: the first run might remove the lowercase version of the file, and the second run would install the uppercase version. Or a sharp radmind admin might be able to avoid the problem altogether by renaming files in troublesome transcripts.
Continue reading “Radmind: converting to case-insensitive transcripts”

Radmind: converting to case-insensitive transcripts

Converting NetInfo accounts to dslocal

If you are doing an in-place upgrade from Tiger to Leopard without using the Apple Leopard Install DVD, you may need a way to convert existing local or mobile accounts from NetInfo to the dslocal store.

Here’s a script that converts local accounts; it requires the nicl binary, which you can copy from any Tiger installation.

For local accounts, it uses nicl to read the account info, and dscl to create a new corresponding account. For mobile accounts, it uses createmobileaccount to recreate the mobile account.

Enjoy.

Converting NetInfo accounts to dslocal

Automating Firmware Updates

Apple recently posted a firmware update for the MacBook that they claim addresses the spontaneous shutdown issue. If you have a lot of MacBooks at your site, you’ll have lots of fun visiting each one and doing the update.

It turns out that this updater can be run via command-line; either remotely via ARD or ssh, or via a local script on the machine. This opens up the possibility of automating it.
Continue reading “Automating Firmware Updates”

Automating Firmware Updates

radmind 1.5.1, Tiger, and symlinks

A little over a month ago, I discovered an issue with the way radmind 1.5.1 created symlinks under Tiger. This exhibited itself as a broken symlink to a directory on an automounted NFS volume. After discussing the matter with the radmind developers, and being unable to demonstrate any other actual problems caused by this, I decided to script a fix to the broken symlink and move on.

Yesterday, James Reynolds posted to both the MacEnterprise list and the radmind-users list about a problem with Adobe Bridge crashing on launch. James had used ktrace to discover it was crashing when trying to read a symlink.

James also discovered that if he copied the offending files from another (good) machine, Bridge worked as expected. It was only when radmind installed the files that things broke.

This rang a bell. I was quickly able to duplicate James’ issue by using radmind 1.5.1 to install Adobe Photoshop CS2 on a Mac.

After recreating the symlinks like so:

grep -h "^l" /var/radmind/client/AdobePhotoshopCS2.T | /usr/local/bin/lapply -u 022

Adobe Bridge worked as expected.

What is going on here?

There is a bug in Tiger that radmind 1.5.1 is exercising. Classically, owner, group and mode should be ignored on symlinks, deferring to the owner, group and mode of the symlink’s target. But in some operations, Tiger seems to honor symlink permissions, and if they are set so that a user cannot read the link, access to the link’s target will fail.

Here is a demonstration of the bug outside of the context of radmind:

(istanbul.fas) root [210] % uname -a
Darwin istanbul.fas.fa.disney.com 8.5.0 Darwin Kernel Version 8.5.0: Sun Jan 22 10:38:46 PST 2006; root:xnu-792.6.61.obj~1/RELEASE_PPC Power Macintosh powerpc
(istanbul.fas) root [211] % cd /
(istanbul.fas) root [212] % umask 077
(istanbul.fas) root [213] % ln -s /Users/Shared /Shared
(istanbul.fas) root [214] % ls -al /Shared
lrwx------ 1 root admin 13 Mar 2 10:33 /Shared@ -> /Users/Shared

Note the restricted permissons for the /Shared symlink.

(istanbul.fas) root [215] % umask 022
(istanbul.fas) root [216] % readlink /Shared
/Shared
(istanbul.fas) root [217] % exit
exit
(istanbul.fas) gneagle [202] % readlink /Shared
(Nothing is returned)

As root, “readlink /Shared” returns the target path: “/Users/Shared”.
As “gneagle”, a non-privileged user, it returns nothing.

radmind 1.5.1 changed the way it creates files in an attempt to improve security. It changes the umask to 077, creates the file, and then chmods it to its intended permissions. But chmod has no effect on symlinks, and radmind captures no user, group or mode info for symlinks since the are not supposed to be significant.

The net effect is that all sysmlinks are created with owner as root and with mode 700. Normally that would not be a problem, and indeed is not under Panther. But under Tiger, it can be a problem.

So until Apple fixes the bug (I’ve reported to them) or the radmind team implements a workaround in their code, you can avoid this issue going foward by calling lapply with a “-u 022” option. This tells lapply to use a umask of 022 when creating temporary files. The net effect is that symlinks are created with mode 755, which makes them usable by non-root users.

That still leaves a major problem to solve. How do you fix the symlinks already created by radmind with a umask of 077? I developed a script to do just that.

#!/usr/bin/perl –w
 
# fixSymlinks.pl
# by Greg Neagle, greg.neagle@disney.com
 
# - This script attempts to fix broken symlinks created by
# lapply 1.5.1 when run with the default umask option
#
# - No attempt is made to fix symlinks referenced in negative
# transcripts
#
# - Symlinks that are referenced in a positive transcript and a
# higher-precedence negative transcript therefore _will_ be
# modified, this could be bad. In my loadsets, I can find no
# actual occurrences of this problem, since the only symlink I
# have in any negative transcript is this one:
# l /private/etc/localtime /usr/share/zoneinfo/US/Pacific
# and it doesn't appear in any other transcripts.
# But the danger is there.
#
# - A deeper potential problem is transcript entries that
# _remove_ symlinks: if a transcript creates a symlink, and
# a higher-precedence transcript removes it, we'll have an
# unwanted symlink hanging around. A subsequent radmind
# session will remove it, but if this script is run again,
# it will come back.
#
# - Therefore we need to deal with "- l" lines in the
# transcripts as well. Unfortunately, we cannot just blindly
# pass these lines to lapply, because if symlink doesn't exist
# in the filesystem, lapply will fail and stop processing
# transcript lines.
#
# So we'll need to feed symlink removal lines to lapply
# one-by-one and ignore failures.
 
use strict;
 
my $radmindClientDir = "/var/radmind/client";
my $commandFile = $radmindClientDir . "/command.K";
my $symlinkremovet = "/tmp/symlinkremove.T";
 
#turn off output buffering
$|++;
 
(open K, $commandFile) or die "Can't open the command file!";
while (<K>) {
   my $commandFileLine = $_;
   if ($commandFileLine =~ /^p/) {
      #positive transcript
      my @lineItems = split;
      my $transcript = $lineItems[1];
      print "Processing $transcript...\n";
      # re-create all symlinks in the transcript:
      # grep out all lines that begin with l and send them
      # to lapply with a 022 umask
      system "/usr/bin/grep -h \"^l\" \"$radmindClientDir/$transcript\" | /usr/local/bin/lapply -u 022";
      # grab symlink removal lines
      system "/usr/bin/grep -h \"^- l\" \"$radmindClientDir/$transcript\" > $symlinkremovet";
      print "Removing symlinks flagged for removal in $transcript...\n";
      if (open T, $symlinkremovet) {
         while (<T>) {
            #feed each line one-by-one to lapply
            chomp;
            system "echo $_ | /usr/local/bin/lapply -u 022";
         }
         close T;
      }
   }
}
close K;

radmind 1.5.1, Tiger, and symlinks

Display appliance: The Revenge; or Scripting QuickTime Player

QuickTime PlayerIn previous posts, we’ve told launchd to watch a directory for changes and launch a Perl script when changes occur. This script will tell QuickTime Player what to do.

I have a web CGI that people can use to select which QuickTime movie to display. This CGI then creates a symlink called “next.mov” that points to the selected movie. The Perl script looks for this file. Note that we don’t need QuickTime Pro in order to display the movie full-screen; AppleScript can tell QuickTime Player to go full-screen even without QuickTime Pro.

Here is a stripped-down version of the script I use:


#!/usr/bin/perl -w
 
use strict;
 
my $loggedInUser = `who | grep console`;
 
# directory in which we look for movies to play
my $mediapath = "/Users/Shared/DigitalWalkway/display";
 
if ($loggedInUser ne "") {
   # if there is a symlink at "$mediapath/next.mov" then
   # close the current movie and rename next.mov to current.mov
   if (-l "$mediapath/next.mov") {
      closeCurrentMovie();
      rename "$mediapath/next.mov", "$mediapath/current.mov";
   }
 
   # if there is a symlink at "$mediapath/current.mov" then
   # play it if its not already playing
   if (-l "$mediapath/current.mov" ) {
      # readlink gets the actual path of the file
      # that the symlink points to
      my $realpath = `readlink "$mediapath/current.mov"`;
      chomp $realpath;
 
      # basename strips off the path
      # and leaves just the filename
      my $realname = `basename "$realpath"`;
      chomp $realname;
 
      # we use osascript to embed an AppleScript into the Perl
      # script. in the AppleScript, we check to see if the
      # currently playing movie's name is the real name of
      # he current.mov link so we don't keep restarting
      # the same movie over and over
      `osascript<<EOF
tell application "QuickTime Player"
   activate
   set show welcome movie automatically to false
   copy "" to movieName
   if number of movies > 0 then
      copy (name of front movie) to movieName
   end if
   if (movieName is not "$realname") then
      open POSIX file "$mediapath/current.mov"
      set looping of movie 1 to true
      present movie 1 scale screen
   else
      if (display state of front movie is not presentation) then
         present front movie scale screen
      end if
      if (playing of front movie is false) then
         play front movie
      end if
   end if
end tell
EOF`;
   }
}
 
 
sub closeCurrentMovie {
   `osascript<<EOF
tell application "QuickTime Player"
   close every movie
end tell
EOF`;
   if (-l "$mediapath/current.mov" ) {
      unlink "$mediapath/current.mov";
   }
}

Display appliance: The Revenge; or Scripting QuickTime Player

Updating Dock Icons – The Sequel

Dock image
Over a year ago, I posted a script that updated all the icons in a user’s Dock that were pointing to Office v.X apps to Office 2004 apps. That script doesn’t really work under Tiger, because the default plist format has changed in Tiger from XML to binary.

Fortunately, the fix is pretty easy. You can use a utility called “plutil” to convert to/from XML and binary formats:

/usr/bin/plutil -convert xml1 "$homedir/Library/Preferences/com.apple.dock.plist"

Here’s a script that updates Photoshop CS and ImageReady CS dock items to their CS2 counterparts:


#!/usr/bin/perl
# This script edits com.apple.dock.plist
# to remove Adobe Photoshop CS entries and replace them with
# Adobe Photoshop CS2 entries.
#
#
# by Greg Neagle, gregneagle@mac.com, Jan 2006
 
$homedir = $ENV{'HOME'};
 
# is Photoshop CS2 installed?
if (-d "/Applications/Adobe Photoshop CS2") {
   # has this script done its thing?
   if (!(-e "$homedir/Library/Preferences/com.apple.dock.plist.pscs")) {
      
      # convert plist to xml format
      `/usr/bin/plutil -convert xml1 "$homedir/Library/Preferences/com.apple.dock.plist"`;
 
      open DOCKPREFS, "$homedir/Library/Preferences/com.apple.dock.plist"
         or die ("Cannot open $homedir/Library/Preferences/com.apple.dock.plist");
      open DOCKOUTPUT, ">$homedir/Library/Preferences/com.apple.dock.plist.new"
         or die ("Cannot open $homedir/Library/Preferences/com.apple.dock.plist.new for output");
      until (eof DOCKPREFS) {
         $text = readline(DOCKPREFS);
         print DOCKOUTPUT "$text";
         if ($text =~ "file-data") {
            $dictBlock = "";
            $officeAppFound = 0;
            while(!($text =~ "")) {
               $text = readline(DOCKPREFS);
               if ($text =~ "/Applications/Adobe Photoshop CS/Adobe ImageReady CS.app") {
                  # replace the path
                  $text =~ s|Adobe Photoshop CS/Adobe ImageReady CS.app|Adobe Photoshop CS2/Adobe ImageReady CS2.app|;
                  $psAppFound = 1;
               } elsif ($text =~ "/Applications/Adobe Photoshop CS/Adobe Photoshop CS.app") {
                  # replace the path
                  $text =~ s|Adobe Photoshop CS/Adobe Photoshop CS.app|Adobe Photoshop CS2/Adobe Photoshop CS2.app|;
                  $psAppFound = 1;
               }
               $dictBlock = $dictBlock . $text;
            }
            if ($psAppFound) {
               # get rid of the _CFURLAliasData block
               $dictBlock =~ s|_CFURLAliasData.*||s
            }
            print DOCKOUTPUT "$dictBlock";
         }
      }
      close (DOCKOUTPUT);
      close (DOCKPREFS);
 
      #rename our files
      `mv $homedir/Library/Preferences/com.apple.dock.plist $homedir/Library/Preferences/com.apple.dock.plist.pscs` ;
      `mv $homedir/Library/Preferences/com.apple.dock.plist.new $homedir/Library/Preferences/com.apple.dock.plist` ;
 
      # restart the Dock
      `killall Dock`;
   }
}
#END

Updating Dock Icons – The Sequel