Todo web server and application in C

2023-10-16

A few weeks ago, I saw a post on my company's internal chat room that someone wrote a small todo application in x86 assembly. It's a topic, along with starting to write C, that I have been taking some interest in with some earnest. For me, it is also a way to jump into Unix tooling. Since I have been interested in this a bit for awhile, I decided to read through it and rewrite it as closely as possible in C. Here is the result of that: https://gitlab.com/bolsen80/todo.c. The original project is here: https://github.com/tsoding/todo.asm with it being discussed here: https://www.youtube.com/watch?v=WnBXLmKk_qw&t=2494s.

This turned out to be interesting because assembly (I would think particularly x86) can get confusing. You need to write as simple and descriptive as possible because the source code does not document itself at all. Some x86 instructions have implicit behavior and side effects. For example, add rax, 1 is simple enough, but you need to know that it can set carry flags. Somethine like, for example: mul rdx implicitly multiplies the rax register with the rdx register, along with the familiar carry flags being set.

What is interesting about tsoding's attempt is the clarity around the mimicked functions; it's a good first look at x86 assembly to be honest, where there is a clear set of 'functions' that make system calls. His version does not use libc at all, because most of the work can just be done in the kernel. As part of my little attempt, I only used libc functions that represented the system calls (read, write, socket, bind, accept) without particularly I/O buffering functions (like, printf(3) buffers the I/O, to avoid extra system calls.)

The application itself is an XSS minefield :D Tsoding jokes: "XSS support in case you still want to have JavaScript on the Frontend". Maybe doing html encoding in C can be a challenge for another day. :)

There is one issue with it that I hope to understand better. The accept loop takes one connection at a time. When calling listen(2), the kernel sets up a buffer of connections that accept(2) takes from. Sometimes a connection might be present, but accept(2) is not taking from the queue until after a long delay. I don't imagine this to be uncommon. As a follow-up, I am going to tinker with making the accept(2) call non-blocking with fcntl(2) and poll(2) or epoll(7) (which is Linux-specific.)

In: c programming