Michael Weber: Random Bits and Pieces

Bluetooth Icon

I cannot recommend the MacOS X 10.5.8 upgrade. It promised to improve Bluetooth connectivity (my mouse occasionally loses connection shortly). After the upgrade, I now have to reboot to access BT keyboard and mouse after waking up the MacBook from sleep.

This seriously sucks. Dear Internet, if you happen to know a way around this please do help me out.

Screenshot of non-working OS X Bluetooth
Time Machine Icon Click-click
Click-click who?
Click-click, dead hard disk, harbinger of panic attacks.

Thankfully, I am somewhat battle-hardened in this regard, and I had been using Apple's Time Machine as backup strategy. Even more lucky, I recently bought a NAS solution which automates backups completely and allowed me to forget about them.

The only remaining thrill was that until now, I did not get around verifying that a full restore actually works. However, Restoring from Time Machine delivers as advertised. A couple of hours after exchanging the hard disk, everything was back up again.

There are two small snags I encountered, though. First, the MacOS X installer does not recognize a completely empty hard disk. I used the Disk Utilities application (from the Utilities menu) to format the hard disk.

Second, while this is good enough for the installer to work, the Restore application still does not see the (now-formatted) hard disk. It helped to go back to the initial dialog of the installer (in which the language can be selected) and then go forward until I could choose Restore System from Backup (again from the Utilities menu).

Finder Icon

Despite my earlier voiced contempt for MacPorts, I present you build_macports64, a small script which bootstraps a 64-bit MacPorts environment from their SVN repository. No manual intervention is required.

Dependencies: 32-bit MacPorts installed (you want to keep it anyway for the moment), and a configured sudo. By default, it installs to /opt/local64/.

I have the following packages build and installed (here, universal means x86_64):

% port installed
The following ports are currently installed:
  apr @1.3.3_1+universal (active)
  apr-util @1.3.4_1+universal (active)
  bzip2 @1.0.5_2+universal (active)
  curl @7.19.5_0+universal (active)
  db45 @4.5.20_4+universal (active)
  db46 @4.6.21_5+universal (active)
  expat @2.0.1_0+universal (active)
  gettext @0.17_4+universal (active)
  gmake @3.81_0+universal (active)
  gperf @3.0.4_0+universal (active)
  libiconv @1.12_2+universal (active)
  ncurses @5.7_0+universal (active)
  ncursesw @5.7_0+universal (active)
  openssl @0.9.8k_0+universal (active)
  pkgconfig @0.23_1+universal (active)
  popt @1.15_0+universal (active)
  readline @6.0.000_1+universal (active)
  sqlite3 @3.6.14.1_0+universal (active)
  tcl @8.5.6_0+threads+universal (active)
  zlib @1.2.3_2+universal (active)

Sadly, at the moment there are some crucial packages which do not build as x86_64 even if requested (perl, boost). For others, the build just breaks, e.g., because CFLAGS are not passed properly. But that was to be expected, I guess. With more people using it and reporting/fixing bugs, the situation is likely to improve over time.

Hexley, the mascot for OSX/Darwin

Suppose we want to monitor memory and time usage of a child process. Frequently. On MacOS X. With an unprivileged user. I did not find anywhere a description of how to achieve this. This articles pulls together some of the needed information.

MacOS X's Mach kernel surely does not make it straight-forward, but the biggest showstopper is the lack of documentation on how the low-level operations are supposed to be used together. Apple's stance appears to be that they do not like these interfaces to be used. It was even suggested to parse the output of the ps utility which is not such a hot idea if the monitoring has to be done frequently enough. Adequate high-level interfaces are not provided either.

The Big Picture

Note: much of the following was learned by reading random (undocumented) source code and guess work. Please let me know about any inaccuracies.

Not very long ago, Tim Becker wrote an article on Determining memory usage in process on OSX. The function to use is task_info(), which takes a task port as parameter. We can obtain a task port for our own process via mach_task_self(). For arbitrary processes, the first function that comes to mind is task_for_pid(), which, given a pid_t, returns its task port. But, since we can do a lot of nasty things with task ports, it is a privileged operation, at least on newer versions of OS X (for Intel Macs).

So, we need a different way to get at the task port of a process. In Mach, we have to ask the other process nicely whether it grants us this right. For fork()ed processes, we can establish a communication channel between parent and child, on which the child sends its own task port back to the parent. The parent process is then free to use it in the call to task_info() to obtain information about its child.

Fork(1) It!

There is one small problem, though. In contrast to, e.g., Unix file descriptors, task ports are not inherited across a fork(). Hence the parent process must set up a special bootstrap port to get things off the ground. Since the parent is at the receiving end, the port must be created with MACH_PORT_RIGHT_RECEIVE.

Furthermore, the parent process must explicitly grant its child the right to send back messages via this port. This is done in function setup_recv_port() below.

static int
setup_recv_port (mach_port_t *recv_port)
{
    kern_return_t       err;
    mach_port_t         port = MACH_PORT_NULL;
    err = mach_port_allocate (mach_task_self (),
                              MACH_PORT_RIGHT_RECEIVE, &port);
    CHECK_MACH_ERROR (err, "mach_port_allocate failed:");

    err = mach_port_insert_right (mach_task_self (),
                                  port,
                                  port,
                                  MACH_MSG_TYPE_MAKE_SEND);
    CHECK_MACH_ERROR (err, "mach_port_insert_right failed:");

    *recv_port = port;
    return 0;
}

Subsequently, the newly created port is set as bootstrap port in the parent. It is the only port that is inherited by child processes.

kern_return_t       err;
mach_port_t         parent_recv_port = MACH_PORT_NULL;

if (setup_recv_port (&parent_recv_port) != 0)
    return -1;
err = task_set_bootstrap_port (mach_task_self (), parent_recv_port);
CHECK_MACH_ERROR (err, "task_set_bootstrap_port failed:");

For the child process to send a message back to its parent process it first asks for the bootstrap port. Then we create a message which contains the task port we want to send back (the magic constants may or may not be completely correct, but they appear to work) and send it (function send_port()):

static int
send_port (mach_port_t remote_port, mach_port_t port)
{
    kern_return_t       err;

    struct {
        mach_msg_header_t          header;
        mach_msg_body_t            body;
        mach_msg_port_descriptor_t task_port;
    } msg;

    msg.header.msgh_remote_port = remote_port;
    msg.header.msgh_local_port = MACH_PORT_NULL;
    msg.header.msgh_bits = MACH_MSGH_BITS (MACH_MSG_TYPE_COPY_SEND, 0) |
        MACH_MSGH_BITS_COMPLEX;
    msg.header.msgh_size = sizeof msg;

    msg.body.msgh_descriptor_count = 1;
    msg.task_port.name = port;
    msg.task_port.disposition = MACH_MSG_TYPE_COPY_SEND;
    msg.task_port.type = MACH_MSG_PORT_DESCRIPTOR;

    err = mach_msg_send (&msg.header);
    CHECK_MACH_ERROR (err, "mach_msg_send failed:");

    return 0;
}

On the parent side, we prepare to receive the same message (with a some trailing information tacked on for whatever reason). And voilà, we have the child's task port. It can later be used as first parameter to a task_info() call.

static int
recv_port (mach_port_t recv_port, mach_port_t *port)
{
    kern_return_t       err;
    struct {
        mach_msg_header_t          header;
        mach_msg_body_t            body;
        mach_msg_port_descriptor_t task_port;
        mach_msg_trailer_t         trailer;
    } msg;

    err = mach_msg (&msg.header, MACH_RCV_MSG,
                    0, sizeof msg, recv_port,
                    MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
    CHECK_MACH_ERROR (err, "mach_msg failed:");

    *port = msg.task_port.name;
    return 0;
}

The parent process now has access to the child's task. Now we need to do some cleanup work. We need to restore the original bootstrap port both in parent and child process. This is so that both have access to the Mach bootstrap daemon again. This can be done by having the child process create another port with the above functions, and send it to the parent. Through this, the parent process then sends back its original bootstrap port to the child. (There is probably a simpler way to achieve this. I would gladly like to know.)

Here's the code:

static task_t       child_task = MACH_PORT_NULL;

pid_t
sampling_fork ()
{
    kern_return_t       err;
    mach_port_t         parent_recv_port = MACH_PORT_NULL;
    mach_port_t         child_recv_port = MACH_PORT_NULL;

    if (setup_recv_port (&parent_recv_port) != 0)
        return -1;
    err = task_set_bootstrap_port (mach_task_self (), parent_recv_port);
    CHECK_MACH_ERROR (err, "task_set_bootstrap_port failed:");

    pid_t               pid;
    switch (pid = fork ()) {
    case -1:
        err = mach_port_deallocate (mach_task_self(), parent_recv_port);
        CHECK_MACH_ERROR (err, "mach_port_deallocate failed:");
        return pid;
    case 0: /* child */
        err = task_get_bootstrap_port (mach_task_self (), &parent_recv_port);
        CHECK_MACH_ERROR (err, "task_get_bootstrap_port failed:");
        if (setup_recv_port (&child_recv_port) != 0)
            return -1;
        if (send_port (parent_recv_port, mach_task_self ()) != 0)
            return -1;
        if (send_port (parent_recv_port, child_recv_port) != 0)
            return -1;
        if (recv_port (child_recv_port, &bootstrap_port) != 0)
            return -1;
        err = task_set_bootstrap_port (mach_task_self (), bootstrap_port);
        CHECK_MACH_ERROR (err, "task_set_bootstrap_port failed:");
        break;
    default: /* parent */
        err = task_set_bootstrap_port (mach_task_self (), bootstrap_port);
        CHECK_MACH_ERROR (err, "task_set_bootstrap_port failed:");
        if (recv_port (parent_recv_port, &child_task) != 0)
            return -1;
        if (recv_port (parent_recv_port, &child_recv_port) != 0)
            return -1;
        if (send_port (child_recv_port, bootstrap_port) != 0)
            return -1;
        err = mach_port_deallocate (mach_task_self(), parent_recv_port);
        CHECK_MACH_ERROR (err, "mach_port_deallocate failed:");
        break;
    }

    return pid;
}

Documentation about the Mach IPC Interface is available. In principle, I like how port rights (capabilities) are handled in Mach, compared to the Unix way of sharing almost everything across a fork(). If only it would have been made easier to discover the details.

(On Linux, all this would have been done by reading the information we are interested in from the /proc or /sys file systems. A MacFUSE based procfs filesystem for OS X exists as well, but that is unlikely to be installed, and perhaps too cumbersome to set up for most users.)

UPDATE 2010-01-12: Visibility

A number of people have contacted me about the sampling_fork code example. It has found its way into Will Drewry's patient0 and the Chromium browser.

NetNewsWire: More news, less junk. Faster.

Reading news feeds? Forget Safari. Forget Apple Mail. Instead, try out NetNewsWire: It sports a convenient user interface and an integrated web browser (via WebKit), need I say more?

NetNewsWire Screenshot

I do not follow many news feeds, and after I switched to MacOS X, I used to use Safari's built-in feed reader. It was nothing to write home about, but it has a simple user interface and worked well enough for my modest needs. With OS X 10.5 Leopard, Apple Mail grew RSS support and I switched to it as default feed reader. I liked that it was the same interface as for email, but some things were rubbing me the wrong way. For instance, I created a Smart Mailbox to get rid of already read feed items: Smart Mailbox in Apple
Mail

The main show stopper was that some feeds basically require to visit a web page (e.g., the BBC News feed contains only a one-sentence summary of the stories. When in Apple Mail, this causes a switch to the default Web Browser, and possibly a desktop switch as well (thanks to Spaces).

With NetNewsWire, the awkward application switching (and backswitching) is history. Now, if only it could also view PDF files inline (via PDFKit)...

Page 1/5: 1 2 3 4 5 »