PIMp my calendar, part 4

Previous parts of the saga brought me to a point where I had an upgraded instance of the Radicale CalDAV server talking to my Android phone's CalDAV adapter and still getting nowhere. I decided to debug the Android-side code.

Attaching the debugger was a bit of a trick since the adapter has no Activity. Once I got past that, I discovered the response from Radicale was getting parsed just fine but it was only happening in an authentication context (i.e. while adding the new "CalDAV account"). I generally found that the adapter was basically a bunch of callbacks and I seemed unable to properly trigger them.

I then noticed something funny in the log (i.e. the LogCat view in the DDMS perspective in Eclipse):

VFY: unable to resolve static field 27 (CONTENT_URI) in Landroid/provider/CalendarContract$Events;

According to the documentation, Events.CONTENT_URI is one of the basic constants used when working with calendars - how could it be missing? The question led to a quick series of discoveries:

  • My phone runs Android 2.3.7 a.k.a. Gingerbread (well, not really a discovery for me but a key piece of the puzzle).
  • Proper support for non-Google calendars was only added in version 4.0 a.k.a. Ice Cream Sandwich; what there was before was unofficial and unsupported.
  • The CalDAV Adapter project lists API level 14 (Ice Cream Sandwich) as the minimum required API level in its Android manifest.
  • Back when I first tried to run the CalDAV adapter, Eclipse ADT had told me I had no compatible device; I tried modifying the manifest, changing the minimum API level to 10 (Gingerbread).
  • Changing the minimum API level of an Android project doesn't trigger a re-build in Eclipse ADT; changing the target API level does trigger a re-build.
  • Once prompted to re-build the project, ADT flags usage of higher-than-minimum-level APIs as an error, breaking the build.
  • When Dalvik encounters unknown API usage while executing the code, it marks the block where the API was used as dead code and moves on.

So I was trying to use an application that was incompatible with my handset and both the development environment and the runtime responded with silent failures. I think ADT in particular behaved quite impolitely. If using APIs beyond the minimum level is off-limits then changing the minimum level should trigger a re-build, right? As for the Android runtime, the documentation says

...the application will crash during runtime when attempting to access the unavailable APIs.

which seems to be false (or at least no indication of a crash reaches the user). In any case, the approach I'd been taking turned out to be a dead end. Continued in part 5.


PIMp my calendar, part 3

My efforts to put a server-backed calendar on my Android phone entered a new phase. I had Radicale on the server and the CalDAV adapater on the phone but they seemed to talk past each other. My phone would set up a CalDAV account successfully but then tell me "You have no calendar" when I tried to create events.

Radicale documentation says calendars are created automagically based on a URL convention, i.e. simply accessing a non-existent calendar should bring it into existence. I tried it with the Mozilla Lightning client and it worked as advertised. The Android adapter, however, still wouldn't see the new calendar nor its events.

WireShark revealed that a CalDAV client apparently enjoys a lot of freedom when formulating requests. A PROPFIND request from Lightning looked like this:

<D:propfind xmlns:D="DAV:"
    xmlns:CS="http://calendarserver.org/ns/"
    xmlns:C="urn:ietf:params:xml:ns:caldav">
  <D:prop>
    <D:resourcetype/>
    <D:owner/>
    <D:supported-report-set/>
    <C:supported-calendar-component-set/>
    <CS:getctag/>
  </D:prop>
</D:propfind>

whereas the Android adapter was sending

<d:propfind xmlns:d="DAV:"
    xmlns:c="urn:ietf:params:xml:ns:caldav"
    xmlns:cs="http://calendarserver.org/ns/"
    xmlns:ic="http://apple.com/ns/ical/">
  <d:prop>
    <d:displayname />
    <d:resourcetype />
    <cs:getctag />
  </d:prop>
</d:propfind>

and it seemed Radicale was not happy with the displayname bit:

...
<propstat>
  <prop>
    <displayname />
  </prop>
  <status>HTTP/1.1 404 Not Found</status>
</propstat>
...

Server-side source code revealed that Radicale returns 404 for properties which it does not recognize (see the final else in the long if-elif chain in _propfind_response() in xmlutils.py). My installed version of Radicale was 0.7. I downloaded the most recent stable release (0.7.1) from the project site and tried that instead. Lo and behold, it finally said

<multistatus xmlns="DAV:"
    xmlns:C="urn:ietf:params:xml:ns:caldav"
    xmlns:CS="http://calendarserver.org/ns/">
  <response>
    <href>/jh/calendar/</href>
    <propstat>
      <prop>
        <displayname>calendar</displayname>
        <resourcetype>
          <C:calendar />
          <collection />
        </resourcetype>
        <CS:getctag>"0"</CS:getctag>
      </prop>
      <status>HTTP/1.1 200 OK</status>
    </propstat>
  </response>
</multistatus>

The Calendar app on my phone, however, still insisted I had no calendar. Continued in part 4.


PIMp my calendar, part 2

My effort to set up a self-hosted calendaring solution is taking on epic proportions, turning into another CUPS saga:

  • I quickly discarded the local iCal option because I can't trust a piece of closed-source freeware from someone calling themselves "Khaos".
  • The Kolab connector dropped out of the race because Kolab itself has complex dependencies and no OpenBSD package.
  • Zafara just felt too heavy-weight and, well, corporate for my needs.

I decided to concentrate on CalDAV servers with existing OpenBSD packages: DAVical, ownCloud, Radicale and SabreDAV. All except Radicale were PHP-based which made my decision easy as I'm allergic to PHP. I have to admit, though, ownCloud looks mighty fine and seems to have a lot of momentum.

Radicale has a no-nonsense, focused feel. It is written in Python which is very nice indeed. Installing it was a breeze. I then tried to set up a calendar in Kontact and failed miserably. CalDAV support in Akonadi is apparently confined to a legacy KResource implementation and blah blah blah. I had no stamina to go there. What was important was my phone.

Installing Gérald Garcia's open-source AndroidCaldavSyncAdapater via Google Play would have been a snap. The thing is, just as I don't want Google knowing about my appointments I don't want it knowing what I run on my phone. Don't get me wrong - I'm quite fond of Google. I'm simply more fond of my privacy.

Installing the adapter from GitHub turned out to be quite smooth. Once I cloned the repository I found out the project relied on the ADT Eclipse plugin for building and signing the installation package - no trouble there. I had a bit of fun getting ADT to see my phone because I made a typo in the udev rule file. I figured it out eventually and got the package onto the phone.

Adding a Radicale account on Android involved going to Settings > Accounts & sync > Add account > CalDAV Sync Adapter and filling out my user name, password and the URL. Excited, I opened the calendar application. No nagging about MS Exchange - great. Alas, when I tried to create an event I was told "You have no calendars." Continued in part 3.


PIMp my calendar, part 1

I have a confession to make: I'm a PIM dinosaur. My appointment list resides in an ultra-minimalist GTD app I wrote as an exercise in HTML5 local storage. Even though it runs on my Android phone, it has no alarm function (did I say it was HTML5?). It's just a list of appointments I have to actively look into.

Of course, the phone has a native calendar app. Back when I had stock firmware it demanded my Google account details before it would talk to me. My appointments are none of Google's business (despite what Google may think) so I stayed away from the app. Now that I run CyanogenMod it asks about my MS Exchange account. Let's just say I don't do MS Exchange.

An alarm would be nice, though. Syncing with Kontact on my notebook wouldn't hurt either. Since I'm in the process of building a new home server, I considered fixing my calendar woes as well.

My research turned up several interesting facts:

  • The Android calendar can be backed by anything which says it's a "calendar service".
  • The default calendar service on my phone talks ActiveSync - the MS Exchange calendar synchronization protocol.
  • Other groupware tools besides MS Exchange talk ActiveSync.
    • Zafara with its Z-Push connector looks the most mature.
  • Other calendar services for Android exist.

It seems I first need to decide whether I would actually use the syncing capability of a server or whether it's best to choose the iCal option and forget about a server altogether. Continued in part 2.


The battle of the C5280: Aftermath

After a protracted investigation spanning several days (see part 1, part 2, part 3 and part 4), my new home server is finally providing access to my HP Photosmart C5280 printer-scanner combo. The basic goal has been achieved but I cannot be very happy with the end result. To get the printer to work, I had to disable the ulpt and umass USB drivers in the server's OpenBSD 5.2 kernel - they were both being assigned to the device along with the ugen driver the system actually wanted to talk to.

Granted, the ulpt driver is largely superfluous when the printer works with ugen (though I can imagine having another USB printer to which I'd want to print through a service other than CUPS that would specifically need ulpt). Disabling umass is more serious. As it happens, my home server needs no USB storage at the moment but that could change in the future, putting me in a difficult spot. I don't think this is an acceptable state of affairs in the long term, especially when the previous home server running OpenBSD 4.8 exhibits no such limitations.

Regarding the effort it took me to get to this point, it was largely a function of my insistence on figuring things out on my own. I do enjoy this sort of detective work from time to time and I did learn a bunch of new things so it was definitely time well spent. Truly resolving the issue is beyond my capacity, however. It's a task for OpenBSD hackers who know their way around USB plumbing.

UPDATE The issue has been fixed in OpenBSD 5.3 which was released on May 1, 2013.


The battle of the C5280, part 4

I have previously recounted how I forced libusb to print trace statements, cajoled CUPS into recognizing my printer and mastered gdb in order to probe in CUPS' bowels.

Fortunately, no such bowel-probing was needed. As I examined where the NULL in dpriv->devname may have come from, I found out it was only being set in good old obsd_get_device_list:

dpriv->devname = NULL;

/*
 * If a device is attached to ugen(4) it has
 * only one 'devname'.
 */
if (!strncmp("ugen", di.udi_devnames[0], 4))
    dpriv->devname =
        strdup(di.udi_devnames[0]);

I duly exercised the spot via my toy program with a breakpoint at the strcmp line. It turned out that the expectation expressed over there in the comment is, in my case, wrong. At busnode /dev/usb0, address 3, the value of di.udi_devnames was ["ulpt0", "umass0", "ugen0"].

My subsequent search for solutions involved a crash-course in USB driver architecture and lots of source-code browsing. The general aim was to make the resulting libusb_device palatable to CUPS by setting it up correctly. It was a tall order and I didn't really figure out what the usb man page meant when it said

For each USB device there may be additional drivers attached to it.

More specifically, I didn't find out whether it was possible to "activate" a different driver than the default one (perhaps by switching the device to another configuration). I suspect it would have taken serious time to finish that investigation but along the way I discovered CUPS' package readme (first as pkg/README in the port directory, then under /usr/local/share/doc after installing). It advised removing the ulpt driver as colliding with ugen.

I tried the suggested remedy (using config -e -o) and it didn't work - but it wouldn't, would it? The string in di.udi_devnames[0] still didn't start with "ugen". I obviously had to remove the umass driver as well. One more reboot and... the test page materialized.


The battle of the C5280, part 3

In part 1 and part 2 I describe how I got CUPS to recognize my printer. Alas, the battle was far from over: it still refused to print. Back in /var/log/cups/error_log I found

libusb write operation returned fffffff4.

Of course, fffffff4 translates to -12 which translates to LIBUSB_ERROR_NOT_SUPPORTED. More trace statements were in order.

From the surrounding log messages I figured out that the error was returned from obsd_submit_transfer in openbsd_usb.c. I found all places in that function where the error could have been returned and equipped them with traces. The culprit turned out to be one level deeper, in _sync_gen_transfer:

if (dpriv->devname == NULL)
    return (LIBUSB_ERROR_NOT_SUPPORTED);

By now my round-trip was quite masochistic:

  1. Edit openbsd_usb.c as patched by the port.
  2. Make a new patch.
  3. Replace the port's patch with the new one.
  4. Rebuild port.
  5. Stop CUPS.
  6. Uninstall libusb and CUPS (CUPS depends on libusb).
  7. Install libusb from the port.
  8. Install CUPS.
  9. Start CUPS.
  10. Try something.
  11. See what turns up in the log.

I did have a script for steps 2 through 9 but now I needed to find out how NULL got into dpriv->devname. I realized it was time to learn gdb - the GNU interactive debugger.

If you click on that link you will find a typical OpenBSD man page: clear, succint, to the point (I've said this before: documentation is the most impressive thing about OpenBSD, by far). Figuring out how to compile libusb with debugging symbols was another stumbling block, however. The line .ifdef DEBUG in the port's Makefile had given me a mistaken impression that the value of DEBUG didn't matter. I put DEBUG=1 into /etc/mk.conf and libusb promptly stopped building, with configure saying something to the effect that CC was unable to generate an executable. Rummaging throug configure.log, the incriminating lines looked approximately like this:

cc -O2 -pipe 1 conftest.c
cc: 1: No such file or directory

Silly me thought the first "1" was a parameter for the "-pipe" option while the other "1" was a line number. Once I put one and one together (ahem), man cc told me DEBUG should be -g, obviously, after which make clean build produced a file 100K bigger than before, pregnant with debugging wisdom. I was happy.

Having conquered the last obstacle, I was stepping through my toy program in no time. I even found the multi-window ncurses-based TUI (text user interface) although that wasn't mentioned in the man page. One irritating thing about TUI was that it didn't have the debbuged program's standard streams under control. Each time a trace statement was printed it blew up the layout. I finally turned it off by running gdb with -tty=/dev/null which wasn't ideal either but it was an improvement. To be frank, I would expect TUI to have a dedicated console window to handle the program's I/O. Alas, that doesn't seem to be the case.

The source seemed to be somewhat out of sync with the code being executed ("next" kept jumping back and forth, gdb itself would segfault once I launched the program a few times). I ascribed this to optimizations performed by CC so I turned them off by saying CFLAGS=-O0 -g in the port's Makefile and rebuilding (the -g was necessary because apparently CFLAGS in the Makefile overrides CFLAGS assembled by other means). To my surprise, it did help - stepping became perfectly smooth from then on.

Of course, gdb only took me deeper into the mystery. Continued in part 4.


The battle of the C5280, part 2

I ended part 1 with a description of my struggles to display trace statements inserted into libusb. One spot where I needed to add a trace was

if ((fd = open(busnode, O_RDWR)) < 0) {
    if (errno != ENOENT && errno != ENXIO)
        usbi_err(ctx, "could not open %s", busnode);
    continue;
}

Notice that the code here keeps a rather important detail (errno) to itself. I changed the code to

if ((fd = open(busnode, O_RDWR)) < 0) {
    if (errno != ENOENT && errno != ENXIO) {
        usbi_err(ctx, "could not open %s", busnode);
        fprintf(stderr, "errno: %d", errno);
    }
    continue;
}

and once the output started flowing I found out that errno was 13 (EACCESS). A classic file permissions problem, it's just that it wasn't very well reported. It should have been apparent back when I saw the backend work without problems when run directly.

Rather than figuring out which user's permissions are in effect when CUPS calls the backend, I simply turned on global read-write permissions for /dev/ugen* to see if it would help. It didn't.

After another bit of "fun" detective work, I found the reason buried in the libusb port. For those unfamiliar with OpenBSD ports, a port is basically a recipe for turning an external piece of software into an OpenBSD package. It contains instructions for fetching and unpacking the source, patching it for OpenBSD, building it, packaging the result in a neat .tgz file and even installing an unistalling it.

I noticed that the patch for openbsd_usb.c was rather big - almost 18K. On closer look, it was a substantial rewrite of the whole file which was kind of unexpected since the original code was already supposed to be OpenBSD-specific. One thing the rewrite changed was that it was no longer iterating over /dev/ugen but rather over /dev/usb. I failed to notice it when inserting trace statements - the pattern had been extracted into constants at the start of the file.

Armed with new wisdom, I turned on global read-write permissions for /dev/usb*. Progress! CUPS finally found my printer. I clicked on "Print test page". As you may have guessed, nothing happened. Continued in part 3.


« Page 3 / 6 »
Proudly powered by Pelican, which takes great advantage of Python.