Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ linux_platform_headers = \
generic/hostname.h \
generic/uname.h \
linux/CGroupUtils.h \
linux/DockerMgr.h \
linux/Compat.h \
linux/GPU.h \
linux/HugePageMeter.h \
Expand Down Expand Up @@ -218,6 +219,7 @@ linux_platform_sources = \
generic/hostname.c \
generic/uname.c \
linux/CGroupUtils.c \
linux/DockerMgr.c \
linux/Compat.c \
linux/GPU.c \
linux/HugePageMeter.c \
Expand Down
2 changes: 1 addition & 1 deletion linux/CGroupUtils.c
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,7 @@ char* CGroup_filterContainer(const char* cgroup) {
}

if (!s.pos) {
return xStrdup("/");
return xStrdup("");
}

s.buf = xCalloc(s.pos + 1, sizeof(char));
Expand Down
108 changes: 108 additions & 0 deletions linux/DockerMgr.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
htop - DockerMgr.c
Retrieves running Docker container names via the Docker API over the unix socket.
Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/

#include "config.h" // IWYU pragma: keep

#include "linux/DockerMgr.h"

#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

#include "Hashtable.h"
#include "XUtils.h"

#define DOCKER_SOCKET "/var/run/docker.sock"
#define RESPONSE_SIZE 262144
#define MAX_CONTAINER_NAME_LEN 25

/* ponytail: simplest possible cache; refresh by dropping cache on long IDs is out of scope */
static Hashtable* nameCache = NULL;

static unsigned int hashStr(const char* s) {
unsigned int h = 5381;
while (*s)
h = (h * 33) ^ (unsigned char)*s++;
return h;
}
Comment on lines +29 to +34

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

sed -n '24,110p' linux/DockerMgr.c
rg -n 'typedef.*ht_key_t|Hashtable_new|Hashtable_put|Hashtable_get|Hashtable_create' Hashtable.* linux/DockerMgr.c

Repository: htop-dev/htop

Length of output: 3258


🏁 Script executed:

printf '%s\n' '--- Hashtable key and declarations ---'; cat -n Hashtable.h | sed -n '1,45p'; printf '%s\n' '--- Hashtable insertion and lookup ---'; cat -n Hashtable.c | sed -n '105,135p;205,250p;275,315p'; printf '%s\n' '--- Docker ID sources and cgroup parsing ---'; rg -n -i 'docker|container.*id|cgroup|containerId|container_id' linux | head -100

Repository: htop-dev/htop

Length of output: 13313


🏁 Script executed:

printf '%s\n' '--- Hashtable internals ---'; cat -n Hashtable.c | sed -n '30,105p;130,165p'; printf '%s\n' '--- Docker cgroup ID extraction ---'; cat -n linux/CGroupUtils.c | sed -n '205,280p'; printf '%s\n' '--- Cache caller ---'; cat -n linux/LinuxProcessTable.c | sed -n '1003,1018p'

Repository: htop-dev/htop

Length of output: 7254


🏁 Script executed:

printf '%s\n' '--- Hashtable insertion replacement ---'; cat -n Hashtable.c | sed -n '152,205p'; printf '%s\n' '--- Docker-specific filter branch ---'; cat -n linux/CGroupUtils.c | sed -n '25,75p;280,360p'

Repository: htop-dev/htop

Length of output: 6938


Use a collision-safe cache key for Docker IDs.

hashStr reduces each 12-character Docker ID to a 32-bit ht_key_t. Distinct IDs can therefore produce the same key. Hashtable_get compares only that key, so the second ID can receive the first ID's cached name.

Use a representation that preserves all 12 hexadecimal characters. Parsing into an integer requires widening ht_key_t beyond its current 32-bit type. Otherwise, retain the full ID and handle hash collisions before returning or replacing entries.

The owner-semantics warning does not apply: nameCache already uses Hashtable_new(16, true), and replacement values are freed under that setting.


static Hashtable* getCache(void) {
if (!nameCache)
nameCache = Hashtable_new(16, true);
return nameCache;
}

static char* queryName(const char* id) {
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0)
return NULL;

struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", DOCKER_SOCKET);

if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
close(fd);
return NULL;
}

char req[256];
int reqLen = snprintf(req, sizeof(req),
"GET /containers/%s/json HTTP/1.1\r\n"
"Host: localhost\r\n"
"Accept: application/json\r\n"
"Connection: close\r\n\r\n", id);
if (reqLen <= 0 || reqLen >= (int)sizeof(req) || write(fd, req, (size_t)reqLen) != reqLen) {
close(fd);
return NULL;
}

char buf[RESPONSE_SIZE];
size_t used = 0;
ssize_t rd;
while (used < sizeof(buf) - 1 && (rd = read(fd, buf + used, sizeof(buf) - 1 - used)) > 0)
used += (size_t)rd;
close(fd);

if (used == 0)
return NULL;
buf[used] = '\0';

char* key = strstr(buf, "\"Name\":\"");
if (!key)
return NULL;
char* val = key + strlen("\"Name\":\"");
if (*val == '/')
val++;
char* end = strchr(val, '"');
if (!end)
return NULL;
return xStrndup(val, (size_t)(end - val));
}

Comment on lines +42 to +90

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

sed -n '1,125p' linux/DockerMgr.c
printf '\n-- socket timeout/nonblocking patterns --\n'
rg -n --glob '*.[ch]' 'SO_RCVTIMEO|SO_SNDTIMEO|poll\(|select\(|fcntl\(.*O_NONBLOCK|connect\(' linux | head -120

Repository: htop-dev/htop

Length of output: 3102


Bound the complete Docker socket exchange

queryName performs synchronous connect, write, and read calls during process-table refresh. A stalled Docker socket can delay the refresh indefinitely. Bound the complete exchange: use nonblocking connect with a finite poll deadline, and apply the same deadline to request writes and response reads. SO_RCVTIMEO and SO_SNDTIMEO alone do not bound connect.

The stack-buffer and negative-cache suggestions are separate concerns and are omitted from this correction.

char* DockerMgr_getContainerName(const char* id) {
Hashtable* cache = getCache();
ht_key_t key = hashStr(id);

char* cached = Hashtable_get(cache, key);
if (cached)
return xStrdup(cached);

char* name = queryName(id);
if (name) {
/* Limit the container name shown in the CONTAINER column to 25 characters */
char* shortName = xStrndup(name, MAX_CONTAINER_NAME_LEN);
free(name);
Hashtable_put(cache, key, shortName);
return xStrdup(shortName);
}
return NULL;
}
12 changes: 12 additions & 0 deletions linux/DockerMgr.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#ifndef HEADER_DockerMgr
#define HEADER_DockerMgr
/*
htop - DockerMgr.h
Retrieves running Docker container names via the Docker API over the unix socket.
Released under the GNU GPLv2+, see the COPYING file
in the source distribution for its full text.
*/

char* DockerMgr_getContainerName(const char* id);

#endif /* HEADER_DockerMgr */
10 changes: 10 additions & 0 deletions linux/LinuxProcessTable.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ in the source distribution for its full text.
#include "Table.h"
#include "UsersTable.h"
#include "linux/CGroupUtils.h"
#include "linux/DockerMgr.h"
#include "linux/Compat.h"
#include "linux/GPU.h"
#include "linux/LinuxMachine.h"
Expand Down Expand Up @@ -1005,6 +1006,15 @@ static void LinuxProcessTable_readCGroupFile(LinuxProcess* process, openat_arg_t

char* container_short = CGroup_filterContainer(process->cgroup);
if (container_short) {
/* If this is a Docker container, try to resolve the real name via the Docker API */
static const char* dockerPrefix = "!docker:";
if (String_startsWith(container_short, dockerPrefix)) {
char* resolved = DockerMgr_getContainerName(container_short + strlen(dockerPrefix));
if (resolved) {
free(container_short);
container_short = resolved;
}
}
Row_updateFieldWidth(CONTAINER, strlen(container_short));
free_and_xStrdup(&process->container_short, container_short);
free(container_short);
Expand Down