Skip to content

Conversation

@Explorer09
Copy link
Contributor

Improve the Settings_write() operation so that it can create directories just before creating and writing to the new htoprc file.

It can also work with a custom htoprc file path specified in HTOPRC environment variable.

With this improvement, htop will no longer create any config directory on reading the configuration; only on writing will the config directory be created.

Settings.c Outdated
Comment on lines 574 to 600
int ret = 0;
int savedErr = errno;

char *p = filePath + len;
for (; p > filePath; p--) {
if (*p != '/')
continue;

*p = '\0';
bool ok = !mkdir(filePath, S_IRWXU);
*p = '/';
if (ok)
break;

if (errno != ENOENT)
return -errno;

ret = -errno;
}
if (p <= filePath)
return ret;

errno = savedErr;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why a double loop here? Wouldn't it suffice to just grow from the shortest path (AKA root) towards all the sub directories? Also, with fstatat and mkdirat you even could leverage the work you need to do for splitting the path anyway …

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I have considered the possibility of walking from the top level downwards. However, it can come with a different set of problems such as: I might not have permission to list or write to an upper level directory while writing to a lower level directory is okay. (One case of such a filesystem is in Termux.) Walking from a top level could mean I have to ignore errors all the way. I just prefer a safer route.
  2. Any API that uses dirfd as arguments is out because the API for creating the final temp file, mkstemp(3), has no way to specify a dirfd in. Surely it can be worked-around by using fchdir(2), but then you need a way to change back the working directory which is another pain to handle (and probably not worth it).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. The mkdir(2) family of system calls in Unix has a problem that I would consider a design defect: There's no way to create and open the new directory (grabbing its fd) in one atomic call. Yeah, that means trying to workaround a data race would be difficult (even though possible).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed the third issue, while looking at the manpage of mkdirat(2), which returns 0 on success instead of a dirfd …

And while you may not be allowed to list parent directories, you should still be allowed to open dirfd to any parent directories on the way, otherwise you wouldn't be able to walk the path to the child directory. And that's IMHO all you need to opendirat if it exists, fstatat to verify the subdir, and mkdirat otherwise.

Or expressed differently: If there is a directory /home/mystery/invisible/foo where you can't list /home/mystery, you still need to be able to confirm /home/mystery/invisible to exist: This can be either done by trying to create it (EEXIST on mkdir) or trying to opendir it. So, you either can't open the directory (EPERM vs. ENOENT) or the directory doesn exist and you just can't open it (EPERM).

This holds for the top-most directory that you need to create: If you can access a directory for reading you can determine if a certain entry inside that directory exists, even if you can't list that directory. This even holds true if you only obtain access to any child: If you can access any child, you know all of its parents MUST exist; thus no need to create them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And while you may not be allowed to list parent directories, you should still be allowed to open dirfd to any parent directories on the way, otherwise you wouldn't be able to walk the path to the child directory. And that's IMHO all you need to opendirat if it exists, fstatat to verify the subdir, and mkdirat otherwise.

I need mkdirat and openat in one single operation, not two. If I check if directory exists using openat, and create the directory using mkdirat otherwise, there is a TOCTTOU, and I would rather give up trying to openat the thing.

Or expressed differently: If there is a directory /home/mystery/invisible/foo where you can't list /home/mystery, you still need to be able to confirm /home/mystery/invisible to exist: This can be either done by trying to create it (EEXIST on mkdir) or trying to opendir it. So, you either can't open the directory (EPERM vs. ENOENT) or the directory doesn exist and you just can't open it (EPERM).

I don't feel it right to assume mkdir("/home/mystery/invisible", ...) will always return EEXIST. Even if I know the directory will exist, the kernel might as well return EACCSS or EPERM when it doesn't have the permission to even tell whether it exists or not.

(I don't know if any kernel does this yet, but if any does, I'll praise it as having a good security feature.)

This holds for the top-most directory that you need to create: If you can access a directory for reading you can determine if a certain entry inside that directory exists, even if you can't list that directory. This even holds true if you only obtain access to any child: If you can access any child, you know all of its parents MUST exist; thus no need to create them.

When it comes to the constraint that I have to "mkdir and open in one single operation", this part of the question is moot. I would call mkdir on all parent directories no matter what. And, as I mentioned above, it's not reliable to expect mkdir to say EEXIST for any directory that does exist.

Settings.c Outdated
}

static int makeAncestorDirectories(char* filePath, size_t len) {
assert(len == strlen(filePath));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why assert on the length of the name, when you need to calculate it anyways?

Also, cf. note below, you can grow downwards from / to /home to /home/user etc. without knowing the length and have this work just the same.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've changed the algorithm to walk through the path from top down, so the question of string length is now moot.

@BenBE BenBE added the enhancement Extension or improvement to existing feature label Jan 5, 2026
@Explorer09 Explorer09 force-pushed the settings-ancestor-dir branch from ba6f54c to 38e0e08 Compare January 5, 2026 07:12
@fasterit
Copy link
Member

fasterit commented Jan 5, 2026

We deliberate did not create the dirs in the past as this is a security / DOS risk.

If htop cannot save its config, it will nicely complain:

Can not save configuration to /tmp/doesnotexist/htop.rc: No such file or directory

That should be good enough for the technically inclined that can use htop beyond showing hackers in movies.

@Explorer09
Copy link
Contributor Author

We deliberate did not create the dirs in the past as this is a security / DOS risk.

If htop cannot save its config, it will nicely complain:

Can not save configuration to /tmp/doesnotexist/htop.rc: No such file or directory

That should be good enough for the technically inclined that can use htop beyond showing hackers in movies.

I don't think it makes sense not to create the directories.

What kind of security risk can this bring? The same thing can be done with mkdir -p spamming, and users won't need htop to do what-you-called DoS here.

What I can see is exposing directories that have mode bit set wrong so that the user can spam things in them.

@Explorer09 Explorer09 force-pushed the settings-ancestor-dir branch from 38e0e08 to ae8a84d Compare January 5, 2026 19:47
... and don't create config directories when reading the htop config.

The new subroutine for creating directories can also work with custom
paths specified in HTOPRC environment variable.

Signed-off-by: Kang-Che Sung <[email protected]>
No need to temporarily duplicate the XDG_CONFIG_HOME path string.

Note that I didn't simplify or leave out the allocation of
"$XDG_CONFIG_HOME/htop" or "$HOME/.config/htop" string because I
suspect this string might be reused in the future.

Signed-off-by: Kang-Che Sung <[email protected]>
@Explorer09 Explorer09 force-pushed the settings-ancestor-dir branch from ae8a84d to 2edeff7 Compare January 6, 2026 17:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Extension or improvement to existing feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants