Shell

Weird things happen when you run out of disk space

I had interesting things happen when I ran out of disk space today.

The most notable one was that I saw “Process completed” — or some variant of that — every time I tried to open a Terminal window with a new shell session. I briefly staved it off by specifying Bash as my shell, but then it came right back after opening another tab or two in Terminal. Consulting Google led me to this “Terminal application quits” thread at Apple Discussions. On a lark, I tried deleting /usr/bin/login as one poster suggested. It worked!

…But only for a little while. The problem returned. In the meantime, I had freed up some disk space because I’d realized I couldn’t save files anywhere (“But ~/Pictures is writable!”). Clearly something else had become an issue, because disk space was available.

Then I found another thread, “Terminal’s ’Process Completed’ message and /usr/bin/login,” on Apple Discussions. The more permanent solution from that thread appears to be the removal of corrupted Apple System Log databases. Once I did that and restarted the ASL service, all was well and has stayed that way so far.

Filling up my disk must have corrupted the logs as they were being written or rotated, and led to this cascade failure. Like I said, interesting!

One of the posters in the second Apple Discussions thread indicated that the underlying database corruption issue is addressed in Snow Leopard. However, it seems that you could still see this on Leopard — my experience was with a recently-patched Mac OS X 10.5.8 system.

Python 32-bit execution on Snow Leopard

The default installation of Python on Mac OS X Snow Leopard is version 2.6.1. According to the man page for Python on Snow Leopard, Python 2.6 executes as a 64-bit application by default.

If, for some reason, you need to run it as a 32-bit application, this can be changed at the command line:

# Prefer 32-bit execution for Python 2.6.1 on Snow Leopard
$ defaults write com.apple.versioner.python Prefer-32-Bit -bool yes

The preference can be set in either the User or Local filesystem domain in Mac OS X, following the normal precedence rules. To unset it, presumably you would change the boolean to “no” — or perhaps even delete the “Prefer-32-Bit” key.

There is also an environment variable that can override this preference.

Updating Acquia Drupal versions in a Mercurial repository

After using Acquia Drupal for a while, I took advantage of a trial subscription to the Acquia Network. The network’s services showed me that I had files present in my install that the agent could not account for.

I suspected this was happening because of the way I manage my Acquia Drupal installation with Mercurial. So, I’ve modified my previous process (and updated my instructions) to extract the downloaded tar archive with the --recursive-unlink option. This option appears to successfully remove the contents of every directory before putting new files back into them.

$ tar --strip-path=1 --directory=acquia_drupal --recursive-unlink -zxvf acquia-drupal-1.2.12.5047.tar.gz
acquia-drupal-1.2.8/
acquia-drupal-1.2.8/robots.txt
...
acquia-drupal-1.2.8/INSTALL.txt

When the archive is extracted in this way, my repository’s working directory shows modified, unknown, and deleted files. This allows me to treat each category of files individually before I commit the changes for a Drupal update as a revision.

$ hg status

The modified files will be tracked normally because they’ve already been added to the Mercurial repository, so I don’t need to do anything special for them.

The unknown files are ones that are completely new, and have not appeared in the same position in a previous revision. They have yet to be tracked by Mercurial, so I have to add them to the repository. To add just those unknown files, then, I have to pick them out from the status listing:

$ hg status --unknown

In order to operate just on those files to add them to the repository, I run a for loop:

$ for FILEPATH in `hg status --unknown --no-status`
for> do
for>    hg add "$FILEPATH"
for> done

This changes the “?” status to “A,” because the files were successfully being tracked by Mercurial.
I use the “--no-status” flag on the “status” command so that just the file paths are printed; the actual status code is not, which is appropriate for the target of the “add” command in the loop.

I do the same basic steps with deleted files. These are files that were in the previous revisions but have been deleted by the --recursive-unlink option from the tar extraction and not replaced with the extraction of the new Acquia Drupal tar archive. If the deleted files had been replaced by the tar extraction, they would either be unchanged (which would not show up in the “status” output) or marked as modified.

To remove the files that are marked as deleted from the repository’s working directory:

$ for FILEPATH in `hg status --deleted --no-status`
for> do
for>    hg remove "$FILEPATH"
for> done

However, that may be the same as simply using the following, which I have to explore further:

$ hg remove --after

So, to follow all of these changes in the repository, I run the loop for the uknown files and the loop for the deleted files. The modified files are already tracked, so I don’t need to do anything additional for them. After that, a “commit” will record all of the changes — modifications, additions, and deletions — in the repo.

These commands are based on my current understanding of Mercurial, and they do work for me right now. There could certainly be another better way to do this in one fell swoop — or at least fewer steps. I would welcome that, so if you’re aware of a way, feel free to comment or contact me.

Update: I found that the “hg addremove” command cleanly replaces all of the shell loops I mentioned above. Therefore, I recommend using it instead of the “for” loops I described.

Spot problem commands in Apple Installer package scripts

It should come as no surprise that Apple Installer installation packages can contain scripts. These scripts are supposed to conduct important operations during the course of the software installation.

However, when you are the system administrator of more than one Mac, you find that developers sometimes miss a good balance between what you think should be in the installer payload versus what should be in its scripts. The payload of a installer, by definition, are the files and links that should be installed, along with information on where they should be installed as well as how (i.e. ownership, permissions).

Therefore, developers should not need to run scripts that create or delete files — they should be created from the payload itself, and if a file must be deleted during the install then consider that perhaps you’re doing it wrong. Likewise, there should be little need move or copy files, because as many copies as desired can be installed from the paylod. Similarly, the need to change ownership or modify permissions should be taken care of in the payload.

Perhaps I’m being a purist here. I’m certainly accused of that, from time to time. However, this just makes sense to me and I happen to think that many developers are similarly logical people. They just aren’t the kind of logical people who happen to spend effort on software installation, especially the kind that results in a deployment-friendly installer package.

So how do we as administrators verify the quality of the scripts in installers? Is there a way we can quickly peer into them to decide if any of the scripts’ steps will be superfluous or even (gasp!) harmful?

Well, I have a quick suggestion for scanning packaged installers. The following one-liner shell command will search an installer package or metapackage for scripts that have the kinds of steps outlined above.

$ find /path/to/installer.pkg -regex '.*/*\(flight\)' -or -regex '.*/*\(install\)' -or -regex '.*/*\(upgrade\)' -exec grep --with-filename --line-number '\(cp\|mv\|ln\|>{1,2}\|cat\|echo\|chown\|chmod\|rm\|srm\)' {} \;

Note that this will only work for the traditional installer packages; it will not work with Leopard-style flat packages (which are documented so badly by Apple that the best description comes from reverse engineering by Iceberg's author). The one-liner will currently only find the defined install operations scripts: preflight, preinstall, preupgrade, postinstall, postupgrade, and postflight. (Any other scripts are likely to be called by one of those six.) It assumes those scripts will be shell scripts, currently, even though any of them could be written in other scripting languages installed with Mac OS X, like Python, Perl, or Ruby. It will also not work on the JavaScript-based system and volume requirements portions of the installation.

However, it’s a start. The output displays the offending file and line number, so you can conduct more careful examination of the matches it finds.

I haven’t run this on an exhaustive list of installation packages, but I have already seen at least one installer that produces worrisome output.

Update: I’ve changed the regex for the pre/postflight script so that it is more general that what I originally posted. I’m also having some problems with the snippet working with a certain installer whose scripts I know have cp and chmod commands. So, I may be back to the drawing board with this; comments are welcome.

Membership in the lpadmin group on Mac OS X Leopard

In this MacEnterprise list thread about printing authentication, Greg Neagle mentions that:

Under Leopard, all local users are members of lpadmin, but I think network users are not. So this method won't grant network users CUPS rights.

To confirm Greg's suspicions, I ran the following shell snippet.

$ for CHECKUSER in mobile_account_user network_account_user local_account_user
do
        /bin/echo "--- $CHECKUSER"
                for CHECKGROUP in authedusers consoleusers interactusers netaccounts localaccounts netusers lpadmin
        do
                /bin/echo -n "$CHECKUSER in $CHECKGROUP: "
                dsmemberutil checkmembership -U "$CHECKUSER" -G "$CHECKGROUP"
        done
done

This loops through the fictional accounts, "mobile_account_user," "network_account_user," and "local_account_user." These accounts are, as you might expect, as a locally-cached mobile account from a network directory, a wholly network directory-based account, and a simple local admin account. While the accounts presented here are fictional, the results were confirmed on a live system bound to a directory service.

The rest of the snippet determines if the accounts are members of any of the specified computational groups that debuted in Leopard. The last group checked is the "lpadmin" group. By looking at these group memberships, we can determine whether Leopard thinks that the account being tested is a local or network account.

Running the snippet above, with the right accounts available, produces the following output:

--- mobile_account_user
mobile_account_user in authedusers: user is not a member of the group
mobile_account_user in consoleusers: user is not a member of the group
mobile_account_user in interactusers: user is not a member of the group
mobile_account_user in netaccounts: user is a member of the group
mobile_account_user in localaccounts: user is not a member of the group
mobile_account_user in netusers: user is not a member of the group
mobile_account_user in lpadmin: user is a member of the group
--- network_account_user
network_account_user in authedusers: user is not a member of the group
network_account_user in consoleusers: user is not a member of the group
network_account_user in interactusers: user is not a member of the group
network_account_user in netaccounts: user is a member of the group
network_account_user in localaccounts: user is not a member of the group
network_account_user in netusers: user is not a member of the group
network_account_user in lpadmin: user is not a member of the group
--- local_account_user
local_account_user in authedusers: user is not a member of the group
local_account_user in consoleusers: user is not a member of the group
local_account_user in interactusers: user is not a member of the group
local_account_user in netaccounts: user is not a member of the group
local_account_user in localaccounts: user is a member of the group
local_account_user in netusers: user is not a member of the group
local_account_user in lpadmin: user is a member of the group

So, it appears mobile and local users get added to the lpadmin group automatically in Leopard, but network accounts do not.

Note that I didn’t check whether membership in the “admin” group made a difference or not. I also didn’t isolate for that factor.

I found it interesting that the mobile account is a member of “netaccounts” but not “localaccounts.” (By group membership alone, I’m not sure you could identify whether an account was a mobile account or not. Yet, that kind of test is part of the point of having these computational groups in the first place.)

Check a new version of Acquia Drupal into a Mercurial repository

Here is a sequence of commands and output that show how I keep the Acquia Drupal open source content management system up to date with Mercurial, the open source distributed version control system.

In the example below, my Mercurial repositories for Drupal are located in the “drupal” subdirectory of my “repo” folder. Once I’ve moved into that directory, I download the Acquia Drupal distribution with curl and then extract it into my previously-created Mercurial working directory, “acquia_drupal,” using tar.

$ cd repo/drupal
$ curl -O http://acquia.com/files/downloads/acquia-drupal-1.2.0.3780.tar.gz
$ tar --strip-path=1 --directory=acquia_drupal --recursive-unlink -zxvf acquia-drupal-1.2.0.3780.tar.gz

(Update: I added the --recursive-unlink option after I noticed that the Acquia Network control panel keeps track of extra — possibly unneeded — files and folders you have in your install. The recursive unlink option seems to avoid having stray files from old versions of modules hanging around in your repository after you install updates.)

After extracting Acquia Drupal my Mercurial working directory, I get the status of the repository. It shows there are changes from the last version I checked in — and this includes new files, denoted by a “?” at the beginning of their line.

$ cd acquia_drupal
$ hg status
M profiles/acquia/acquia.profile
? modules/acquia/acquia_connector/README.txt
? modules/acquia/acquia_connector/acquia_agent/acquia.ico
? modules/acquia/acquia_connector/acquia_agent/acquia_agent.info
? modules/acquia/acquia_connector/acquia_agent/acquia_agent.install
? modules/acquia/acquia_connector/acquia_agent/acquia_agent.module
? modules/acquia/acquia_connector/acquia_agent/acquia_agent.pages.inc
? modules/acquia/acquia_connector/acquia_agent/acquia_agent_drupal_version.inc
? modules/acquia/acquia_connector/acquia_agent/acquia_agent_streams.inc
? modules/acquia/acquia_connector/acquia_spi/acquia_spi.info
? modules/acquia/acquia_connector/acquia_spi/acquia_spi.install
? modules/acquia/acquia_connector/acquia_spi/acquia_spi.module

Since there are new files, I have to add them so they’ll be tracked by the repository. I only need to add in the parent directory for any changed files, and any new files within it will also be added for tracking.

$ hg add modules/acquia/acquia_connector
adding modules/acquia/acquia_connector/README.txt
adding modules/acquia/acquia_connector/acquia_agent/acquia.ico
adding modules/acquia/acquia_connector/acquia_agent/acquia_agent.info
adding modules/acquia/acquia_connector/acquia_agent/acquia_agent.install
adding modules/acquia/acquia_connector/acquia_agent/acquia_agent.module
adding modules/acquia/acquia_connector/acquia_agent/acquia_agent.pages.inc
adding modules/acquia/acquia_connector/acquia_agent/acquia_agent_drupal_version.inc
adding modules/acquia/acquia_connector/acquia_agent/acquia_agent_streams.inc
adding modules/acquia/acquia_connector/acquia_spi/acquia_spi.info
adding modules/acquia/acquia_connector/acquia_spi/acquia_spi.install
adding modules/acquia/acquia_connector/acquia_spi/acquia_spi.module
$ hg status
M profiles/acquia/acquia.profile
A modules/acquia/acquia_connector/README.txt
A modules/acquia/acquia_connector/acquia_agent/acquia.ico
A modules/acquia/acquia_connector/acquia_agent/acquia_agent.info
A modules/acquia/acquia_connector/acquia_agent/acquia_agent.install
A modules/acquia/acquia_connector/acquia_agent/acquia_agent.module
A modules/acquia/acquia_connector/acquia_agent/acquia_agent.pages.inc
A modules/acquia/acquia_connector/acquia_agent/acquia_agent_drupal_version.inc
A modules/acquia/acquia_connector/acquia_agent/acquia_agent_streams.inc
A modules/acquia/acquia_connector/acquia_spi/acquia_spi.info
A modules/acquia/acquia_connector/acquia_spi/acquia_spi.install
A modules/acquia/acquia_connector/acquia_spi/acquia_spi.module

Excellent; the new files have been added. After this, I just need to accommodate the deleted files that no longer need to be tracked (created when using the “--recursive-unlink” option on tar). For that, see my newer instructions.

Now that the right files are being tracked, I need to commit the changes — modified, added, and deleted files — to the repository. This will create a new revision in the repository’s history, which I’ll tag with the text “Acquia Drupal 1.2.0.”

$ hg commit -m "Acquia Drupal 1.2.0 imported."
$ hg tag "Acquia Drupal 1.2.0"
$ hg tip
changeset:   10:423c84439928
tag:         tip
user:        Jeremy Reichman <jaharmi@jaharmi.com>
date:        Wed Jan 14 21:07:23 2009 -0600
summary:     Added tag Acquia Drupal 1.2.0 for changeset 0df92d3d243d

Once this revision is checked in, I can use it to propagate changes to other repositories. I keep the main Acquia Drupal distribution in its own repository, and then use the “hg fetch” command to pull its changes into one where I track contributed modules. That second repository is then pulled into a third repository which stores just the changes for my production Web site. The use of three repositories in this way modularizes and isolates the updates.

Radmind transcripts symlinks damaged by editing with Transcript Editor

Radmind transcripts with symlinks will be damaged when edited in the Radmind Transcript Editor. I have confirmed this with RTE version 0.7.7 used in conjunction with the version 1.13.0 Radmind command line tools.

The problem appears to be an interaction between RTE 0.7.7 (which is old) and newer Radmind tools, according to posts on the Radmind-Users mailing list. It apparently relates to any version of the Radmind tools greater than 1.12.0, which introduced symlink ownership, when used in combination with RTE 0.7.7. This issue is in the Radmind bug report tracker and has been fixed in the CVS version of RTE. To use that newer version of RTE, you have to build the GUI tools from CVS.

You only see the problem — assuming you are using the right combination of versions — if you edit and save a transcript or create a new transcript within RTE (either by drag and drop or the “Add Item to Transcript” command). So, using the RTE to simply view the transcript file — and then editing with a different editor (which is an inconvenience) — is a workaround.

To get a count of the affected transcripts (on your Radmind server), use the following command:

$ grep \?\?\?\? /var/radmind/transcript/* | cut -f 1 -d ":" | sort | uniq | wc -l

You can simplify the grep search to only return the path of each match, and then process that with Awk to get just the basename of the file. Here’s how to use that technique to get the list of affected transcript files on a Radmind client:

$ sudo grep --files-with-matches \?\?\?\? /var/radmind/client/*.T | awk -F/ '{print $NF}'

As for actually fixing the damaged transcripts, it appears that the best way to do so is to recreate them from scratch.

Getting the settings right for the Drupal GeSHi Filter module

I wanted to find a way to do syntax highlighting of code snippets on my Drupal blog. I came across the GeSHi Filter module, which lets Drupal sites take advantage of the apparently well-regarded GeSHi Generic Syntax Highlighter library that’s meant for just this purpose.

However, I ran into some roadblocks implementing it on my site. Here’s the short story of what I settled on after some trial and error.

My existing code snippets are in <code> blocks, and the initial GeSHi Filter settings applied badly to them. I made the decision to only use GeSHi on <blockcode> blocks, since I wasn’t using that tag yet and it wouldn’t conflict with the snippets already posted.

I most commonly write Bash/Zsh, Python, and AppleScript snippets on my blog. However, the Bash code I was using as part of my trial and error simply wasn’t highlighting; it was coming through as the default (and boring) plain text — but was at least boxed off from the rest of the blog post.

I thought that GeSHi wasn't correctly discovering that the code was written in UNIX shell syntax. I couldn’t find a way to specify the language for that blockcode tag, until I did some searching on the ’net. To change my blockquotes to choose a certain language — at least for the purposes of this Drupal module, if not for GeSHI in general — I needed to add the “lang=lang” style to the tag. For Bash, I could use “lang=bash,” for Python, “lang=python,” and for AppleScript, “lang=applescript.” That made sense.

However, my code was still not being syntax highlighted. I discovered that the Drupal module came with an initial set of languages enabled. The others were all turned off, but that could be changed in the module settings. Without turning them on, even properly-tagged <blockcode> sections did not get the benefit of syntax highlighting.

I changed the GeSHi Filter options to enable some of the languages that were initially disabled, and then disabled the ones I didn’t anticipate using. This allowed me to add Bash and AppleScript syntax highlighting support, as both had been turned off by default. After that, I saw the results I’d hoped for: a syntax-highlighted code snippet.

It took some work, but now that it’s done, I should be all set.

Check for SSL certificate expiration of Radmind client certificates

You can find out if an SSL certificate has expired with the command below. I’ve found it useful to be able to check for expired certificates in my use of Radmind, where you can uniquely identify clients to the server with them.

$ openssl x509 -in /path/to/cert.pem -noout -checkend 0

I mention this command primarily because I reviewed the the OpenSSL x509 man page (“man x509”) that comes with Mac OS X Leopard, and it didn’t show the “checkend” option for the command. That was odd, because that option was just what I needed.

I did, however, find it documented in the usage statement-style help for the command:

$ openssl x509 --help

In that usage statement, the “checkend” option is described (with little punctuation) as a way to “check whether the cert expires in the next arg seconds [sic] exit 1 if so, 0 if not.” So, using zero seconds shows you if the certificate has already expired, while an integer greater than zero will show if it will expire in the future. No matter how many seconds you check against, you must examine the results from the exit code (the “$?” shell variable) to see if the certificate is or has expired.

I find this is tremendously useful knowledge when dealing with certificates in Radmind, where an expired certificate can mean the failure of a client to connect to the Radmind server. It could be beneficial in other circumstances, of course — but I don't have those circumstances.

Taking this further, you could check for certificate expiration on a Radmind server — if your certificates are stored in the Radmind special directory for each hostname of a managed client. (Substitute one of your own managed clients’ hostnames for “hostname” in the path below.)

$ openssl x509 -in /var/radmind/special/hostname/private/var/radmind/cert/cert.pem -noout -checkend 0

Since you can do it for one client certificate, you could also loop through all of the certificates on a Radmind server. In this example, I’ll continue to use the path of /var/radmind even though, on Mac OS X, I’d generally prefer to specify the full /private/var/radmind; your Radmind server may not be on Mac OS X even if your clients are. Also, you may need to modify the “depth” parameter on your search to accommodate the paths on your server. Finally, I’ll change the “checkend” parameter to 604800, for seven days (60*60*24*7=604800). That produces something along the lines of:

for CERT in `find /var/radmind/special -name cert.pem -print -type f -depth 6`;
do
    openssl x509 -in "/private/var/radmind/$CERT" -noout -checkend 604800
         RESULT=$?
    case "$RESULT" in
     0)
          echo "$CERT: okay"
          ;;
     *)
          echo "$CERT: expiring"
          ;;
esac
done

Change the last line to “done | grep expiring” if you only want to see the expiring certificates.

It’s great to get just the CN of the certificate in these circumstances, since it’s likely you’ll want to act on just those that need attention. One way to do this relatively cleanly is to use OpenSSL x509’s “subject” and “nameopt” options, and then parse the output. Below, I’ll use awk for that. (Again, substitute one of your own managed clients’ hostnames for “hostname” in the path below.)

$ openssl x509 -in /private/var/radmind/special/hostname/private/var/radmind/cert/cert.pem -noout -subject -nameopt sep_multiline | awk '/CN/ {split($1,elements,"=") ; print elements[2] ;}'

Beyond checking for expiration on the server, it may be valuable to do so in your Radmind client scripts, especially if you favor SSL connections. If you find an expired certificate, you can take some remedial action right away that might allow the client to communicate with the server.

I thought about this a while, and the easiest way I came up with — after having already developed more complex logic — was to simply rename or remove the expired certificate from its normal path. Then, allow the client to connect with another authorization level where the client certificate is unnecessary. (Use of a client certificate implies Ramind’s “-w2” authorization level, while a lesser level would mean you’re performing hostname/DNS rather than certificate verification.) This would probably mean you have multiple Radmind server processes running, each on its own port, to accept such incoming requests on the server.

List changed files in a Mercurial repository with a custom output style

While trying to troubleshoot what I’d done to mess up the Mercurial repositories managing my Drupal installations last weekend, I really would have liked a way to see what files had changes in specific revisions. Each revision to a Mercurial repository affects some files, of course, but it seems awfully hard to figure what files changed in that check-in.

I have since found a way to do that by customizing the output of Mercurial. To customize output, you can create templates on the command line (with --template) or for more powerful reformatting, create an output style file.

I struggled for a while to figure out how to use style files, and eventually came up with something that works for me so far.

Since I’ve installed Mercurial from Lee Cantey’s standard binary package for Mac OS X Leopard, I created the file “map-cmdline.changedfiles” at the “/Library/Python/2.5/site-packages/mercurial/templates” path. (Where you put the file may vary depending on where Mercurial is installed, and I’m sorry but I don’t know where it gets installed on other systems.) The contents of “map-cmdline.changedfiles” are below, along with my possibly inept description of what each line is doing:

# Get all of the files in the selected revision
# and stringify them, whatever that means
# but do not 'tabindent' or wrap them to 68/76 columns
# Without first setting changeset to the list of files
# you won't get output from subsequent lines
changeset = '{files|stringify}'
# List modified files, one per line
# preceded by M to mimic `hg status`
file = 'M {file}\n'
last_file = 'M {file}\n'
# List added files, one per line
# preceded by A to mimic `hg status`
file_add = 'A {file_add}\n'
last_file_add = 'A {file_add}\n'
# List deleted files, one per line
# preceded by ! to mimic `hg status`
file_del = '! {file_del}\n'
last_file_del = '! {file_del}\n'

I don’t know why the “map-cmdline.” portion of the filename is there, but as long as I have it, I can call the style file from the command line with what follows the period. So, I can call the style with “--style changedfiles” — and that tiny bit of voodoo seems reasonable enough to me. (The other styles in the directory above, many of which end in “.tmpl” extensions, seem related to the Mercurial Web server, hgweb. I tried, but I couldn’t use their names at the command line, with or without their extensions. Plus, their contents looked HTML-ish.)

With the “map-cmdline.changedfiles” style file saved in that location, I can call Mercurial’s “log” command:

$ hg log --style changedfiles -r tip

… which gives me a list of the files changed in the “tip” (or latest revision) of the repository. I could substitute in any revision identifier for “tip.”

I haven’t actually seen the “file_add” and “file_del” keywords in action; every time I’ve used this style file in the manner described, I’ve only seen files marked as “M” — even if I’m looking at a revision where new files were first checked into the repo. I’m confused by that, but I’m not going to let it sour my day at this point.

There might have been an easier way to do this but I didn’t find one last weekend. It took me some time to figure even this bit out, and I hope writing this post saves someone new to Mercurial from future frustration.

Syndicate content