Miriam Ruiz
random thoughts on technology and life

{September 17, 2007}   Storing data in $HOME directory

When packaging games, one of the operations I often have to do to the source code is modifying it so that it uses absolute file and directory names instead of relative ones. When coders develop for Windows, or when they develop their games to be able to play them from their home directory, they just would go that way and won’t care about it, but when the idea is installing the files in the system, the Filesystem Hierarchy Standard (FHS) must be respected, and thus things will need to be place in their proper directories.

For read-only files, this is not a big deal, all that has to be done is replacing the relative file name in the file opening commands with the absolute one, doing fopen(“/usr/share/games/package/dir/file”) instead of fopen(“dir/file”). I usually prefer to do it like fopen(DATADIR “/dir/file”) and set DATADIR from the Makefile, with a -DDATADIR=\”/usr/share/games/package\” switch added to the CFLAGS, and maybe also setting it inside the code if it’s not previously defined (#ifndef and so).

There are some files and directories in which doing this is not possible, because they want read-write permissions. This files include saving the configuration, hiscores, new levels created with the game editor, game saves and so. They might be moved to some directory under “/var/lib/”, and create a new group en the system so that it’s writable, and so on. I don’t like that approach a single bit. I prefer to move all tthat data to the user’s directory. This is not as straightforward as it was for read-only data, but it’s not really that difficult anyway. It would be more or less like:

#ifndef _WIN32
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <pwd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>

#ifndef _WIN32
char file[PATH_MAX];
char *home;
struct passwd *passwd;
if (!getuid() || !(home = getenv("HOME"))){
passwd = getpwuid (getuid());
if (!home){
fprintf(stderr, "$HOME is not defined.\n");
if (strlen(home) > PATH_MAX - sizeof("/.package/settings.ini")){
fprintf(stderr, "$HOME is excessively long.\n");
snprintf(file, sizeof(file), "%s/.package", home);
mkdir(file, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0;
strncat(file, "/settings.ini", sizeof(file)-1);
char file[21];
snprintf(file, sizeof(file), "./Data/settings.ini");
FILE *f = fopen(file, "w");

Sometimes you also need that the files are already created, you can check if they already exist with stat(), and then acting accordingly, either creating them from your C code, or maybe triggering a system() shell call whith whatever is needed.

struct stat stat_buf;
stat(file, &stat_buf);
if (stat(file, &stat_buf) == -1){
int settings_fd = creat(file, O_WRONLY);

NOTE: I’ve updated the code to add some suggestions. Thanks to Gonéri Le Bouder and Nico Golde.

fatal says:

There should already be a “games” group. Storing in hame directory doesn’t work when you want to store things like “hiscore”. (Needs to be a shared file)
Additionally the “$HOME does not exist, use /tmp” probably creates a temporary file vulnerability. You should mkstemp() and use that subdirectory instead of directly to /tmp.


Miry says:

I agree, and there is already a “games” group. In any case I prefer to store hiscores in the user’s directory, anyway, unless the game itself is already designed to work in a centralized way about that.

About the /tmp directory, the other approach is to just exit() with an error if $HOME is not defined. $HOME should always be defined, so the /tmp is just a workaround for that.

Steve says:

Don’t use getenv( “HOME” ).

Instead use the getpwent files to read the home directory from /etc/passwd / ldap / wherever.

A user will always have a home directory (which might even exist!), but there are many situations where $HOME might be unset/random.

Miry says:

A better fallback mechanism than /tmp might be:

struct passwd *passwd; passwd = getpwuid ( getuid()); printf(“\n The Home Directory is %s”, passwd->pw_dir);

(Thanks, Gonéri)

Steve: It might be better to use $HOME then, to let users override it, am I wrong?

Stoffe says:

It would be really nice if you would use the XDG Base Directory Specification[1] instead of yet another dotfile directly in $HOME… the specification is getting more and more common as it gets adopted by more and more applications and distros.

Anyway, thanks for all the games. :)

1. http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html

Miry says:

I’ve changed the code to add some suggestions:
* Use pwd.h as a fallback mechanism if $HOME does not exist
* Check the lenght of $HOME to prevent a buffer overflow.
* If someone does “sudo someapp” and the app reads $HOME, it will create a config file in the user directory with the root privilege.

Uwe Hermann says:

Btw, more info on the /tmp and $HOME vulnerabilities and how to fix them:


Marius Gedminas says:

Wouldn’t it be simpler to write

char *file = “./Data/settings.ini”;

instead of

char file[21];
snprintf(file, sizeof(file), “./Data/settings.ini”);


Also, although it doesn’t matter in this particular case (because you carefully check strlen(home) against PATH_MAX – length_of_your_extras), the limit argument of strncat limits the number of *source* characters copied. Passing the size of the destination buffer gives the wrong impression and could cause a bug if someone removed the explicit length check. The safe way of using strncat is strncat(dest, src, sizeof(dest) – strlen(src)), IIRC.

Miry says:

Yup, you’re right about strnlen, Marius. Anyway, this is just an example of what you might find in a game and how to fix it. In fact, it is a real example of a real game.

Jon says:

Although I can’t see /tmp hardcoded in your example, since there is some discussion about it here :) aside from the XDG spec, I think honouring $TMPDIR is a good idea.

Leave a Reply


This is a personal webpage that belongs to Miriam Ruiz.
If you want to contact her, you can do at:

May 2019
« Nov    

La Lista de Sinde