Plymouth Multi-head support

September 29, 2009

So it’s been a while since I’ve blogged.

This is just a short blog post to say that I landed multi-head support on plymouth master yesterday.  More details here:

What this means is the boot splash redraws itself once per monitor,  in a way that looks good for the native resolution each monitor.  Previously, we’d draw right on the fb console, which is cloned across all monitors.  That made the splash have large black margins on some monitors, or look stretched.

One implication of this change is we now use libdrm directly when possible instead of going through the /dev/fb interface.

We still don’t do any accelerated rendering.  We don’t really need it, since plymouth splashes are normally designed to be very simple (Think “cell phone splash” not “3D video grame”).

So one common programming idiom you’ll find a lot if you sniff around code in random projects is a helper function that reads or writes in a loop. Something almost, but not completely unlike…

bool write_it_harder (int fd, void *buf, size_t size_of_buf)
while (there are still bytes to write) {
the_number_of_bytes_written = write (fd, buf, the_number_of_bytes_left_to_write);
/* check for errors then figure out the_number_of_bytes_left_to_write */
return true;

THIS IS WRONG. It’s not a good idea to do in a gui program. It means that your app will

1) block until all the bytes are written (which could be a while, if the thing getting written to is slow)
2) will completely fall over on some devices

It’s really better (although harder to code) to treat read()/write() as one-shot operations. You do them once, get a result, and then should go back to poll() and wait for it to report that the fd you are working with is readable or writable again.

Another common one is:

bytes_read = read (fd, buf, sizeof (buf));

if (bytes_read < 0 && errno == EAGAIN)
goto again;


1) you may get into a situation where read() will return EAGAIN over and over again in a tight, battery killing, cpu heating loop
2) there is no guarantee that you’ll get EAGAIN on successive calls anyway. pseudo-terminals for instance, will give you EAGAIN once, and then EIO for successive calls.

So, only every call read() or write() when poll() says its okay to call them, and realize that if poll() says its okay to call them, it’s only saying it’s okay to call them ONCE, and you need to ask poll() permission to call them again.

I’ve put a sample program up here to show what I mean.

screensaver extension

July 25, 2007

So during GUADEC I investigated autologin/followed by auto-locking the screen. The idea is a user may want to setup their machine such that they can turn it on, go get coffee, and come back to it all loaded (but locked).

One snag I ran into was that gnome-screensaver’s screensaver and the various components of the desktop (panel, splash screen, background, etc) would race to be on top of the window stack. The splash screen would pop up, then the screensaver would raise over it, then the panel would pop up and the screensaver would raise over it, etc. It was very flickery.

One solution to this is to use the MIT screensaver extension included with X. It provides a really-truely-always-on-top window that things like the splash screen and panel can’t ever raise over.

The screensaver extension has been rejected in the past for a few reasons:

1) We didn’t think we could get a fade in animation prelock since X maps the screensaver window automatically instead of letting gnome-screensaver do it

2) We didn’t think we could get separate screensavers running per head in a xinerama multihead setup since the screensaver extension only provides one window that covers the entire X screen (and so spans all the heads in xinerama setup)

3) We didn’t think X could reasonable know when the system was idle since there are more forms of user activity than input devices.

I wrote a little test program that demonstrates the first two issues are resolvable, and posted about it here:

The fix for the first issue is to use a None background pixmap on the screensaver window so that when it gets mapped the contents of the screen don’t get erased and we can fade from there.

The fix for the second issue is to run the screensavers on child windows of screensaver window instead of on the screensaver window itself.

Login Records

June 29, 2007

So one of the the features I needed to implement for RHEL 5 was btmp logging for GDM. The btmp log is a record of failed log attempts that an admin can access by running the /usr/bin/lastb command. It’s similiar to the wtmp log which is a record of login and logouts by users, and also like the utmp log which is a record of users currently logged in.

GDM already had indirect support for those last two types of login records because when starting a session it would invoke the sessreg utility that ships with X. This utility would create an entry in the wtmp and utmp logs. This utility couldn’t create an entry in the btmp log, however, because it only gets invoked after a session is started, and the btmp log records failed login attempts–attempt that don’t lead to a session getting started.

The format of the 3 logs are nearly identical, so it made sense to do all the logging with in GDM. For RHEL 5, I pushed a patch that did wtmp and btmp logging, but neglected to do utmp logging. The reason for this was a bit of text in the utmp man page

xdm(8) should not create a utmp record, because there is no assigned terminal. Letting it create one will result in errors, such as ’finger: cannot stat /dev/machine.dom’. It should create wtmp entries, though, just like ftpd(8) does.

This turned out to be a mistake. People like to be able to see who is currently logged in by using the w command, which relies on utmp being up to date. I’ll be correcting that in a future RHEL 5 update.

Anyway, I submitted my patch upstream and Brian Cameron picked up the work and made it work in Solaris. This broke it in Linux, so he passed it back to me to fix for Linux.

There is still one issue left. utmp entries have a specific field per record called the ut_line field. For text logins you would put the name of the terminal device that is controlling the session. So if the user is logged in on vt 1, then the associated terminal device is /dev/tty1 and the ut_line field would be tty1.

For X logins, there is no controlling terminal, so there is some debate what should go in the ut_line field. Note, the warning I quoted above. Some utilities expect there to be a file in /dev with whatever value is given to ut_line. According to Brian, Solaris has traditionally dealt with this problem by creating fake files in /dev for each X login, just so programs like finger and w don’t freak out. These fake files are called “pseudo devices” and they’re sort of akin to pseudo-terminals, except they aren’t terminal devices.

Anyway, it’s not clear what we should do for ut_line. The whole idea of creating fake files just to satisfy some old unix utilities is sort of offensive, and it has SELinux implications as well. For RHEL 4, ut_line was not set to a valid file. It was simply the display number (:0 or whatever), so I’m thinking that’s probably the right way to go.

detached directories

June 18, 2007

So one thing I wanted for my Graphical Boot stuff was a standalone environment for it to work in. I wanted it to have its own /proc, device nodes, etc, because it gets run so early in the boot process that those things aren’t set up yet globally. If I make my program set them up globally, then other parts of the boot process get confused that things are set up already. What I really want is my own private directory that only my process can see and that goes away when my process exits.

This is something you can do with individual files pretty easily. Something like:

int fd;
char file[] ="/tmp/XXXXXX";
fd = mkstemp (file);
unlink (file);

It turns out you can do something similar with directories, too, although there are some caveats. If your program

  1. creates a temp directory
  2. mounts a ram filesystem in the directory
  3. opens the directory and stores the file descriptor somewhere
  4. lazily unmounts the ram filesystem
  5. remove the temp directory

Then the directory will no longer be visible from the filesystem, but will exist as long as the stored file descriptor is opened. The program can fchdir() to the directory using the saved file descriptor and work with it. Now some of the caveats are:

  1. the program needs to be root to mount the filesystem
  2. MNT_DETACH (the mount flag used to lazily unmount a filesystem) is unsupported api
  3. you can’t mount other filesystems in your detached directory

Overall, it’s kind of a neat concept, but those caveats make it fairly impractical to use.