Sunday, May 1, 2011

Python based Init

Update: 
05/05/2011 - The issue does not reside in Pythons implementation of signal, but it was rather an issue with Python being compiled against uClibc version 0.9.31.  Recompiling with version 0.9.32-rc3 of uClibc and enabling NPTL seems to have corrected the issue.  I will leave the original, incorrect post as a example of ctypes callback feature- Just ignore the other ignorant rambling ;)



I've stubbled upon the first major issue with re-writing the userland of Linux in Python. Linux seems to filter all signals to PID 1 or the Init process; only passing the signals Init has installed handlers for.  This makes sense and explains why you can't kill Init with a SIGKILL (I've tried, haven't you?).

The issue is, for some reason, the signal module in Python doesn't seem to install the signal handler in a way Linux expects from Init.  Here is a post on SO explaining the issue.  This was looking like a show stopper.  Just to ensure I wasn't crazy, I compiled a quick C program that would spawn a child process and the child would send a signal to PID 1- it worked so the issue is with Python's signal handling.

It was looking like the first piece of the TeeOS system was going to have to be a compiled binary until I thought about using the ctypes module.  I'd already used ctypes to use the mount() function in libc to mount /proc (Python doesn't offer an os.mount() as I would have expected).

Using ctypes and loading the systems C Library I could call the binary signal() function in the libc shared object.  It took awhile to figure out the callback function (the signal handler) as I wanted to keep that a Python function if I could.  After reading the callback example on the ctypes callback more than a few times I ended up with this test Init script:

#!/usr/bin/python

import os
import sys
import time

from ctypes import *

def SigHUP():
    print "Caught SIGHUP"
    return 0

def SigCHLD():
    print "Caught SIGCHLD"
    return 0

SIGFUNC = CFUNCTYPE(c_int)
SigHUPFunc = SIGFUNC(SigHUP)
SigCHLDFunc = SIGFUNC(SigCHLD)

libc = cdll.LoadLibrary('libc.so.0')
libc.signal(1, SigHUPFunc)
libc.signal(17, SigCHLDFunc)

print "\n\n\n\n\n\n\n\n\33[31mWelcome to TeeOS\33[0m"

print "Mounting Proc: %s" % libc.mount(None, "/proc", "proc", 0, None)

print "forking for ash"
cpid = os.fork()
if cpid == 0:
    os.closerange(0, 4)
    sys.stdin = open('/dev/tty2', 'r')
    sys.stdout = open('/dev/tty2', 'w')
    sys.stderr = open('/dev/tty2', 'w')
    os.execv('/bin/ash', ('ash',))

print "ash started on tty2"

print "sleeping"
while True:
    libc.pause()

Using the signal handling functions directly in libc has allowed me to not use the the Python signal module at all.  Using the command `kill -l` I can find the needed signal numbers and hard code those in the program.  Since portablity isn't at all an issue, this works.

Testing the above code worked perfectly.  I'm not sure if this is a known issue with Python or if I'm just trying to color to far outside the lines.  Now its time to write the first major piece of the TeeOS system.

1 comment:

  1. samplebias from SO posted a possible solution to use straight Python code- which didn't work. However, I think his post is very informative and worth a read for anyone trying to do signal handling in Python.

    ReplyDelete