OpenFilesScreen.c 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. /*
  2. htop - OpenFilesScreen.c
  3. (C) 2005-2006 Hisham H. Muhammad
  4. Released under the GNU GPLv2+, see the COPYING file
  5. in the source distribution for its full text.
  6. */
  7. #include "config.h" // IWYU pragma: keep
  8. #include "OpenFilesScreen.h"
  9. #include <errno.h>
  10. #include <fcntl.h>
  11. #include <inttypes.h>
  12. #include <stdbool.h>
  13. #include <stdio.h>
  14. #include <stdlib.h>
  15. #include <unistd.h>
  16. #include <sys/types.h>
  17. #include <sys/wait.h>
  18. #include <sys/stat.h>
  19. #include "Macros.h"
  20. #include "Panel.h"
  21. #include "ProvideCurses.h"
  22. #include "Vector.h"
  23. #include "XUtils.h"
  24. // cf. getIndexForType; must be larger than the maximum value returned.
  25. #define LSOF_DATACOL_COUNT 8
  26. typedef struct OpenFiles_Data_ {
  27. char* data[LSOF_DATACOL_COUNT];
  28. } OpenFiles_Data;
  29. typedef struct OpenFiles_ProcessData_ {
  30. OpenFiles_Data data;
  31. int error;
  32. int cols[LSOF_DATACOL_COUNT];
  33. struct OpenFiles_FileData_* files;
  34. } OpenFiles_ProcessData;
  35. typedef struct OpenFiles_FileData_ {
  36. OpenFiles_Data data;
  37. struct OpenFiles_FileData_* next;
  38. } OpenFiles_FileData;
  39. static size_t getIndexForType(char type) {
  40. switch (type) {
  41. case 'f':
  42. return 0;
  43. case 'a':
  44. return 1;
  45. case 'D':
  46. return 2;
  47. case 'i':
  48. return 3;
  49. case 'n':
  50. return 4;
  51. case 's':
  52. return 5;
  53. case 't':
  54. return 6;
  55. case 'o':
  56. return 7;
  57. }
  58. /* should never reach here */
  59. abort();
  60. }
  61. static const char* getDataForType(const OpenFiles_Data* data, char type) {
  62. size_t index = getIndexForType(type);
  63. return data->data[index] ? data->data[index] : "";
  64. }
  65. OpenFilesScreen* OpenFilesScreen_new(const Process* process) {
  66. OpenFilesScreen* this = xCalloc(1, sizeof(OpenFilesScreen));
  67. Object_setClass(this, Class(OpenFilesScreen));
  68. if (Process_isThread(process)) {
  69. this->pid = Process_getThreadGroup(process);
  70. } else {
  71. this->pid = Process_getPid(process);
  72. }
  73. return (OpenFilesScreen*) InfoScreen_init(&this->super, process, NULL, LINES - 2, " FD TYPE MODE DEVICE SIZE OFFSET NODE NAME");
  74. }
  75. void OpenFilesScreen_delete(Object* this) {
  76. free(InfoScreen_done((InfoScreen*)this));
  77. }
  78. static void OpenFilesScreen_draw(InfoScreen* this) {
  79. InfoScreen_drawTitled(this, "Snapshot of files open in process %d - %s", ((OpenFilesScreen*)this)->pid, Process_getCommand(this->process));
  80. }
  81. static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) {
  82. OpenFiles_ProcessData* pdata = xCalloc(1, sizeof(OpenFiles_ProcessData));
  83. pdata->cols[getIndexForType('s')] = 8;
  84. pdata->cols[getIndexForType('o')] = 8;
  85. pdata->cols[getIndexForType('i')] = 8;
  86. int fdpair[2] = {0, 0};
  87. if (pipe(fdpair) == -1) {
  88. pdata->error = 1;
  89. return pdata;
  90. }
  91. pid_t child = fork();
  92. if (child == -1) {
  93. close(fdpair[1]);
  94. close(fdpair[0]);
  95. pdata->error = 1;
  96. return pdata;
  97. }
  98. if (child == 0) {
  99. close(fdpair[0]);
  100. dup2(fdpair[1], STDOUT_FILENO);
  101. close(fdpair[1]);
  102. int fdnull = open("/dev/null", O_WRONLY);
  103. if (fdnull < 0) {
  104. exit(1);
  105. }
  106. dup2(fdnull, STDERR_FILENO);
  107. close(fdnull);
  108. char buffer[32] = {0};
  109. xSnprintf(buffer, sizeof(buffer), "%d", pid);
  110. // Use of NULL in variadic functions must have a pointer cast.
  111. // The NULL constant is not required by standard to have a pointer type.
  112. execlp("lsof", "lsof", "-P", "-o", "-p", buffer, "-F", (char*)NULL);
  113. exit(127);
  114. }
  115. close(fdpair[1]);
  116. OpenFiles_Data* item = &(pdata->data);
  117. OpenFiles_FileData* fdata = NULL;
  118. bool lsofIncludesFileSize = false;
  119. FILE* fp = fdopen(fdpair[0], "r");
  120. if (!fp) {
  121. pdata->error = 1;
  122. return pdata;
  123. }
  124. for (;;) {
  125. char* line = String_readLine(fp);
  126. if (!line) {
  127. break;
  128. }
  129. unsigned char cmd = line[0];
  130. switch (cmd) {
  131. case 'f': /* file descriptor */
  132. {
  133. OpenFiles_FileData* nextFile = xCalloc(1, sizeof(OpenFiles_FileData));
  134. if (fdata == NULL) {
  135. pdata->files = nextFile;
  136. } else {
  137. fdata->next = nextFile;
  138. }
  139. fdata = nextFile;
  140. item = &(fdata->data);
  141. } /* FALLTHRU */
  142. case 'a': /* file access mode */
  143. case 'D': /* file's major/minor device number */
  144. case 'i': /* file's inode number */
  145. case 'n': /* file name, comment, Internet address */
  146. case 's': /* file's size */
  147. case 't': /* file's type */
  148. {
  149. size_t index = getIndexForType(cmd);
  150. free_and_xStrdup(&item->data[index], line + 1);
  151. size_t dlen = strlen(item->data[index]);
  152. if (dlen > (size_t)pdata->cols[index]) {
  153. pdata->cols[index] = (int)CLAMP(dlen, 0, INT16_MAX);
  154. }
  155. break;
  156. }
  157. case 'o': /* file's offset */
  158. {
  159. size_t index = getIndexForType(cmd);
  160. if (String_startsWith(line + 1, "0t")) {
  161. free_and_xStrdup(&item->data[index], line + 3);
  162. } else {
  163. free_and_xStrdup(&item->data[index], line + 1);
  164. }
  165. size_t dlen = strlen(item->data[index]);
  166. if (dlen > (size_t)pdata->cols[index]) {
  167. pdata->cols[index] = (int)CLAMP(dlen, 0, INT16_MAX);
  168. }
  169. break;
  170. }
  171. case 'c': /* process command name */
  172. case 'd': /* file's device character code */
  173. case 'g': /* process group ID */
  174. case 'G': /* file flags */
  175. case 'k': /* link count */
  176. case 'l': /* file's lock status */
  177. case 'L': /* process login name */
  178. case 'p': /* process ID */
  179. case 'P': /* protocol name */
  180. case 'R': /* parent process ID */
  181. case 'T': /* TCP/TPI information, identified by prefixes */
  182. case 'u': /* process user ID */
  183. /* ignore */
  184. break;
  185. }
  186. if (cmd == 's')
  187. lsofIncludesFileSize = true;
  188. free(line);
  189. }
  190. fclose(fp);
  191. int wstatus;
  192. while (waitpid(child, &wstatus, 0) == -1)
  193. if (errno != EINTR) {
  194. pdata->error = 1;
  195. return pdata;
  196. }
  197. if (!WIFEXITED(wstatus)) {
  198. pdata->error = 1;
  199. } else {
  200. pdata->error = WEXITSTATUS(wstatus);
  201. }
  202. /* We got all information we need; no post-processing needed */
  203. if (lsofIncludesFileSize)
  204. return pdata;
  205. /* On linux, `lsof -o -F` omits SIZE, so add it back. */
  206. /* On macOS, `lsof -o -F` includes SIZE, so this block isn't needed. If no open files have a filesize, this will still run, unfortunately. */
  207. size_t fileSizeIndex = getIndexForType('s');
  208. for (fdata = pdata->files; fdata != NULL; fdata = fdata->next) {
  209. item = &fdata->data;
  210. const char* filename = getDataForType(item, 'n');
  211. struct stat sb;
  212. if (stat(filename, &sb) == 0) {
  213. char fileSizeBuf[21]; /* 20 (long long) + 1 (NULL) */
  214. xSnprintf(fileSizeBuf, sizeof(fileSizeBuf), "%"PRIu64, (uint64_t)sb.st_size); /* sb.st_size is long long on macOS, long on linux */
  215. free_and_xStrdup(&item->data[fileSizeIndex], fileSizeBuf);
  216. }
  217. }
  218. return pdata;
  219. }
  220. static void OpenFiles_Data_clear(OpenFiles_Data* data) {
  221. for (size_t i = 0; i < ARRAYSIZE(data->data); i++)
  222. free(data->data[i]);
  223. }
  224. static void OpenFilesScreen_scan(InfoScreen* super) {
  225. Panel* panel = super->display;
  226. int idx = Panel_getSelectedIndex(panel);
  227. Panel_prune(panel);
  228. OpenFiles_ProcessData* pdata = OpenFilesScreen_getProcessData(((OpenFilesScreen*)super)->pid);
  229. if (pdata->error == 127) {
  230. InfoScreen_addLine(super, "Could not execute 'lsof'. Please make sure it is available in your $PATH.");
  231. } else if (pdata->error == 1) {
  232. InfoScreen_addLine(super, "Failed listing open files.");
  233. } else {
  234. char hdrbuf[128] = {0};
  235. snprintf(hdrbuf, sizeof(hdrbuf), "%5.5s %-7.7s %-4.4s %6.6s %*s %*s %*s %s",
  236. "FD", "TYPE", "MODE", "DEVICE",
  237. pdata->cols[getIndexForType('s')], "SIZE",
  238. pdata->cols[getIndexForType('o')], "OFFSET",
  239. pdata->cols[getIndexForType('i')], "NODE",
  240. "NAME"
  241. );
  242. Panel_setHeader(panel, hdrbuf);
  243. OpenFiles_FileData* fdata = pdata->files;
  244. while (fdata) {
  245. OpenFiles_Data* data = &fdata->data;
  246. char* entry = NULL;
  247. xAsprintf(&entry, "%5.5s %-7.7s %-4.4s %6.6s %*s %*s %*s %s",
  248. getDataForType(data, 'f'),
  249. getDataForType(data, 't'),
  250. getDataForType(data, 'a'),
  251. getDataForType(data, 'D'),
  252. pdata->cols[getIndexForType('s')],
  253. getDataForType(data, 's'),
  254. pdata->cols[getIndexForType('o')],
  255. getDataForType(data, 'o'),
  256. pdata->cols[getIndexForType('i')],
  257. getDataForType(data, 'i'),
  258. getDataForType(data, 'n'));
  259. InfoScreen_addLine(super, entry);
  260. free(entry);
  261. OpenFiles_Data_clear(data);
  262. OpenFiles_FileData* old = fdata;
  263. fdata = fdata->next;
  264. free(old);
  265. }
  266. OpenFiles_Data_clear(&pdata->data);
  267. }
  268. free(pdata);
  269. Vector_insertionSort(super->lines);
  270. Vector_insertionSort(panel->items);
  271. Panel_setSelected(panel, idx);
  272. }
  273. const InfoScreenClass OpenFilesScreen_class = {
  274. .super = {
  275. .extends = Class(Object),
  276. .delete = OpenFilesScreen_delete
  277. },
  278. .scan = OpenFilesScreen_scan,
  279. .draw = OpenFilesScreen_draw
  280. };