Sunday, April 20, 2008

Stale /etc/group.lock & /etc/gshadow.lock on RH9

Last week for the first time in quite a long time an automated update process failed me. Even though it had been in use for several months already, and had seen numerous successful test runs, one machine was (almost) completely screwed up after the process. It may be interesting to know that all this is taking place on rather "antique" Red Hat 9 systems. I have not had the time to double check whether the following still holds true for more modern systems, any comments are appreciated.

The update logs contained a strange line:

useradd: unable to lock group file

Well, while the message itself is one of the more explicit ones, I could not understand what would be causing a lock on the group file. There is indeed a useradd command in the upgrade script, however it is the only one, and the update takes place right after a reboot. Hence no other process could possibly hold a lock on /etc/group.

Looking at the /etc directory I found these two files: /etc/group.lock and /etc/gshadow.lock.

I was quite surprised, when I could readily add a new group manually on the affected machine with the exact same command line from the update script.

After that a quick sweep over all production systems that are to be upgraded revealed, that each and every one of them contained the two files named above, usually with a creation date at about the time of their initial setups. So how could any of those ever have been upgraded successfully? Apparently my manual useradd succeeded as well...

Turns out that the .lock files are not just simple flags. At first I thought they were just by their presence acting as a signal to any command trying to change the contents of the group files. Apparently that's not the case. Their content is relevant as well: They both contain the process ID (PID) of the command that last acted upon the /etc/group and /etc/gshadow files. Unfortunately due to a bug described in a RedHat Errata Entry (on RH Desktop 3, not specifically RH9) those files do not get properly unlocked, when the useradd command exits (even normally).

So you get a stale lock file lying around. Next time useradd tries to modify the group files, it will not complain if there is no process currently running that has the PID denominated by the .lock file! This is really nasty, because it will work almost every time you try, but there is of course a realistic chance that some random process will just by chance get the same PID the first useradd once had and be running at the time you call it the second time. In that case, useradd is mistaken to believe that some other process is currently modifying the group or gshadow file and aborts with the error message above!

I have not found an updated shadow-utils RPM for RH9, so right now I need to scan all our scripts for group modifications and replace the groupadd calls to a function or script that will take care of removing the lock file once the command has finished.

#!/bin/bash
groupadd ${*}
rm -f /etc/group.lock
rm -f /etc/gshadow.lock

Great that this hit us on a test system before rolling out into production.... Maybe sometimes Murphy is a nice guy after all ;-)

Saturday, April 12, 2008

Amazon.de Browser Switch

Just noticed that Amazon.de seems to deem Firefox users "not worthy" of their new site design ;-)

Amaon.de (Firefox 3 Beta 5)

Amazon.de (IE7)

Strangely enough Amazon.com show the same (blue) layout in both browsers.

Just wondering...

Monday, April 07, 2008

Locale.getDefault() FindBugs Detector

I just hacked together a little FindBugs plugin that will just detect calls to the static Locale.getDefault() method. This may be undesirable for application code to do - e.g. in cases where you have an application framework that provides a more flexible way of localization.

This is nothing I deem worthy of contributing to the FindBugs team, because technically there is nothing wrong with Locale.getDefault(). Nevertheless maybe you can use it, too.

Just drop it into the plugin directory of your FindBugs installation. The plugin contains the source code of its single class inside the jar. Because this page is not yet full, I post it here as well for a quick glance:

package de.shipdown.fb.detect;

import java.util.Locale;

import org.apache.bcel.classfile.Code;
import org.apache.bcel.generic.ObjectType;

import edu.umd.cs.findbugs.BugAccumulator;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.ba.AnalysisContext;
import edu.umd.cs.findbugs.ba.ch.Subtypes2;
import edu.umd.cs.findbugs.bcel.OpcodeStackDetector;
import edu.umd.cs.findbugs.classfile.ClassDescriptor;
import edu.umd.cs.findbugs.classfile.DescriptorFactory;

/**
 * This is a FindBugs plugin designed to detect calls to the
 * {@link Locale#getDefault()} method. This may be undesirable in certain
 * contexts.
 * 
 * @author Daniel Schneller
 */
public class LocaleGetDefaultDetector extends OpcodeStackDetector {

	/** The reporter to report to */
	final private BugReporter reporter;
	final private BugAccumulator bugAccumulator;

	/**
	 * {@link ObjectType} for {@link java.util.Locale}
	 */
	private final ClassDescriptor localeType = DescriptorFactory
			.createClassDescriptor("java/util/Locale");

	/**
	 * Creates a new instance of this Detector.
	 * 
	 * @param aReporter
	 *            {@link BugReporter} instance to report found problems to.
	 */
	public LocaleGetDefaultDetector(BugReporter aReporter) {
		reporter = aReporter;
		bugAccumulator = new BugAccumulator(reporter);
	}

	Subtypes2 subtypes2 = AnalysisContext.currentAnalysisContext()
			.getSubtypes2();

	@Override
	public void visit(Code obj) {
		super.visit(obj);
		bugAccumulator.reportAccumulatedBugs();
	}

	/**
	 * Checks for method invocations ({@link org.apache.bcel.generic.INVOKESTATIC})
	 * on a {@link Locale#getDefault()}
	 * 
	 * @param seen
	 *            An opcode to be analyzed
	 * @see edu.umd.cs.findbugs.visitclass.DismantleBytecode#sawOpcode(int)
	 */
	@Override
	public void sawOpcode(int seen) {
		// we are only interested in static method calls
		if (seen != INVOKESTATIC) {
			return;
		}

		try {
			String className = getClassConstantOperand();
			if (className.startsWith("[")) {
				// Ignore array classes
				return;
			}
			ClassDescriptor cDesc = DescriptorFactory
					.createClassDescriptor(className);

			// if it is not compatible with Locale we are not
			// interested anymore
			if (!subtypes2.isSubtype(cDesc, localeType)) {
				return;
			}

			String invokedName = getNameConstantOperand();
			if (!invokedName.equals("getDefault")) {
				return;
			}

			bugAccumulator.accumulateBug(new BugInstance(this,
					"LOCALE_GETDEFAULT", NORMAL_PRIORITY).addClassAndMethod(
					this).addCalledMethod(this), this);

		} catch (ClassNotFoundException e) {
			AnalysisContext.reportMissingClass(e);
		}
	}

}

The file can be downloaded here: LocaleGetDefaultDetector.jar