Implement screen for active file locks

This commit is contained in:
Fynn Wulf 2020-10-09 21:02:27 +02:00 committed by Benny Baumann
parent 0806a7958b
commit 84dad4c38e
6 changed files with 313 additions and 1 deletions

View File

@ -34,6 +34,10 @@ in the source distribution for its full text.
#include "Vector.h" #include "Vector.h"
#include "XUtils.h" #include "XUtils.h"
#ifdef HTOP_LINUX
#include "linux/ProcessLocksScreen.h"
#endif
Object* Action_pickFromVector(State* st, Panel* list, int x, bool followProcess) { Object* Action_pickFromVector(State* st, Panel* list, int x, bool followProcess) {
Panel* panel = st->panel; Panel* panel = st->panel;
@ -371,6 +375,19 @@ static Htop_Reaction actionLsof(State* st) {
return HTOP_REFRESH | HTOP_REDRAW_BAR; return HTOP_REFRESH | HTOP_REDRAW_BAR;
} }
#ifdef HTOP_LINUX
static Htop_Reaction actionShowLocks(State* st) {
Process* p = (Process*) Panel_getSelected(st->panel);
if (!p) return HTOP_OK;
ProcessLocksScreen* pls = ProcessLocksScreen_new(p);
InfoScreen_run((InfoScreen*)pls);
ProcessLocksScreen_delete((Object*)pls);
clear();
CRT_enableDelay();
return HTOP_REFRESH | HTOP_REDRAW_BAR;
}
#endif
static Htop_Reaction actionStrace(State* st) { static Htop_Reaction actionStrace(State* st) {
Process* p = (Process*) Panel_getSelected(st->panel); Process* p = (Process*) Panel_getSelected(st->panel);
if (!p) return HTOP_OK; if (!p) return HTOP_OK;
@ -435,6 +452,9 @@ static const struct { const char* key; const char* info; } helpRight[] = {
{ .key = " e: ", .info = "show process environment" }, { .key = " e: ", .info = "show process environment" },
{ .key = " i: ", .info = "set IO priority" }, { .key = " i: ", .info = "set IO priority" },
{ .key = " l: ", .info = "list open files with lsof" }, { .key = " l: ", .info = "list open files with lsof" },
#ifdef HTOP_LINUX
{ .key = " x: ", .info = "list file locks of process" },
#endif
{ .key = " s: ", .info = "trace syscalls with strace" }, { .key = " s: ", .info = "trace syscalls with strace" },
{ .key = " w: ", .info = "wrap process command in multiple lines" }, { .key = " w: ", .info = "wrap process command in multiple lines" },
{ .key = " F2 C S: ", .info = "setup" }, { .key = " F2 C S: ", .info = "setup" },
@ -620,6 +640,9 @@ void Action_setBindings(Htop_Action* keys) {
keys['S'] = actionSetup; keys['S'] = actionSetup;
keys['C'] = actionSetup; keys['C'] = actionSetup;
keys[KEY_F(2)] = actionSetup; keys[KEY_F(2)] = actionSetup;
#ifdef HTOP_LINUX
keys['x'] = actionShowLocks;
#endif
keys['l'] = actionLsof; keys['l'] = actionLsof;
keys['s'] = actionStrace; keys['s'] = actionStrace;
keys[' '] = actionTag; keys[' '] = actionTag;

View File

@ -129,6 +129,7 @@ linux_platform_headers = \
linux/LinuxProcessList.h \ linux/LinuxProcessList.h \
linux/Platform.h \ linux/Platform.h \
linux/PressureStallMeter.h \ linux/PressureStallMeter.h \
linux/ProcessLocksScreen.h \
linux/SELinuxMeter.h \ linux/SELinuxMeter.h \
linux/ZramMeter.h \ linux/ZramMeter.h \
linux/ZramStats.h \ linux/ZramStats.h \
@ -145,6 +146,7 @@ myhtopplatsources = \
linux/LinuxProcessList.c \ linux/LinuxProcessList.c \
linux/Platform.c \ linux/Platform.c \
linux/PressureStallMeter.c \ linux/PressureStallMeter.c \
linux/ProcessLocksScreen.c \
linux/SELinuxMeter.c \ linux/SELinuxMeter.c \
linux/ZramMeter.c \ linux/ZramMeter.c \
zfs/ZfsArcMeter.c \ zfs/ZfsArcMeter.c \

View File

@ -88,7 +88,7 @@ AC_TYPE_UID_T
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------
AC_FUNC_CLOSEDIR_VOID AC_FUNC_CLOSEDIR_VOID
AC_FUNC_STAT AC_FUNC_STAT
AC_CHECK_FUNCS([fstatat memmove strncasecmp strstr strdup]) AC_CHECK_FUNCS([fstatat memmove readlinkat strdup strncasecmp strstr])
save_cflags="${CFLAGS}" save_cflags="${CFLAGS}"
CFLAGS="${CFLAGS} -std=c99" CFLAGS="${CFLAGS} -std=c99"

View File

@ -123,6 +123,9 @@ will display the list of file descriptors opened by the process.
Display the command line of the selected process in a separate screen, wrapped Display the command line of the selected process in a separate screen, wrapped
onto multiple lines as needed. onto multiple lines as needed.
.TP .TP
.B x
Display the active file locks of the selected process in a separate screen.
.TP
.B F1, h, ? .B F1, h, ?
Go to the help screen Go to the help screen
.TP .TP

258
linux/ProcessLocksScreen.c Normal file
View File

@ -0,0 +1,258 @@
/*
htop - ProcessLocksScreen.c
(C) 2020 htop dev team
Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
#include "config.h" // IWYU pragma: keep
#include "ProcessLocksScreen.h"
#include <dirent.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "CRT.h"
#include "FunctionBar.h"
#include "IncSet.h"
#include "ProcessList.h"
#include "XUtils.h"
typedef struct FileLocks_Data_ {
char* data[4];
int id;
unsigned int dev[2];
uint64_t inode;
uint64_t start;
uint64_t end;
} FileLocks_Data;
typedef struct FileLocks_ProcessData_ {
bool error;
struct FileLocks_LockData_* locks;
} FileLocks_ProcessData;
typedef struct FileLocks_LockData_ {
FileLocks_Data data;
struct FileLocks_LockData_* next;
} FileLocks_LockData;
ProcessLocksScreen* ProcessLocksScreen_new(const Process* process) {
ProcessLocksScreen* this = xMalloc(sizeof(ProcessLocksScreen));
Object_setClass(this, Class(ProcessLocksScreen));
if (Process_isThread(process))
this->pid = process->tgid;
else
this->pid = process->pid;
return (ProcessLocksScreen*) InfoScreen_init(&this->super, process, NULL, LINES-3, " ID TYPE EXCLUSION READ/WRITE DEVICE:INODE START END FILENAME");
}
void ProcessLocksScreen_delete(Object* this) {
free(InfoScreen_done((InfoScreen*)this));
}
static void ProcessLocksScreen_draw(InfoScreen* this) {
InfoScreen_drawTitled(this, "Snapshot of file locks of process %d - %s", ((ProcessLocksScreen*)this)->pid, this->process->comm);
}
/*
* Return the absolute path of a file given its pid&inode number
*
* Based on implementation of lslocks from util-linux:
* https://sources.debian.org/src/util-linux/2.36-3/misc-utils/lslocks.c/#L162
*/
static char *ProcessLocksScreen_getInodeFilename(pid_t pid, ino_t inode) {
struct stat sb;
struct dirent *de;
DIR *dirp;
size_t len;
int fd;
char path[PATH_MAX];
char sym[PATH_MAX];
char* ret = NULL;
memset(path, 0, sizeof(path));
memset(sym, 0, sizeof(sym));
xSnprintf(path, sizeof(path), "%s/%d/fd/", PROCDIR, pid);
if (strlen(path) >= (sizeof(path) - 2))
return NULL;
if (!(dirp = opendir(path)))
return NULL;
if ((fd = dirfd(dirp)) < 0 )
goto out;
while ((de = readdir(dirp))) {
if (String_eq(de->d_name, ".") || String_eq(de->d_name, ".."))
continue;
/* care only for numerical descriptors */
if (!strtoull(de->d_name, (char **) NULL, 10))
continue;
#if !defined(HAVE_FSTATAT) || !defined(HAVE_READLINKAT)
char filepath[PATH_MAX + 1];
xSnprintf(filepath, sizeof(filepath), "%s/%s", path, de->d_name);
#endif
#ifdef HAVE_FSTATAT
if (!fstatat(fd, de->d_name, &sb, 0) && inode != sb.st_ino)
continue;
#else
if (!stat(filepath, &sb)) && inode != sb.st_ino)
continue;
#endif
#ifdef HAVE_READLINKAT
if ((len = readlinkat(fd, de->d_name, sym, sizeof(sym) - 1)) < 1)
goto out;
#else
if ((len = readlink(filepath, sym, sizeof(sym) - 1)) < 1)
goto out;
#endif
sym[len] = '\0';
ret = xStrdup(sym);
break;
}
out:
closedir(dirp);
return ret;
}
static FileLocks_ProcessData* ProcessLocksScreen_getProcessData(pid_t pid) {
FileLocks_ProcessData* pdata = xCalloc(1, sizeof(FileLocks_ProcessData));
FILE* f = fopen(PROCDIR "/locks", "r");
if (!f) {
pdata->error = true;
return pdata;
}
char buffer[1024];
FileLocks_LockData** data_ref = &pdata->locks;
while(fgets(buffer, sizeof(buffer), f)) {
if (!strchr(buffer, '\n'))
continue;
int lock_id;
char lock_type[16];
char lock_excl[16];
char lock_rw[16];
pid_t lock_pid;
unsigned int lock_dev[2];
uint64_t lock_inode;
char lock_start[25];
char lock_end[25];
if (10 != sscanf(buffer, "%d: %15s %15s %15s %d %x:%x:%"PRIu64" %24s %24s",
&lock_id, lock_type, lock_excl, lock_rw, &lock_pid,
&lock_dev[0], &lock_dev[1], &lock_inode,
lock_start, lock_end))
continue;
if (pid != lock_pid)
continue;
FileLocks_LockData* ldata = xCalloc(1, sizeof(FileLocks_LockData));
FileLocks_Data* data = &ldata->data;
data->id = lock_id;
data->data[0] = xStrdup(lock_type);
data->data[1] = xStrdup(lock_excl);
data->data[2] = xStrdup(lock_rw);
data->data[3] = ProcessLocksScreen_getInodeFilename(lock_pid, lock_inode);
data->dev[0] = lock_dev[0];
data->dev[1] = lock_dev[1];
data->inode = lock_inode;
data->start = strtoull(lock_start, NULL, 10);
if (!String_eq(lock_end, "EOF")) {
data->end = strtoull(lock_end, NULL, 10);
} else {
data->end = ULLONG_MAX;
}
*data_ref = ldata;
data_ref = &ldata->next;
}
fclose(f);
return pdata;
}
static inline void FileLocks_Data_clear(FileLocks_Data* data) {
for (size_t i = 0; i < ARRAYSIZE(data->data); i++)
free(data->data[i]);
}
static void ProcessLocksScreen_scan(InfoScreen* this) {
Panel* panel = this->display;
int idx = Panel_getSelectedIndex(panel);
Panel_prune(panel);
FileLocks_ProcessData* pdata = ProcessLocksScreen_getProcessData(((ProcessLocksScreen*)this)->pid);
if (pdata->error) {
InfoScreen_addLine(this, "Could not read file locks.");
} else {
FileLocks_LockData* ldata = pdata->locks;
if (!ldata) {
InfoScreen_addLine(this, "No locks have been found for the selected process.");
}
while (ldata) {
FileLocks_Data* data = &ldata->data;
char entry[512];
if (ULLONG_MAX == data->end) {
xSnprintf(entry, sizeof(entry), "%10d %-10s %-10s %-10s %02x:%02x:%020"PRIu64" %20"PRIu64" %20s %s",
data->id,
data->data[0], data->data[1], data->data[2],
data->dev[0], data->dev[1], data->inode,
data->start, "<END OF FILE>",
data->data[3] ? data->data[3] : "<N/A>"
);
} else {
xSnprintf(entry, sizeof(entry), "%10d %-10s %-10s %-10s %02x:%02x:%020"PRIu64" %20"PRIu64" %20"PRIu64" %s",
data->id,
data->data[0], data->data[1], data->data[2],
data->dev[0], data->dev[1], data->inode,
data->start, data->end,
data->data[3] ? data->data[3] : "<N/A>"
);
}
InfoScreen_addLine(this, entry);
FileLocks_Data_clear(&ldata->data);
FileLocks_LockData* old = ldata;
ldata = ldata->next;
free(old);
}
}
free(pdata);
Vector_insertionSort(this->lines);
Vector_insertionSort(panel->items);
Panel_setSelected(panel, idx);
}
const InfoScreenClass ProcessLocksScreen_class = {
.super = {
.extends = Class(Object),
.delete = ProcessLocksScreen_delete
},
.scan = ProcessLocksScreen_scan,
.draw = ProcessLocksScreen_draw
};

View File

@ -0,0 +1,26 @@
#ifndef HEADER_ProcessLocksScreen
#define HEADER_ProcessLocksScreen
/*
htop - ProcessLocksScreen.h
(C) 2020 htop dev team
Released under the GNU GPLv2, see the COPYING file
in the source distribution for its full text.
*/
#include "InfoScreen.h"
#include <stdlib.h>
typedef struct ProcessLocksScreen_ {
InfoScreen super;
pid_t pid;
} ProcessLocksScreen;
extern const InfoScreenClass ProcessLocksScreen_class;
ProcessLocksScreen* ProcessLocksScreen_new(const Process* process);
void ProcessLocksScreen_delete(Object* this);
#endif