exp2l: Work around a NetBSD 10.0/i386 bug.
[gnulib.git] / lib / get_progname_of.c
blob560587c41a9338ee2f10acf09d162f4c57a2986c
1 /* Determine the program name of a given process.
2 Copyright (C) 2016-2024 Free Software Foundation, Inc.
3 Written by Bruno Haible <bruno@clisp.org>, 2019.
5 This file is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as
7 published by the Free Software Foundation, either version 3 of the
8 License, or (at your option) any later version.
10 This file 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 Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser 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 /* Get MAC_OS_X_VERSION_MIN_REQUIRED, MAC_OS_X_VERSION_MAX_ALLOWED.
45 The version at runtime satisfies
46 MAC_OS_X_VERSION_MIN_REQUIRED <= version <= MAC_OS_X_VERSION_MAX_ALLOWED. */
47 # include <AvailabilityMacros.h>
48 # if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050
49 # include <libproc.h>
50 # if MAC_OS_X_VERSION_MIN_REQUIRED < 1050
51 /* Mac OS X versions < 10.5 don't have this function. Therefore declare it as
52 weak, in order to avoid a runtime error when the binaries are run on these
53 older versions. */
54 extern int proc_pidinfo (int, int, uint64_t, void *, int) WEAK_IMPORT_ATTRIBUTE;
55 # endif
56 # endif
57 #endif
59 #if defined _AIX /* AIX */
60 # include <procinfo.h>
61 #endif
63 #if defined __hpux /* HP-UX */
64 # include <unistd.h>
65 # include <sys/param.h>
66 # include <sys/pstat.h>
67 #endif
69 #if defined __sgi /* IRIX */
70 # include <unistd.h>
71 # include <fcntl.h>
72 # include <sys/procfs.h>
73 #endif
75 #if defined __CYGWIN__ /* Cygwin */
76 # define WIN32_LEAN_AND_MEAN
77 # include <windows.h> /* needed to get 'struct external_pinfo' defined */
78 # include <sys/cygwin.h>
79 #endif
81 #if defined __BEOS__ || defined __HAIKU__ /* BeOS, Haiku */
82 # include <OS.h>
83 #endif
85 char *
86 get_progname_of (pid_t pid)
88 #if defined __linux__ || defined __ANDROID__ || (defined __FreeBSD_kernel__ && !defined __FreeBSD__) || defined __GNU__ || defined __NetBSD__ /* Linux, GNU/kFreeBSD, GNU/Hurd, NetBSD */
89 /* GNU/kFreeBSD mounts /proc as linprocfs, which looks like a Linux /proc
90 file system. */
92 /* Read the symlink /proc/<pid>/exe. */
94 char filename[6 + 10 + 4 + 1];
95 char linkbuf[1024 + 1];
96 ssize_t linklen;
98 sprintf (filename, "/proc/%u/exe", (unsigned int) pid);
99 linklen = readlink (filename, linkbuf, sizeof (linkbuf) - 1);
100 if (linklen > 0)
102 char *slash;
104 /* NUL-terminate the link. */
105 linkbuf[linklen] = '\0';
106 /* Find the portion after the last slash. */
107 slash = strrchr (linkbuf, '/');
108 return strdup (slash != NULL ? slash + 1 : linkbuf);
112 # if defined __ANDROID__
113 /* But it may fail with "Permission denied". As a fallback,
114 read the contents of /proc/<pid>/cmdline into memory. */
116 char filename[6 + 10 + 8 + 1];
117 int fd;
119 sprintf (filename, "/proc/%u/cmdline", (unsigned int) pid);
120 fd = open (filename, O_RDONLY | O_CLOEXEC);
121 if (fd >= 0)
123 char buf[4096 + 1];
124 ssize_t nread = read (fd, buf, sizeof (buf) - 1);
125 close (fd);
126 if (nread >= 0)
128 char *slash;
130 /* NUL-terminate the buffer (just in case it does not have the
131 expected format). */
132 buf[nread] = '\0';
133 /* The program name and each argument is followed by a NUL byte. */
134 /* Find the portion after the last slash. */
135 slash = strrchr (buf, '/');
136 return strdup (slash != NULL ? slash + 1 : buf);
140 # endif
142 #endif
144 #if defined __FreeBSD__ /* FreeBSD */
146 /* Read the symlink /proc/<pid>/file. */
147 char filename[6 + 10 + 5 + 1];
148 char linkbuf[1024 + 1];
149 ssize_t linklen;
151 sprintf (filename, "/proc/%u/file", (unsigned int) pid);
152 linklen = readlink (filename, linkbuf, sizeof (linkbuf) - 1);
153 if (linklen > 0)
155 char *slash;
157 /* NUL-terminate the link. */
158 linkbuf[linklen] = '\0';
159 /* Find the portion after the last slash. */
160 slash = strrchr (linkbuf, '/');
161 return strdup (slash != NULL ? slash + 1 : linkbuf);
164 #endif
166 #if defined __minix /* Minix */
168 /* Read the contents of /proc/<pid>/psinfo into memory. */
169 char filename[6 + 10 + 7 + 1];
170 int fd;
172 sprintf (filename, "/proc/%u/psinfo", (unsigned int) pid);
173 fd = open (filename, O_RDONLY | O_CLOEXEC);
174 if (fd >= 0)
176 char buf[4096 + 1];
177 ssize_t nread = read (fd, buf, sizeof (buf) - 1);
178 close (fd);
179 if (nread >= 0)
181 char *p;
182 int count;
184 /* NUL-terminate the buffer. */
185 buf[nread] = '\0';
187 /* Search for the 4th space-separated field. */
188 p = strchr (buf, ' ');
189 for (count = 1; p != NULL && count < 3; count++)
190 p = strchr (p + 1, ' ');
191 if (p != NULL)
193 char *start = p + 1;
194 char *end = strchr (p + 1, ' ');
195 if (end != NULL)
197 *end = '\0';
198 return strdup (start);
204 #endif
206 #if defined __sun /* Solaris */
208 /* Read the symlink /proc/<pid>/path/a.out.
209 When it succeeds, it doesn't truncate. */
211 char filename[6 + 10 + 11 + 1];
212 char linkbuf[1024 + 1];
213 ssize_t linklen;
215 sprintf (filename, "/proc/%u/path/a.out", (unsigned int) pid);
216 linklen = readlink (filename, linkbuf, sizeof (linkbuf) - 1);
217 if (linklen > 0)
219 char *slash;
221 /* NUL-terminate the link. */
222 linkbuf[linklen] = '\0';
223 /* Find the portion after the last slash. */
224 slash = strrchr (linkbuf, '/');
225 return strdup (slash != NULL ? slash + 1 : linkbuf);
229 /* But it may fail with "Permission denied". As a fallback,
230 read the contents of /proc/<pid>/psinfo into memory.
231 Alternatively, we could read the contents of /proc/<pid>/status into
232 memory. But it contains a lot of information that we don't need. */
234 char filename[6 + 10 + 7 + 1];
235 int fd;
237 sprintf (filename, "/proc/%u/psinfo", (unsigned int) pid);
238 fd = open (filename, O_RDONLY | O_CLOEXEC);
239 if (fd >= 0)
241 /* The contents is a 'struct psinfo'. But since 'struct psinfo'
242 has a different size in a 32-bit and a 64-bit environment, we
243 avoid it. Nevertheless, the size of this contents depends on
244 whether the process that reads it is 32-bit or 64-bit! */
245 #if defined __LP64__
246 # define PSINFO_SIZE 416
247 # define PSINFO_FNAME_OFFSET 136
248 #else
249 # define PSINFO_SIZE 336
250 # define PSINFO_FNAME_OFFSET 88
251 #endif
252 char buf[PSINFO_SIZE];
253 ssize_t nread = read (fd, buf, sizeof (buf));
254 close (fd);
255 if (nread >= PSINFO_FNAME_OFFSET + 16)
257 /* Make sure it's NUL-terminated. */
258 buf[PSINFO_FNAME_OFFSET + 16] = '\0';
259 return strdup (&buf[PSINFO_FNAME_OFFSET]);
264 #endif
266 #if defined __OpenBSD__ /* OpenBSD */
268 /* Documentation: https://man.openbsd.org/sysctl.2 */
269 int info_path[] =
270 { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid, sizeof (struct kinfo_proc), 1 };
271 struct kinfo_proc info;
272 size_t len;
274 len = sizeof (info);
275 if (sysctl (info_path, 6, &info, &len, NULL, 0) >= 0 && len == sizeof (info))
276 return strdup (info.p_comm);
278 #endif
280 #if defined __APPLE__ && defined __MACH__ /* Mac OS X */
281 # if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050
283 /* Mac OS X >= 10.7 has PROC_PIDT_SHORTBSDINFO. */
284 # if defined PROC_PIDT_SHORTBSDINFO
285 # if MAC_OS_X_VERSION_MIN_REQUIRED < 1050
286 if (proc_pidinfo != NULL) /* at runtime Mac OS X >= 10.5 ? */
287 # endif
289 struct proc_bsdshortinfo info;
291 if (proc_pidinfo (pid, PROC_PIDT_SHORTBSDINFO, 0, &info, sizeof (info))
292 == sizeof (info))
293 return strdup (info.pbsi_comm);
295 # endif
297 # if MAC_OS_X_VERSION_MIN_REQUIRED < 1070
298 /* For older versions, use PROC_PIDTBSDINFO instead. */
299 /* Note: The second part of 'struct proc_bsdinfo' differs in size between
300 32-bit and 64-bit environments, and the kernel of Mac OS X 10.5 knows
301 only about the 32-bit 'struct proc_bsdinfo'. Fortunately all the info
302 we need is in the first part, which is the same in 32-bit and 64-bit. */
303 # if MAC_OS_X_VERSION_MIN_REQUIRED < 1050
304 if (proc_pidinfo != NULL) /* at runtime Mac OS X >= 10.5 ? */
305 # endif
307 struct proc_bsdinfo info;
309 if (proc_pidinfo (pid, PROC_PIDTBSDINFO, 0, &info, 128) == 128)
310 return strdup (info.pbi_comm);
312 # endif
314 # endif
315 #endif
317 #if defined _AIX /* AIX */
319 /* Reference: https://www.ibm.com/support/knowledgecenter/en/ssw_aix_61/com.ibm.aix.basetrf1/getprocs.htm
321 struct procentry64 procs;
322 if (getprocs64 (&procs, sizeof procs, NULL, 0, &pid, 1) > 0)
323 return strdup (procs.pi_comm);
325 #endif
327 #if defined __hpux /* HP-UX */
329 char *p;
330 struct pst_status status;
331 if (pstat_getproc (&status, sizeof status, 0, pid) > 0)
333 char *ucomm = status.pst_ucomm;
334 char *cmd = status.pst_cmd;
335 if (strlen (ucomm) < PST_UCOMMLEN - 1)
336 p = ucomm;
337 else
339 /* ucomm is truncated to length PST_UCOMMLEN - 1.
340 Look at cmd instead. */
341 char *space = strchr (cmd, ' ');
342 if (space != NULL)
343 *space = '\0';
344 p = strrchr (cmd, '/');
345 if (p != NULL)
346 p++;
347 else
348 p = cmd;
349 if (strlen (p) > PST_UCOMMLEN - 1
350 && memcmp (p, ucomm, PST_UCOMMLEN - 1) == 0)
351 /* p is less truncated than ucomm. */
353 else
354 p = ucomm;
356 p = strdup (p);
358 else
360 # if !defined __LP64__
361 /* Support for 32-bit programs running in 64-bit HP-UX.
362 The documented way to do this is to use the same source code
363 as above, but in a compilation unit where '#define _PSTAT64 1'
364 is in effect. I prefer a single compilation unit; the struct
365 size and the offsets are not going to change. */
366 char status64[1216];
367 if (__pstat_getproc64 (status64, sizeof status64, 0, pid) > 0)
369 char *ucomm = status64 + 288;
370 char *cmd = status64 + 168;
371 if (strlen (ucomm) < PST_UCOMMLEN - 1)
372 p = ucomm;
373 else
375 /* ucomm is truncated to length PST_UCOMMLEN - 1.
376 Look at cmd instead. */
377 char *space = strchr (cmd, ' ');
378 if (space != NULL)
379 *space = '\0';
380 p = strrchr (cmd, '/');
381 if (p != NULL)
382 p++;
383 else
384 p = cmd;
385 if (strlen (p) > PST_UCOMMLEN - 1
386 && memcmp (p, ucomm, PST_UCOMMLEN - 1) == 0)
387 /* p is less truncated than ucomm. */
389 else
390 p = ucomm;
392 p = strdup (p);
394 else
395 # endif
396 p = NULL;
398 if (p != NULL)
399 return strdup (p);
401 #endif
403 #if defined __sgi /* IRIX */
405 char filename[12 + 10 + 1];
406 int fd;
408 sprintf (filename, "/proc/pinfo/%u", pid);
409 fd = open (filename, O_RDONLY | O_CLOEXEC);
410 if (0 <= fd)
412 prpsinfo_t buf;
413 int ioctl_ok = 0 <= ioctl (fd, PIOCPSINFO, &buf);
414 close (fd);
415 if (ioctl_ok)
417 char *name = buf.pr_fname;
418 size_t namesize = sizeof buf.pr_fname;
419 /* It may not be NUL-terminated. */
420 char *namenul = memchr (name, '\0', namesize);
421 size_t namelen = namenul ? namenul - name : namesize;
422 char *namecopy = malloc (namelen + 1);
423 if (namecopy)
425 namecopy[namelen] = '\0';
426 return memcpy (namecopy, name, namelen);
431 #endif
433 #if defined __CYGWIN__ /* Cygwin */
435 struct external_pinfo *info =
436 (struct external_pinfo *) cygwin_internal (CW_GETPINFO, pid);
437 if (info != NULL)
439 const char *name = info->progname;
440 size_t namesize = sizeof (info->progname);
441 /* It may not be NUL-terminated. */
442 const char *namenul = memchr (name, '\0', namesize);
443 size_t namelen = namenul ? namenul - name : namesize;
445 /* Find the portion after the last backslash.
446 Cygwin does not have memrchr(). */
448 const char *backslash = memchr (name, '\\', namelen);
449 if (backslash != NULL)
451 const char *name_end = name + namelen;
452 for (;;)
454 const char *next_backslash =
455 memchr (backslash + 1, '\\', name_end - (backslash + 1));
456 if (next_backslash == NULL)
457 break;
458 backslash = next_backslash;
460 name = backslash + 1;
461 namelen = name_end - name;
466 char *namecopy = malloc (namelen + 1);
467 if (namecopy)
469 namecopy[namelen] = '\0';
470 return memcpy (namecopy, name, namelen);
475 #endif
477 #if defined __BEOS__ || defined __HAIKU__ /* BeOS, Haiku */
479 team_info info;
480 if (_get_team_info (pid, &info, sizeof (info)) == B_OK)
482 const char *name = info.args;
483 size_t namesize = sizeof (info.args);
484 /* It may not be NUL-terminated. */
485 const char *namenul = memchr (name, '\0', namesize);
486 size_t namelen = namenul ? namenul - name : namesize;
488 /* Take the portion up to the first space. */
490 const char *space = memchr (name, ' ', namelen);
491 if (space != NULL)
492 namelen = space - name;
495 /* Find the portion after the last slash. */
497 const char *slash = memchr (name, '/', namelen);
498 if (slash != NULL)
500 const char *name_end = name + namelen;
501 for (;;)
503 const char *next_slash =
504 memchr (slash + 1, '/', name_end - (slash + 1));
505 if (next_slash == NULL)
506 break;
507 slash = next_slash;
509 name = slash + 1;
510 namelen = name_end - name;
515 char *namecopy = malloc (namelen + 1);
516 if (namecopy)
518 namecopy[namelen] = '\0';
519 return memcpy (namecopy, name, namelen);
524 #endif
526 return NULL;
529 #ifdef TEST
531 #include <stdlib.h>
532 #include <unistd.h>
534 /* Usage: ./a.out
535 or: ./a.out PID
538 main (int argc, char *argv[])
540 char *arg = argv[1];
541 pid_t pid = (arg != NULL ? atoi (arg) : getpid ());
542 char *progname = get_progname_of (pid);
543 printf ("PID=%lu COMMAND=%s\n",
544 (unsigned long) pid, progname != NULL ? progname : "(null)");
545 free (progname);
546 return 0;
550 * Local Variables:
551 * compile-command: "gcc -ggdb -DTEST -Wall -I.. get_progname_of.c"
552 * End:
555 #endif