Skip to content

How does nginx write files in C

I searched for access_log in the entire project and stumbled upon the ngx_http_log_module.c file which also has the following code:

C
static void
ngx_http_log_write(ngx_http_request_t *r, ngx_http_log_t *log, u_char *buf,
    size_t len)
{
    ....

        } else {
            n = ngx_write_fd(log->file->fd, buf, len);
        }

    ....

The func ngx_write_fd is defined in src/os/unix/ngx_files.h.

C
/*
 * we use inlined function instead of simple #define
 * because glibc 2.3 sets warn_unused_result attribute for write()
 * and in this case gcc 4.3 ignores (void) cast
 */
static ngx_inline ssize_t
ngx_write_fd(ngx_fd_t fd, void *buf, size_t n)
{
  return write(fd, buf, n);
}

This function is platform dependant, and is also defined in src/os/win32/ngx_files.c, but I'll ignore that as I'm not using a Windows system. fd stands for file descriptor.

Now I need to find where these file descriptors are being initialzed. The same files also contain the code to open files.

C
#define ngx_open_file(name, mode, create, access)                            \
    open((const char *) name, mode|create, access)

And it is referenced in ngx_cycle.c:

C
        file[i].fd = ngx_open_file(file[i].name.data,
                                   NGX_FILE_APPEND,
                                   NGX_FILE_CREATE_OR_OPEN,
                                   NGX_FILE_DEFAULT_ACCESS);

Remember the goal is to find some code that will help us write files. I have skipped over how nginx brings all of this together. I'll get to that when I get to that.
For now, I can stitch together what I've found:

  • Copy the functions into a new file.
  • Search and copy definitions of all undefined typedefs and constants.
  • Write a main function that calls the copied functions.
C
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

#ifndef ngx_inline
#define ngx_inline inline
#endif

typedef int ngx_fd_t;

#define NGX_FILE_RDWR O_RDWR
#define NGX_FILE_TRUNCATE (O_CREAT | O_TRUNC)
#define NGX_FILE_DEFAULT_ACCESS 0644

static ngx_inline ssize_t ngx_write_fd(ngx_fd_t fd, void *buf, size_t n) {
  return write(fd, buf, n);
}

#define ngx_open_file(name, mode, create, access)                              \
  open((const char *)name, mode | create, access)

int main() {
  ngx_fd_t fd = ngx_open_file("hello.txt", NGX_FILE_RDWR, NGX_FILE_TRUNCATE,
                     NGX_FILE_DEFAULT_ACCESS);


  char buf[] = "Hello, World!";
  ssize_t buf_sz = sizeof(buf) / sizeof(buf[0]);

  ngx_write_fd(fd, buf, buf_sz-1);

  return 0;
}

Run this:

bash
$ gcc main.c
$ chmod +x a.out
$ ./a.out
$ cat hello.txt

There is a lot of interesting stuff happening in the snippet above.

  • Finding the length of an array
C
  char buf[] = "Hello, World!";
  ssize_t buf_sz = sizeof(buf) / sizeof(buf[0]);
  // char arrays end with \0 which we do not want to write to file
  // which is why we're writing buf_sz-1 bytes only
  ngx_write_fd(fd, buf, buf_sz-1);

Besides writing to log files, nginx reads files as well, important configuration files. And that's my next challenge. How is the config file parsed?