Print yourself!

I’ve been working a bit on a printing API for gtk+. Its a highlevel API that integrates cairo, pango and a native print dialogs. Its really easy to get nice printing output, and it will work on all platforms, using native printing. I just made a code drop so that people can look at the APIs and comment on it.

The example code in it is pretty awesome, it actually prints itself. Here is the output pdf.

If you read that pdf you’ll see it produces some other output too. I’m sure interested parties can find that file too. 🙂

At last!

Daniel Veillard built the ekiga beta (ekiga is basically GnomeMeeting 2.0) in rawhide yesterday, so I decided to try it out. For the longest time I’ve thought GnomeMeeting was a really cool idea, but whenever I tried to actually use it I’d alway fail due to some firewall issue. (I have a pretty standard setup with a Linksys WRT54 broadband router as firewall.)

Not so this time!

It was incredibly smooth to set up, even autodetecting how to avoid NAT problems. The only manual thing I had to do was to enable the top secret “+20dB mic boost” setting in the volume control. I tried calling various echo servers on the net, which worked fine. Then I called caolan, who just got a “commercial VOIP thingie”. Worked immediately! Maybe the firewall handling got better, or maybe SIP is just easier to get through firewalls. I don’t care, and I don’t have to, it just works!

The integration with the free ekiga.net service and its central address book really makes this much more usable. Hats of to Damien!

Seek and Ye Shall Find

The last two weeks I have been working on the nautilus-search (and now nautilus-search2) branch of Nautilus. The initial code in this branch was written by andersca and was later maintained by joe. It had a pluggable search interface with a beagle implementation that let you easily search for files from Nautilus. Places->Search in the menu would give you a text entry in the toolbar, and when you typed text there the matches was shown in the normal directory view. Here is how it looked:

While pretty cool and useful, this really isn’t state of the art compared to e.g. MacOS X or MS Vista. For one, it doesn’t support what Apple calls “Smart Folders” and MS “Virtual Folders”, nor does it allow you to specify the search other than with text. Furthermore, the search acts as a special type of folder, but I didn’t like the fact that its not obvious that this is not a “normal” folder.

So, after spending some time on the code I now have this:

The blue area is a special part of the window I called “extra location widgets” in the code. It can be used to show that this is a special kind of folder, and I think we should use it for folders in burn:/// also. Furthermore, the “+” button lets you add limitations on the search:

You can also search by type:

When you are satisfied with your search you can use File -> Save Search As… which gives you this dialog:

Looking in home, we see the newly saved file “search for alex.savedSearch”. Its really a simple xml file describing the query. However, we can tell by the expander that Nautilus treats it as a folder.

If you expand it, you get the results of the search as if it was a normal folder:

And if you click on it you show the file as a folder:

You can also edit it by clicking on the edit button, giving you back the original query edit widgets. When you’ve changed them to your liking, File -> Save Search updates the search file on disk:

All this code is on the nautilus-search2 branch in cvs. Its mostly working, I just need to fix some bugs and add a simple (non-indexing) search backend and I think its ready to be merged to HEAD.

Sabayon LDAP support

Earlier this week I blogged about Sabayon, the profile editor. I got several comments about how it would be nice if it supported LDAP. So, I spent some time this week hacking up LDAP support.

I wrote some initial documentation on how this works, and now I’d like some feedback. The system is very flexible and IMHO nice, but I’ve never administrated an LDAP network, so I’m not the best person to ask.

If you are interested in Sabayon and LDAP, please read the docs and send your impressions to the mailing list.

Live, uncut french egg-sauce thingie

The last couple of weeks I have been working on Sabayon, an application that some other Red Hat people started on earlier this year. Its an editor for user profiles, where you can set up settings and add files for a class of users and easily push them out to users. Today I released version 2.12.1, which has a bunch of fixes and new features.

The interesting thing about Sabayon is the way you edit the profile. Instead of using a completely new interface it uses the standard interface you normally use. This is accomplished by launching an actual desktop inside a window, letting you make your changes in that desktop and then save it. You can also look at the changes that has happened in the session and ignore some of them, or make some of them mandatory.

Just describing Sabayon isn’t doing it justice though. To really see how this is different from other similar things you have to see it in action. So, I prepared a screencast
of it.

Once you’ve finished editing your profile it will be stored as a zip file with xml metadata that can easily be applied to a user by running the sabayon-apply tool. There is also support for deploying the profiles from a central http server.

I think this is a really cool app, but I’m a programmer, not really an administrator. I wonder what system administrators think of this? Is it an interesting approach? Is it better than what you’re using currently? Would you use this?

If you want to test Sabayon I recommend you use version 2.12.1. Its currently building in Fedora extras, so it should be available soon.

Wobbly widgets

Recently I’ve been researching a bit into requirements and usecases for a new canvas for Gnome/Gtk+. One of the important features of a canvas is the ability to embedd normal Gtk+ widgets into the canvas and have them integrate nicely. This means the embedded widgets should properly stack with other canvas items, and (less importantly) they should support arbitrary transformations.

The problem with this is that widgets in Gtk+ use child X windows, for clipping/scrolling and to get events. These windows are managed by X and won’t correctly mix with the drawing of the other items in the canvas. This can be seen in the widget item in gnome-canvas, which just doesn’t work very well.

An obvious solution to this is to render the whole widget to an offscreen pixmap and then just copy that translated to the canvas when rendering the canvas item. However, currently there isn’t a way to render Gtk+ widgets to a pixmap. There are some hacks you can use, but they don’t work at all for widgets that have child windows.

This week I’ve been working on a change to Gdk that will allow it to take any GdkWindow and make it (and its children) into an offscreen pixmap, much like the Composite Extension but client-side. Once we have this you can do all sorts of effects with widgets, just like Composite allows effects on a larger scale.

Yesterday I managed to get it to render an actual widget tree for the first time, so I hacked up a simple demo to show off the sort of things you can do with this:

When seen live the water effect actually moves, as can be seen here.

This work is not nearly finished, I haven’t even started to think about how do handle input events, but it does look promising.

New household member

Yesterday our new household member arrived:

The kittens mother is the cat of my wifes sister, and she (the cat that is) is also staying at our place for the weekend:

We haven’t decided on a name for the kitten yet. We need to get to know her a bit first.

Kittens are like little packages of energy. You get tired just looking at her for 5 minutes. Climbing the plants, running around, eating the furniture, fighting with her mother…

The art of decoding backtraces without debug info

I’ve been debugging some Nautilus crashers today. It involved decoding
backtraces, and since this is a useful thing to be able to do I
decided to do a writeup about it:

Many bugs that get reported contain backtraces, mostly thanks to
bug-buddy. However, many of these reports where made on a system where
the programs and libraries involved didn’t have debug
information. Having the reporter retry with a build that has debug
info (manually built or with debuginfo packages) help tremendously
with debugging the problem.

However, its often hard to get this, as bug-buddy reports are rarely
followed by the reporter, and the bug might be hard to
reproduce. Thus, its important to learn to read backtraces without
debug info. Such backtraces have several issues:

  • They contain no line number information, so you don’t know where in
    a function something happened

  • You cannot see the values of arguments and local variables
  • You cannot trust the function names given in the backtrace, since
    the debugger doesn’t know about static functions.

The first two issues you just have to accept, as there is no way to
extract such information. However the third issue can often be worked
around. This means you can get a mostly accurate trace of what
happened before the crash, which can help you figure out the
problem.

To decode such a backtrace you have to know how the debugger generates
the backtrace. The debugger locates the active stack frame on the top
of the stack by looking at a register. Each such frame contains a
pointer to the invoking frame, plus the address where execution should
continue when that frame returns. Using these addresses, plus the current
instruction pointer, the debugger can figure out which function was
executing. There are two problems though:

  • If the last thing function foo() does is call function bar() and
    return its return value (or bar() returns void) the compiler can do an
    optimization so that the return from bar() immediately returns to the
    function that called foo(). This means such functions will not be
    visible in the backtrace.

  • The way gdb figures out what function is executing is by looking at
    the program/library symbol tables, combined with knowledge about where
    in memory the code was loaded. The last function symbol before the
    executing address is selected. However, in our case the static
    functions are not in the symbol table, so the result is the nearest
    non-static function before the actual function.

Armed with this knowledge and the code for the application you can often
figure out what functions were actually called. Its important that the
code you look at is about the same version as the reporter, since
changes to the code affect the result you get.

As an example, let me take bug
302096
, a nautilus crasher bug that was recently reported. There
are multiple duplicates, all without debug info, and with very vague
reports of how this actually happened.

Here is the backtrace from the bug:

#0  0xffffe410 in __kernel_vsyscall ()
#1  0xb7c0148b in __waitpid_nocancel () from /lib/tls/i686/cmov/libpthread.so.0
#2  0xb7dddd97 in libgnomeui_module_info_get () from /usr/lib/libgnomeui-2.so.0
#3  
#4  0xb77a2a8f in g_type_check_instance_is_a () from /usr/lib/libgobject-2.0.so.0
#5  0x08088666 in nautilus_window_open_location_full ()
#6  0xb7fb9785 in nautilus_window_info_open_location () from /usr/lib/libnautilus-private.so.2
#7  0x08092423 in fm_directory_view_confirm_multiple_windows ()
#8  0x0809db34 in fm_directory_view_notify_selection_changed ()
#9  0xb7f6fcbb in nautilus_directory_add_file_monitors () from /usr/lib/libnautilus-private.so.2
#10 0xb7f70c27 in nautilus_async_destroying_file () from /usr/lib/libnautilus-private.so.2
#11 0xb7f7347f in nautilus_directory_async_state_changed () from /usr/lib/libnautilus-private.so.2
#12 0xb7f7243a in nautilus_directory_force_reload_internal () from /usr/lib/libnautilus-private.so.2
#13 0xb774fb67 in _gnome_vfs_job_complete () from /usr/lib/libgnomevfs-2.so.0
#14 0xb77500a2 in _gnome_vfs_job_complete () from /usr/lib/libgnomevfs-2.so.0
#15 0xb75d1a03 in g_child_watch_add () from /usr/lib/libglib-2.0.so.0
#16 0xb75ced0f in g_main_depth () from /usr/lib/libglib-2.0.so.0
#17 0xb75cfcb5 in g_main_context_dispatch () from /usr/lib/libglib-2.0.so.0
#18 0xb75cffd7 in g_main_context_dispatch () from /usr/lib/libglib-2.0.so.0
#19 0xb75d051e in g_main_loop_run () from /usr/lib/libglib-2.0.so.0
#20 0xb79bd10f in gtk_main () from /usr/lib/libgtk-x11-2.0.so.0
#21 0x0807878a in main ()

Frame 0-3 is just the crash and bug-buddy handling it, so we ignore
those. Frame 4 tells us the crash was likely a NULL pointer or an
invalid pointer passed to some gobject type check. The interesting
parts start at Frame 5.

Looking at the code we see that both
nautilus_window_open_location_full() and
nautilus_window_info_open_location() are followed immediately by
non-static functions. Also, nautilus_window_info_open_location() calls
nautilus_window_open_location_full(), so these are probably right.
However, fm_directory_view_confirm_multiple_windows() is followed by
multiple static functions, and it doesn’t call
nautilus_window_info_open_location(). We then search for a
nautilus_window_info_open_location() call below but before the next
non-static function. Fortunately we only get one hit, open_location().
Doing the same with #8, fm_directory_view_notify_selection_changed() shows
that this must be activate_callback().

#9 is a bit trickier since activate_callback() is a callback
function and won’t be called immediately. However, its only used in
one place where its passed as callback to
nautilus_file_call_when_ready(). So, we start from
nautilus_directory_add_file_monitors() and look for a callback that
would result from such a call. There are not many functions to choose
from, and obviously the call must be from ready_callback_call().
#10 is found out to be call_ready_callbacks() by a simple search. #11
has no non-statics, so it must be right.

#12 is harder, it could be
right, since nautilus_directory_force_reload_internal() does call
nautilus_directory_async_state_changed(), but there are no less than
11 other such calls before the next non-static function. Here we have
to use our knowledge of the code, and the other information that the
bug reporters gave about what they were doing at the time of the
crash. One way forward is to just guess which call was right and work
from that. If you then get a backtrace that makes no sense you know
you picked wrong.

In the bug you can see that I initially guessed that #12 was
nautilus_directory_force_reload_internal() (although I now believe
this to be wrong). #13 is in gnome-vfs, which doesn’t call
nautilus_directory_force_reload_internal(), but there could be one or
more hidden stack frames here, so I greped the code for calls and
found nautilus-vfs-directory.c::vfs_force_reload() as the only
caller. This function ends with a call to the other function and
returns void, so its a likely candidate for the return optimization
meaning it makes sense that its not visible in the backtrace. I
continued a bit after that, but wasn’t able to follow the trace very
long, since there was too many possibilities.

When you’ve finally decoded the backtrace, or at least parts of it you
need to figure out how this set of calls could have resulted in a
crash. For that, you’re on your own. But at least now you have a bit
more information that can help you.

New toys

Today I got a call from UPS saying they were gonna deliver a package to me later today. I immediately realized that this must be the G5 I won in the Linux on POWER contest.

A couple of hours later the UPS guy arrived. I signed my name on the electronic signing thingie (unreadable as always, does that thing have a 10×10 resolution or what?) and I got this box:

I’ve now installed Fedora Core 4 on it, which seems to work perfectly. I’m also playing around with OS X which was preinstalled. Unfortunately it was only 10.3, so I can’t play with the new Tiger features.

Now, all I got to do is figure out where the heck to put it. I already got a bunch of other machines at this desk:

I’m having problems explaining to my girlfriend (soon to be wife!) why I really need this extra computer in addition to all the old ones. She thinks I should just throw away one of the old machines. I think I need to polish my arguments a bit…