diff --git a/Action.c b/Action.c index 5b68811e..aa9614a5 100644 --- a/Action.c +++ b/Action.c @@ -34,6 +34,10 @@ in the source distribution for its full text. #include "Vector.h" #include "XUtils.h" +#ifdef HTOP_LINUX +#include "linux/ProcessLocksScreen.h" +#endif + Object* Action_pickFromVector(State* st, Panel* list, int x, bool followProcess) { Panel* panel = st->panel; @@ -371,6 +375,19 @@ static Htop_Reaction actionLsof(State* st) { 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) { Process* p = (Process*) Panel_getSelected(st->panel); 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 = " i: ", .info = "set IO priority" }, { .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 = " w: ", .info = "wrap process command in multiple lines" }, { .key = " F2 C S: ", .info = "setup" }, @@ -620,6 +640,9 @@ void Action_setBindings(Htop_Action* keys) { keys['S'] = actionSetup; keys['C'] = actionSetup; keys[KEY_F(2)] = actionSetup; +#ifdef HTOP_LINUX + keys['x'] = actionShowLocks; +#endif keys['l'] = actionLsof; keys['s'] = actionStrace; keys[' '] = actionTag; diff --git a/Makefile.am b/Makefile.am index a6629f6d..c9193a86 100644 --- a/Makefile.am +++ b/Makefile.am @@ -129,6 +129,7 @@ linux_platform_headers = \ linux/LinuxProcessList.h \ linux/Platform.h \ linux/PressureStallMeter.h \ + linux/ProcessLocksScreen.h \ linux/SELinuxMeter.h \ linux/ZramMeter.h \ linux/ZramStats.h \ @@ -145,6 +146,7 @@ myhtopplatsources = \ linux/LinuxProcessList.c \ linux/Platform.c \ linux/PressureStallMeter.c \ + linux/ProcessLocksScreen.c \ linux/SELinuxMeter.c \ linux/ZramMeter.c \ zfs/ZfsArcMeter.c \ diff --git a/configure.ac b/configure.ac index b70a509f..95a8f909 100644 --- a/configure.ac +++ b/configure.ac @@ -88,7 +88,7 @@ AC_TYPE_UID_T # ---------------------------------------------------------------------- AC_FUNC_CLOSEDIR_VOID AC_FUNC_STAT -AC_CHECK_FUNCS([fstatat memmove strncasecmp strstr strdup]) +AC_CHECK_FUNCS([fstatat memmove readlinkat strdup strncasecmp strstr]) save_cflags="${CFLAGS}" CFLAGS="${CFLAGS} -std=c99" diff --git a/htop.1.in b/htop.1.in index 3e95c201..cf337b09 100644 --- a/htop.1.in +++ b/htop.1.in @@ -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 onto multiple lines as needed. .TP +.B x +Display the active file locks of the selected process in a separate screen. +.TP .B F1, h, ? Go to the help screen .TP diff --git a/linux/ProcessLocksScreen.c b/linux/ProcessLocksScreen.c new file mode 100644 index 00000000..bdbf22ff --- /dev/null +++ b/linux/ProcessLocksScreen.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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, "", + data->data[3] ? data->data[3] : "" + ); + } 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] : "" + ); + } + + 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 +}; diff --git a/linux/ProcessLocksScreen.h b/linux/ProcessLocksScreen.h new file mode 100644 index 00000000..ec940fba --- /dev/null +++ b/linux/ProcessLocksScreen.h @@ -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 + + +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