Blog

Building a Tiny KIO Slave

I had an application talking to a small read-only service and was about to put the protocol code directly into its file dialog path. A KIO slave turned out to be a cleaner experiment: teach KDE one URL scheme, then let Konqueror and any KIO-aware application use it.

The slave is a separate process driven through KIO’s protocol. A class derived from KIO::SlaveBase implements operations such as get, stat, or listDir, depending on what the scheme supports. For a tiny read-only resource, get is enough to prove the mechanism:

void NoteProtocol::get(const KURL &url)
{
    QByteArray bytes = loadNote(url.path());

    mimeType("text/plain");
    totalSize(bytes.size());
    data(bytes);
    data(QByteArray());
    finished();
}

Real code must report failures with error() rather than returning silently. It should also send data in useful chunks for large objects instead of reading everything into memory as this demonstration does. The empty data block marks the end of data before finished() completes the operation.

KIO discovers the slave through an installed protocol description and service information. Those files state the scheme, executable, supported operations, and whether the protocol works with files, directories, reading, or writing. If Konqueror says the protocol is unknown, I check installation paths and run kbuildsycoca before blaming the C++.

The process boundary is the interesting part. The client starts a KIO job. KIO selects and communicates with the slave, and the slave translates operations into the remote or special resource’s rules. A failure in the protocol handler need not take the client application down with it, and authentication or progress can use KDE’s common machinery.

I am careful not to claim operations the protocol cannot honor. Advertising rename for an immutable service only moves the error from an absent menu item to an annoyed user. stat results must also be consistent with get; applications rely on metadata for decisions before fetching.

I test the scheme directly in Konqueror and with a tiny KIO client, including a missing object, an empty object, and a path containing escaped characters. URL decoding must happen at the protocol boundary exactly once. Decoding twice turns a perfectly legal percent sign into an accidental instruction.

My preference is a KIO slave when a resource genuinely behaves like a hierarchy of files or directories and should be available across KDE. For one private command, DCOP or an application-specific class is smaller. Giving every service a URL is attractive, but some services are verbs wearing a hat.

Let DCOP Tell You the Signature

A DCOP call failed today even though the method name was correct. The method name, unfortunately, was only part of the name that mattered. I had sent an int; the exported function expected an unsigned value.

Before changing code, I ask the running object what it exposes:

dcop myapp MainIface

The output includes complete function signatures. In this case it shows void setPage(unsigned int). I then call it from the command line with a small test argument:

dcop myapp MainIface setPage 3

DCOP identifies methods by their signatures and marshals known Qt types through QDataStream. The sending and receiving sides must therefore agree on argument types, order, and return type. A visually similar C++ declaration is not necessarily the same wire contract.

For custom values, both programs need compatible streaming operators and type registration. I avoid custom structures in small public interfaces when a QString, QCString, or simple list expresses the operation clearly. Fewer private types also make the command-line client useful.

When writing the C++ caller, I keep the request and reply types visible next to each other:

QByteArray data;
QDataStream args(data, IO_WriteOnly);
unsigned int page = 3;
args << page;

QCString replyType;
QByteArray replyData;
if (!client->call(app, "MainIface", "setPage(unsigned int)",
                  data, replyType, replyData)) {
    kdWarning() << "DCOP setPage call failed" << endl;
}

For a function with a return value, I verify replyType before extracting from replyData. Deserializing a reply under the wrong assumption merely converts a useful interface error into strange application state.

The application and object names should not be copied from a single run if multiple instances are possible. I list registered clients and decide whether the program is meant to address one instance, every instance, or the instance owning a particular document. A suffix added to keep names unique is normal, not a DCOP server losing its arithmetic.

I also preserve failures in scripts. The command-line client returns useful status, and a shell script should check it rather than carrying on after the target application has exited. Interprocess calls cross a lifetime boundary; yesterday’s object name is not a reservation for today.

I keep the exported interface narrow and test it independently of the window. If the command works but a toolbar action does not, DCOP has been acquitted and I can inspect the connection instead.

I let dcop provide the authoritative spelling instead of copying it from memory. Memory is fast, local, and apparently not type-safe.

Moving a KDE 3 Application to 3.1

After installing KDE 3.1, I rebuilt a small KDE 3.0 application expecting either complete success or a useful compiler failure. It built cleanly and then displayed a toolbar assembled from an older installed XML file. Compatibility had done its job; my installation habits had not.

The move from 3.0 to 3.1 is not the upheaval that the KDE 2 to KDE 3 port was. The first task should be a clean rebuild against the new headers and libraries, not a rewrite:

make -f Makefile.cvs
mkdir build-31
cd build-31
../configure --prefix="$KDEDIR" --enable-debug
make
make install

I check the configure summary to ensure it found the intended Qt and KDE prefixes. Having 3.0 under one prefix and 3.1 under another can produce a convincing but incoherent mixture of headers, libraries, services, and data files. kde-config --prefix and kde-config --path data help explain what the running session will search.

Installed resources matter as much as the executable. XMLGUI files, icons, desktop entries, service descriptions, and translations may survive from an earlier build. During testing I inspect the installed paths and remove only obsolete copies belonging to my application. Otherwise a changed action name in the source can appear to have no effect because the shell merged an old resource file.

I then exercise integration points: actions and standard shortcuts, session restoration, DCOP interfaces, KIO URLs, printing, and embedded parts. These boundaries are more likely to expose an assumption than an ordinary button click. Running from a terminal catches connection warnings and missing service messages that a polished window politely conceals.

If I choose to use a new 3.1 facility, I make that a separate change after the compatible build works. This keeps the required minimum version honest. Merely compiling on my workstation does not mean every user has the same minor release.

I also keep the old configuration file for one run and start with an empty one for another. Migration defects and default-setting defects often cancel each other on a developer’s account. Testing both states prevents an old saved value from making the fresh installation appear correct.

My preference is to preserve KDE 3.0 compatibility unless the new feature materially improves the program. The caveat is that testing both versions then becomes real work, including compile tests against both sets of development files. Compatibility declared but never built is just a hopeful comment with better publicity.

Module Parameters in Linux 2.4

I was rebuilding a test module merely to change its debug level. That is a fine way to turn a ten-second experiment into a small ritual. Linux 2.4 module parameters are a better fit.

For a simple integer, the old module parameter declaration is compact:

static int debug;
MODULE_PARM(debug, "i");
MODULE_PARM_DESC(debug, "enable diagnostic messages");

I can then set it while loading:

insmod sample.o debug=1

The type string tells the module loader how to interpret the value. Other forms support strings and arrays, but I avoid elaborate configuration here. Parameters are useful for hardware choices and diagnostics needed at initialization, not as a private replacement for a proper user interface.

Validation still belongs in the initialization function. A parameter arriving from insmod is input, not a promise. If a buffer count must be positive and bounded, the module should reject nonsense with -EINVAL before allocating resources.

I also print the effective non-default setting at load time when it materially changes behavior. That makes dmesg useful when somebody reports that the driver behaves differently on one machine.

My preference is a quiet default and one obvious parameter for debugging. Five boolean switches rapidly become a combination lock whose correct setting nobody remembers. Recompiling was tedious, but at least it did not accept debug=banana with a straight face.

Reading an skb Without Regretting It

I added a packet check to a Linux 2.4 network path and initially treated skb->data as if it always began with the header I wanted. It did in my first test. Networking code has a generous way of rewarding that assumption just long enough to become embarrassing.

The sk_buff carries packet data plus bookkeeping used as the packet moves through layers. Its head, data, tail, and end pointers describe the allocated area and the currently occupied bytes. Protocol processing can pull a consumed header from the front or push space for a new one without copying the whole packet.

Before reading a header, I check that the required bytes are present and use the header position appropriate to that point in the stack. In a simple driver receive path, the driver allocates an skb, reserves alignment if needed, copies or maps packet bytes, sets the protocol, and hands it upward:

skb = dev_alloc_skb(length + 2);
if (!skb)
    return -ENOMEM;

skb_reserve(skb, 2);
memcpy(skb_put(skb, length), packet, length);
skb->dev = dev;
skb->protocol = eth_type_trans(skb, dev);
netif_rx(skb);

Calling skb_put() extends the valid data at the tail and returns the start of the added area. Writing beyond that area corrupts memory, so the allocation and length must agree. eth_type_trans() interprets the Ethernet header, sets the protocol, and adjusts the skb for the network layer.

Ownership changes at netif_rx(). After handing the skb to the network stack, the driver must not free or inspect it as though it still belongs to the receive routine. Error paths before that handoff must free what they allocated.

Transmit has the opposite pressure. The driver’s hard_start_xmit receives an skb and must obey the device queue rules. If hardware resources are exhausted, stopping the queue and waking it when descriptors become available is better than quietly dropping ownership conventions. Interrupt and bottom-half paths also require locking suited to their contexts; sleeping locks are not available there.

I prefer using skb helpers over pointer arithmetic, even when the arithmetic looks obvious. The helpers document whether code is adding, removing, or reserving bytes and retain their checks in debugging builds. The caveat is that not every skb is necessarily laid out as one convenient linear region at every layer, so code must respect the guarantees of its exact call site. Packets are simple on diagrams. The diagrams do not have cache lines.