fchmod-tests, fchmodat tests, lchmod tests: Add more tests.
[gnulib.git] / lib / get_progname_of.c
blob146ceb5f2c5107eb9e785137a082d53a61d42487
1 /* Determine the program name of a given process.
2 Copyright (C) 2016-2021 Free Software Foundation, Inc.
3 Written by Bruno Haible <bruno@clisp.org>, 2019.
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>. */
18 #include <config.h>
20 /* Specification. */
21 #include "get_progname_of.h"
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
27 #if defined __linux__ || defined __ANDROID__ || (defined __FreeBSD_kernel__ && !defined __FreeBSD__) || defined __GNU__ || defined __NetBSD__ || defined __FreeBSD__ /* Linux, GNU/kFreeBSD, GNU/Hurd, NetBSD, FreeBSD */
28 # include <unistd.h>
29 # if defined __ANDROID__
30 # include <fcntl.h>
31 # endif
32 #endif
34 #if defined __minix || defined __sun /* Minix, Solaris */
35 # include <fcntl.h>
36 # include <unistd.h>
37 #endif
39 #if defined __OpenBSD__ /* OpenBSD */
40 # include <sys/sysctl.h> /* sysctl, struct kinfo_proc */
41 #endif
43 #if defined __APPLE__ && defined __MACH__ /* Mac OS X */
44 # include <libproc.h>
45 #endif
47 #if defined _AIX /* AIX */
48 # include <procinfo.h>
49 #endif
51 #if defined __hpux /* HP-UX */
52 # include <unistd.h>
53 # include <sys/param.h>
54 # include <sys/pstat.h>
55 #endif
57 #if defined __sgi /* IRIX */
58 # include <unistd.h>
59 # include <fcntl.h>
60 # include <sys/procfs.h>
61 #endif
63 #if defined __CYGWIN__ /* Cygwin */
64 # define WIN32_LEAN_AND_MEAN
65 # include <windows.h> /* needed to get 'struct external_pinfo' defined */
66 # include <sys/cygwin.h>
67 #endif
69 #if defined __BEOS__ || defined __HAIKU__ /* BeOS, Haiku */
70 # include <OS.h>
71 #endif
73 char *
74 get_progname_of (pid_t pid)
76 #if defined __linux__ || defined __ANDROID__ || (defined __FreeBSD_kernel__ && !defined __FreeBSD__) || defined __GNU__ || defined __NetBSD__ /* Linux, GNU/kFreeBSD, GNU/Hurd, NetBSD */
77 /* GNU/kFreeBSD mounts /proc as linprocfs, which looks like a Linux /proc
78 file system. */
80 /* Read the symlink /proc/<pid>/exe. */
82 char filename[6 + 10 + 4 + 1];
83 char linkbuf[1024 + 1];
84 ssize_t linklen;
86 sprintf (filename, "/proc/%u/exe", (unsigned int) pid);
87 linklen = readlink (filename, linkbuf, sizeof (linkbuf) - 1);
88 if (linklen > 0)
90 char *slash;
92 /* NUL-terminate the link. */
93 linkbuf[linklen] = '\0';
94 /* Find the portion after the last slash. */
95 slash = strrchr (linkbuf, '/');
96 return strdup (slash != NULL ? slash + 1 : linkbuf);
100 # if defined __ANDROID__
101 /* But it may fail with "Permission denied". As a fallback,
102 read the contents of /proc/<pid>/cmdline into memory. */
104 char filename[6 + 10 + 8 + 1];
105 int fd;
107 sprintf (filename, "/proc/%u/cmdline", (unsigned int) pid);
108 fd = open (filename, O_RDONLY | O_CLOEXEC);
109 if (fd >= 0)
111 char buf[4096 + 1];
112 ssize_t nread = read (fd, buf, sizeof (buf) - 1);
113 close (fd);
114 if (nread >= 0)
116 char *slash;
118 /* NUL-terminate the buffer (just in case it does not have the
119 expected format). */
120 buf[nread] = '\0';
121 /* The program name and each argument is followed by a NUL byte. */
122 /* Find the portion after the last slash. */
123 slash = strrchr (buf, '/');
124 return strdup (slash != NULL ? slash + 1 : buf);
128 # endif
130 #endif
132 #if defined __FreeBSD__ /* FreeBSD */
134 /* Read the symlink /proc/<pid>/file. */
135 char filename[6 + 10 + 5 + 1];
136 char linkbuf[1024 + 1];
137 ssize_t linklen;
139 sprintf (filename, "/proc/%u/file", (unsigned int) pid);
140 linklen = readlink (filename, linkbuf, sizeof (linkbuf) - 1);
141 if (linklen > 0)
143 char *slash;
145 /* NUL-terminate the link. */
146 linkbuf[linklen] = '\0';
147 /* Find the portion after the last slash. */
148 slash = strrchr (linkbuf, '/');
149 return strdup (slash != NULL ? slash + 1 : linkbuf);
152 #endif
154 #if defined __minix /* Minix */
156 /* Read the contents of /proc/<pid>/psinfo into memory. */
157 char filename[6 + 10 + 7 + 1];
158 int fd;
160 sprintf (filename, "/proc/%u/psinfo", (unsigned int) pid);
161 fd = open (filename, O_RDONLY | O_CLOEXEC);
162 if (fd >= 0)
164 char buf[4096 + 1];
165 ssize_t nread = read (fd, buf, sizeof (buf) - 1);
166 close (fd);
167 if (nread >= 0)
169 char *p;
170 int count;
172 /* NUL-terminate the buffer. */
173 buf[nread] = '\0';
175 /* Search for the 4th space-separated field. */
176 p = strchr (buf, ' ');
177 for (count = 1; p != NULL && count < 3; count++)
178 p = strchr (p + 1, ' ');
179 if (p != NULL)
181 char *start = p + 1;
182 char *end = strchr (p + 1, ' ');
183 if (end != NULL)
185 *end = '\0';
186 return strdup (start);
192 #endif
194 #if defined __sun /* Solaris */
196 /* Read the symlink /proc/<pid>/path/a.out.
197 When it succeeds, it doesn't truncate. */
199 char filename[6 + 10 + 11 + 1];
200 char linkbuf[1024 + 1];
201 ssize_t linklen;
203 sprintf (filename, "/proc/%u/path/a.out", (unsigned int) pid);
204 linklen = readlink (filename, linkbuf, sizeof (linkbuf) - 1);
205 if (linklen > 0)
207 char *slash;
209 /* NUL-terminate the link. */
210 linkbuf[linklen] = '\0';
211 /* Find the portion after the last slash. */
212 slash = strrchr (linkbuf, '/');
213 return strdup (slash != NULL ? slash + 1 : linkbuf);
217 /* But it may fail with "Permission denied". As a fallback,
218 read the contents of /proc/<pid>/psinfo into memory.
219 Alternatively, we could read the contents of /proc/<pid>/status into
220 memory. But it contains a lot of information that we don't need. */
222 char filename[6 + 10 + 7 + 1];
223 int fd;
225 sprintf (filename, "/proc/%u/psinfo", (unsigned int) pid);
226 fd = open (filename, O_RDONLY | O_CLOEXEC);
227 if (fd >= 0)
229 /* The contents is a 'struct psinfo'. But since 'struct psinfo'
230 has a different size in a 32-bit and a 64-bit environment, we
231 avoid it. Nevertheless, the size of this contents depends on
232 whether the process that reads it is 32-bit or 64-bit! */
233 #if defined __LP64__
234 # define PSINFO_SIZE 416
235 # define PSINFO_FNAME_OFFSET 136
236 #else
237 # define PSINFO_SIZE 336
238 # define PSINFO_FNAME_OFFSET 88
239 #endif
240 char buf[PSINFO_SIZE];
241 ssize_t nread = read (fd, buf, sizeof (buf));
242 close (fd);
243 if (nread >= PSINFO_FNAME_OFFSET + 16)
245 /* Make sure it's NUL-terminated. */
246 buf[PSINFO_FNAME_OFFSET + 16] = '\0';
247 return strdup (&buf[PSINFO_FNAME_OFFSET]);
252 #endif
254 #if defined __OpenBSD__ /* OpenBSD */
256 /* Documentation: https://man.openbsd.org/sysctl.2 */
257 int info_path[] =
258 { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid, sizeof (struct kinfo_proc), 1 };
259 struct kinfo_proc info;
260 size_t len;
262 len = sizeof (info);
263 if (sysctl (info_path, 6, &info, &len, NULL, 0) >= 0 && len == sizeof (info))
264 return strdup (info.p_comm);
266 #endif
268 #if defined __APPLE__ && defined __MACH__ /* Mac OS X */
270 # if defined PROC_PIDT_SHORTBSDINFO
271 struct proc_bsdshortinfo info;
273 if (proc_pidinfo (pid, PROC_PIDT_SHORTBSDINFO, 0, &info, sizeof (info))
274 == sizeof (info))
275 return strdup (info.pbsi_comm);
276 # else
277 /* Note: The second part of 'struct proc_bsdinfo' differs in size between
278 32-bit and 64-bit environments, and the kernel of Mac OS X 10.5 knows
279 only about the 32-bit 'struct proc_bsdinfo'. Fortunately all the info
280 we need is in the first part, which is the same in 32-bit and 64-bit. */
281 struct proc_bsdinfo info;
283 if (proc_pidinfo (pid, PROC_PIDTBSDINFO, 0, &info, 128) == 128)
284 return strdup (info.pbi_comm);
285 # endif
287 #endif
289 #if defined _AIX /* AIX */
291 /* Reference: https://www.ibm.com/support/knowledgecenter/en/ssw_aix_61/com.ibm.aix.basetrf1/getprocs.htm
293 struct procentry64 procs;
294 if (getprocs64 (&procs, sizeof procs, NULL, 0, &pid, 1) > 0)
295 return strdup (procs.pi_comm);
297 #endif
299 #if defined __hpux /* HP-UX */
301 char *p;
302 struct pst_status status;
303 if (pstat_getproc (&status, sizeof status, 0, pid) > 0)
305 char *ucomm = status.pst_ucomm;
306 char *cmd = status.pst_cmd;
307 if (strlen (ucomm) < PST_UCOMMLEN - 1)
308 p = ucomm;
309 else
311 /* ucomm is truncated to length PST_UCOMMLEN - 1.
312 Look at cmd instead. */
313 char *space = strchr (cmd, ' ');
314 if (space != NULL)
315 *space = '\0';
316 p = strrchr (cmd, '/');
317 if (p != NULL)
318 p++;
319 else
320 p = cmd;
321 if (strlen (p) > PST_UCOMMLEN - 1
322 && memcmp (p, ucomm, PST_UCOMMLEN - 1) == 0)
323 /* p is less truncated than ucomm. */
325 else
326 p = ucomm;
328 p = strdup (p);
330 else
332 # if !defined __LP64__
333 /* Support for 32-bit programs running in 64-bit HP-UX.
334 The documented way to do this is to use the same source code
335 as above, but in a compilation unit where '#define _PSTAT64 1'
336 is in effect. I prefer a single compilation unit; the struct
337 size and the offsets are not going to change. */
338 char status64[1216];
339 if (__pstat_getproc64 (status64, sizeof status64, 0, pid) > 0)
341 char *ucomm = status64 + 288;
342 char *cmd = status64 + 168;
343 if (strlen (ucomm) < PST_UCOMMLEN - 1)
344 p = ucomm;
345 else
347 /* ucomm is truncated to length PST_UCOMMLEN - 1.
348 Look at cmd instead. */
349 char *space = strchr (cmd, ' ');
350 if (space != NULL)
351 *space = '\0';
352 p = strrchr (cmd, '/');
353 if (p != NULL)
354 p++;
355 else
356 p = cmd;
357 if (strlen (p) > PST_UCOMMLEN - 1
358 && memcmp (p, ucomm, PST_UCOMMLEN - 1) == 0)
359 /* p is less truncated than ucomm. */
361 else
362 p = ucomm;
364 p = strdup (p);
366 else
367 # endif
368 p = NULL;
370 if (p != NULL)
371 return strdup (p);
373 #endif
375 #if defined __sgi /* IRIX */
377 char filename[12 + 10 + 1];
378 int fd;
380 sprintf (filename, "/proc/pinfo/%u", pid);
381 fd = open (filename, O_RDONLY | O_CLOEXEC);
382 if (0 <= fd)
384 prpsinfo_t buf;
385 int ioctl_ok = 0 <= ioctl (fd, PIOCPSINFO, &buf);
386 close (fd);
387 if (ioctl_ok)
389 char *name = buf.pr_fname;
390 size_t namesize = sizeof buf.pr_fname;
391 /* It may not be NUL-terminated. */
392 char *namenul = memchr (name, '\0', namesize);
393 size_t namelen = namenul ? namenul - name : namesize;
394 char *namecopy = malloc (namelen + 1);
395 if (namecopy)
397 namecopy[namelen] = '\0';
398 return memcpy (namecopy, name, namelen);
403 #endif
405 #if defined __CYGWIN__ /* Cygwin */
407 struct external_pinfo *info =
408 (struct external_pinfo *) cygwin_internal (CW_GETPINFO, pid);
409 if (info != NULL)
411 const char *name = info->progname;
412 size_t namesize = sizeof (info->progname);
413 /* It may not be NUL-terminated. */
414 const char *namenul = memchr (name, '\0', namesize);
415 size_t namelen = namenul ? namenul - name : namesize;
417 /* Find the portion after the last backslash.
418 Cygwin does not have memrchr(). */
420 const char *backslash = memchr (name, '\\', namelen);
421 if (backslash != NULL)
423 const char *name_end = name + namelen;
424 for (;;)
426 const char *next_backslash =
427 memchr (backslash + 1, '\\', name_end - (backslash + 1));
428 if (next_backslash == NULL)
429 break;
430 backslash = next_backslash;
432 name = backslash + 1;
433 namelen = name_end - name;
438 char *namecopy = malloc (namelen + 1);
439 if (namecopy)
441 namecopy[namelen] = '\0';
442 return memcpy (namecopy, name, namelen);
447 #endif
449 #if defined __BEOS__ || defined __HAIKU__ /* BeOS, Haiku */
451 team_info info;
452 if (_get_team_info (pid, &info, sizeof (info)) == B_OK)
454 const char *name = info.args;
455 size_t namesize = sizeof (info.args);
456 /* It may not be NUL-terminated. */
457 const char *namenul = memchr (name, '\0', namesize);
458 size_t namelen = namenul ? namenul - name : namesize;
460 /* Take the portion up to the first space. */
462 const char *space = memchr (name, ' ', namelen);
463 if (space != NULL)
464 namelen = space - name;
467 /* Find the portion after the last slash. */
469 const char *slash = memchr (name, '/', namelen);
470 if (slash != NULL)
472 const char *name_end = name + namelen;
473 for (;;)
475 const char *next_slash =
476 memchr (slash + 1, '/', name_end - (slash + 1));
477 if (next_slash == NULL)
478 break;
479 slash = next_slash;
481 name = slash + 1;
482 namelen = name_end - name;
487 char *namecopy = malloc (namelen + 1);
488 if (namecopy)
490 namecopy[namelen] = '\0';
491 return memcpy (namecopy, name, namelen);
496 #endif
498 return NULL;
501 #ifdef TEST
503 #include <stdlib.h>
504 #include <unistd.h>
506 /* Usage: ./a.out
507 or: ./a.out PID
510 main (int argc, char *argv[])
512 char *arg = argv[1];
513 pid_t pid = (arg != NULL ? atoi (arg) : getpid ());
514 char *progname = get_progname_of (pid);
515 printf ("PID=%lu COMMAND=%s\n",
516 (unsigned long) pid, progname != NULL ? progname : "(null)");
517 free (progname);
518 return 0;
522 * Local Variables:
523 * compile-command: "gcc -ggdb -DTEST -Wall -I.. get_progname_of.c"
524 * End:
527 #endif