--- category: '' date: 2006/09/17 03:36 description: '' link: '' priority: '' slug: '45' tags: linux title: Going Higher Level, Leaving the Shell (Part I) type: text updated: 2006/09/17 03:36 url_type: '' --- .. contents:: Introduction ------------ Why are we using shells? We are using them for interactive purposes. I suppose that's ok. I do it, at least. But why are we using them for system scripts? It's not that they are fast, or small (at least not bash, which most linux distros use)... It's not that it's simple, at least not if you try to make scripts that are not very buggy because of the complex parsing... It must be because we expect every system to have a Bourne shell somewhere. Many Linux users even expect **bash** to be there, and blithely adorn scripts full of bashisms with #!/bin/sh on top. This has been bugging me for a long time, and then it hit me... what would it take to switch a distro so it can boot **without a shell**? And because I am a moron... I decided to try. I will do it with a basic Arch Linux 0.7.2 installation. I will change all its init scripts to work on Lua. Why? Well, for starters, Lua is small and featureful. Then again, I don't actually know Lua. So this is probably going to be interesting. I chose Arch because it has a very basic startup script set. Yes, I have blogged a few days ago that I thought them simplistic. So what? They are still simple, which means they are easier to rewrite. Step 1: /etc/rc.conf -------------------- Pretty much everything in Arch's system uses the rc.conf file. I suppose some users don't even understand the implications of that: you screw this file (which, remember, is written in a pretty evil language) and you break most of the system. Because, yes, this is a shell script fragment, used as a configuration file. Which is more common than you think. However, as long as you only touch what's there and don't write extra stuff, you will be ok. Converting to Lua was trivial, ``X=(a,b,c)`` converts to ``X={"a","b","c"}`` Comments are "--" instead of "#". Then your scripts do ``dofile ("/etc/rc.conf.lua")`` instead of ``. /etc/rc.conf``. So, no problem at all so far. Step 2: /etc/rc.d/functions --------------------------- This is trickier. This file contains functions to display messages, like this:: stat_fail() { deltext /bin/echo -e " $C_OTHER[${C_FAIL}FAIL$C_OTHER]$C_CLEAR " } Where C_OTHER and company are stuff like this:: C_OTHER="\033[1;34m" # prefix & brackets The only difference is that Lua doesn't use octal:: C_OTHER="\27[1;34m" -- prefix & brackets Now look like this (echo is basically print):: function stat_fail() deltext() echo (" "..C_OTHER.."["..C_FAIL.."FAIL"..C_OTHER.."]"..C_CLEAR.." ") end Now, the first real hurdle comes up. I am not going to replicate the whole linux shell utils in Lua. So, we need a way to do backquoting. Backquoting is one of those "nifty" shell features. You put a command in backquotes, and you get that replaced, at runtime, with the output of the command. It's not hard, though. All I need is a way to open a pipe to the command, and read the output. And it's in the docs ;-) :: function shell(c) local o, h h = assert(io.popen( c,"r" )) o = h:read("*all") h:close() return o end So, let's add that to functions.lua first of all. Then, we turn things like this:: STAT_COL=$[`stty size | awk 'BEGIN { RS=" " }; END { print $1 }'` - 13] Into this:: STAT_COL=tonumber(shell("stty size | awk 'BEGIN { RS=" " }; END { print $1 }'")) - 13 Which is not much better, but hey, the really ugly part is **still shell script (and AWK!)** ;-) So, how can we make that nicer? How about this (split is from Lua's wiki):: STAT_COL=tonumber(split(shell("stty size")," ")[2]-13) Nicer, shorter... less languajes per line... I think I am enjoying this! But anyway, here's a better idea:: STAT_COL=tonumber(shell("tput cols"))-13 Another hurdle:: [ -d /var/run/daemons ] I just can't find a way to see if a folder exists in Lua's standard library. Hell, there is no way even to **create** a folder! But no problem, there is an add-on called luafilesystem, which has all that so we are still ok. Not trivial, but very simple, so far. I had to write a few helper functions for string manipulation, and some "shell-like" features, but not hard at all. Having to go outside the language already is a little worrying, though. Step 3: rc.sysinit ------------------ This 267 line shell script runs at the very beginning of the booting process, so it's our next step. It's not impossible either, but it's starting to get harder. The main thing is the extremely limited standard library that comes with Lua. There is no way to: * Check permissions * Change permissions * See if a file exists And many other things a shell has to be able to handle easily. One extra annoyance are Lua's comments. Since they are different from every other scripting language, you can't use even this trivial shell file:: # Set your NIS domain name here NISDOMAINNAME="" So you have to create this one:: -- Set your NIS domain name here NISDOMAINNAME="" That's painful, in part because it's so simple. Some pieces of the script are converted in the bad way, using os.execute() which means they are **more expensive** than in shell, because it spawns a shell for that command alone. Number of things running like that: 35. I need to find a way to run external commands witout invoking a shell. Yes, a fork/exec. Which is not there, it seems ;-) So, the script works as a Lua script, but it's cheating a bit. Status after an afternoon of hacking: ------------------------------------- I changed functions and rc.conf, and have a hackish rc.sysinit. That works, which means right now the system is booting in Lua, except for rc.multi (which is quite short) Tomorrow's job: write a decent shelly library for Lua.