| /* Determine the program name of a given process. |
| Copyright (C) 2016-2020 Free Software Foundation, Inc. |
| Written by Bruno Haible <bruno@clisp.org>, 2019. |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program. If not, see <https://www.gnu.org/licenses/>. */ |
| |
| #include <config.h> |
| |
| /* Specification. */ |
| #include "get_progname_of.h" |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #if defined __linux__ || defined __ANDROID__ || (defined __FreeBSD_kernel__ && !defined __FreeBSD__) || defined __GNU__ || defined __NetBSD__ || defined __FreeBSD__ /* Linux, GNU/kFreeBSD, GNU/Hurd, NetBSD, FreeBSD */ |
| # include <unistd.h> |
| # if defined __ANDROID__ |
| # include <fcntl.h> |
| # endif |
| #endif |
| |
| #if defined __minix || defined __sun /* Minix, Solaris */ |
| # include <fcntl.h> |
| # include <unistd.h> |
| #endif |
| |
| #if defined __OpenBSD__ /* OpenBSD */ |
| # include <sys/sysctl.h> /* sysctl, struct kinfo_proc */ |
| #endif |
| |
| #if defined __APPLE__ && defined __MACH__ /* Mac OS X */ |
| # include <libproc.h> |
| #endif |
| |
| #if defined _AIX /* AIX */ |
| # include <procinfo.h> |
| #endif |
| |
| #if defined __hpux /* HP-UX */ |
| # include <unistd.h> |
| # include <sys/param.h> |
| # include <sys/pstat.h> |
| #endif |
| |
| #if defined __sgi /* IRIX */ |
| # include <unistd.h> |
| # include <fcntl.h> |
| # include <sys/procfs.h> |
| #endif |
| |
| #if defined __CYGWIN__ /* Cygwin */ |
| # define WIN32_LEAN_AND_MEAN |
| # include <windows.h> /* needed to get 'struct external_pinfo' defined */ |
| # include <sys/cygwin.h> |
| #endif |
| |
| #if defined __BEOS__ || defined __HAIKU__ /* BeOS, Haiku */ |
| # include <OS.h> |
| #endif |
| |
| char * |
| get_progname_of (pid_t pid) |
| { |
| #if defined __linux__ || defined __ANDROID__ || (defined __FreeBSD_kernel__ && !defined __FreeBSD__) || defined __GNU__ || defined __NetBSD__ /* Linux, GNU/kFreeBSD, GNU/Hurd, NetBSD */ |
| /* GNU/kFreeBSD mounts /proc as linprocfs, which looks like a Linux /proc |
| file system. */ |
| |
| /* Read the symlink /proc/<pid>/exe. */ |
| { |
| char filename[6 + 10 + 4 + 1]; |
| char linkbuf[1024 + 1]; |
| ssize_t linklen; |
| |
| sprintf (filename, "/proc/%u/exe", (unsigned int) pid); |
| linklen = readlink (filename, linkbuf, sizeof (linkbuf) - 1); |
| if (linklen > 0) |
| { |
| char *slash; |
| |
| /* NUL-terminate the link. */ |
| linkbuf[linklen] = '\0'; |
| /* Find the portion after the last slash. */ |
| slash = strrchr (linkbuf, '/'); |
| return strdup (slash != NULL ? slash + 1 : linkbuf); |
| } |
| } |
| |
| # if defined __ANDROID__ |
| /* But it may fail with "Permission denied". As a fallback, |
| read the contents of /proc/<pid>/cmdline into memory. */ |
| { |
| char filename[6 + 10 + 8 + 1]; |
| int fd; |
| |
| sprintf (filename, "/proc/%u/cmdline", (unsigned int) pid); |
| fd = open (filename, O_RDONLY); |
| if (fd >= 0) |
| { |
| char buf[4096 + 1]; |
| ssize_t nread = read (fd, buf, sizeof (buf) - 1); |
| close (fd); |
| if (nread >= 0) |
| { |
| char *slash; |
| |
| /* NUL-terminate the buffer (just in case it does not have the |
| expected format). */ |
| buf[nread] = '\0'; |
| /* The program name and each argument is followed by a NUL byte. */ |
| /* Find the portion after the last slash. */ |
| slash = strrchr (buf, '/'); |
| return strdup (slash != NULL ? slash + 1 : buf); |
| } |
| } |
| } |
| # endif |
| |
| #endif |
| |
| #if defined __FreeBSD__ /* FreeBSD */ |
| |
| /* Read the symlink /proc/<pid>/file. */ |
| char filename[6 + 10 + 5 + 1]; |
| char linkbuf[1024 + 1]; |
| ssize_t linklen; |
| |
| sprintf (filename, "/proc/%u/file", (unsigned int) pid); |
| linklen = readlink (filename, linkbuf, sizeof (linkbuf) - 1); |
| if (linklen > 0) |
| { |
| char *slash; |
| |
| /* NUL-terminate the link. */ |
| linkbuf[linklen] = '\0'; |
| /* Find the portion after the last slash. */ |
| slash = strrchr (linkbuf, '/'); |
| return strdup (slash != NULL ? slash + 1 : linkbuf); |
| } |
| |
| #endif |
| |
| #if defined __minix /* Minix */ |
| |
| /* Read the contents of /proc/<pid>/psinfo into memory. */ |
| char filename[6 + 10 + 7 + 1]; |
| int fd; |
| |
| sprintf (filename, "/proc/%u/psinfo", (unsigned int) pid); |
| fd = open (filename, O_RDONLY); |
| if (fd >= 0) |
| { |
| char buf[4096 + 1]; |
| ssize_t nread = read (fd, buf, sizeof (buf) - 1); |
| close (fd); |
| if (nread >= 0) |
| { |
| char *p; |
| int count; |
| |
| /* NUL-terminate the buffer. */ |
| buf[nread] = '\0'; |
| |
| /* Search for the 4th space-separated field. */ |
| p = strchr (buf, ' '); |
| for (count = 1; p != NULL && count < 3; count++) |
| p = strchr (p + 1, ' '); |
| if (p != NULL) |
| { |
| char *start = p + 1; |
| char *end = strchr (p + 1, ' '); |
| if (end != NULL) |
| { |
| *end = '\0'; |
| return strdup (start); |
| } |
| } |
| } |
| } |
| |
| #endif |
| |
| #if defined __sun /* Solaris */ |
| |
| /* Read the symlink /proc/<pid>/path/a.out. |
| When it succeeds, it doesn't truncate. */ |
| { |
| char filename[6 + 10 + 11 + 1]; |
| char linkbuf[1024 + 1]; |
| ssize_t linklen; |
| |
| sprintf (filename, "/proc/%u/path/a.out", (unsigned int) pid); |
| linklen = readlink (filename, linkbuf, sizeof (linkbuf) - 1); |
| if (linklen > 0) |
| { |
| char *slash; |
| |
| /* NUL-terminate the link. */ |
| linkbuf[linklen] = '\0'; |
| /* Find the portion after the last slash. */ |
| slash = strrchr (linkbuf, '/'); |
| return strdup (slash != NULL ? slash + 1 : linkbuf); |
| } |
| } |
| |
| /* But it may fail with "Permission denied". As a fallback, |
| read the contents of /proc/<pid>/psinfo into memory. |
| Alternatively, we could read the contents of /proc/<pid>/status into |
| memory. But it contains a lot of information that we don't need. */ |
| { |
| char filename[6 + 10 + 7 + 1]; |
| int fd; |
| |
| sprintf (filename, "/proc/%u/psinfo", (unsigned int) pid); |
| fd = open (filename, O_RDONLY); |
| if (fd >= 0) |
| { |
| /* The contents is a 'struct psinfo'. But since 'struct psinfo' |
| has a different size in a 32-bit and a 64-bit environment, we |
| avoid it. Nevertheless, the size of this contents depends on |
| whether the process that reads it is 32-bit or 64-bit! */ |
| #if defined __LP64__ |
| # define PSINFO_SIZE 416 |
| # define PSINFO_FNAME_OFFSET 136 |
| #else |
| # define PSINFO_SIZE 336 |
| # define PSINFO_FNAME_OFFSET 88 |
| #endif |
| char buf[PSINFO_SIZE]; |
| ssize_t nread = read (fd, buf, sizeof (buf)); |
| close (fd); |
| if (nread >= PSINFO_FNAME_OFFSET + 16) |
| { |
| /* Make sure it's NUL-terminated. */ |
| buf[PSINFO_FNAME_OFFSET + 16] = '\0'; |
| return strdup (&buf[PSINFO_FNAME_OFFSET]); |
| } |
| } |
| } |
| |
| #endif |
| |
| #if defined __OpenBSD__ /* OpenBSD */ |
| |
| /* Documentation: https://man.openbsd.org/sysctl.2 */ |
| int info_path[] = |
| { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid, sizeof (struct kinfo_proc), 1 }; |
| struct kinfo_proc info; |
| size_t len; |
| |
| len = sizeof (info); |
| if (sysctl (info_path, 6, &info, &len, NULL, 0) >= 0 && len == sizeof (info)) |
| return strdup (info.p_comm); |
| |
| #endif |
| |
| #if defined __APPLE__ && defined __MACH__ /* Mac OS X */ |
| |
| # if defined PROC_PIDT_SHORTBSDINFO |
| struct proc_bsdshortinfo info; |
| |
| if (proc_pidinfo (pid, PROC_PIDT_SHORTBSDINFO, 0, &info, sizeof (info)) |
| == sizeof (info)) |
| return strdup (info.pbsi_comm); |
| # else |
| /* Note: The second part of 'struct proc_bsdinfo' differs in size between |
| 32-bit and 64-bit environments, and the kernel of Mac OS X 10.5 knows |
| only about the 32-bit 'struct proc_bsdinfo'. Fortunately all the info |
| we need is in the first part, which is the same in 32-bit and 64-bit. */ |
| struct proc_bsdinfo info; |
| |
| if (proc_pidinfo (pid, PROC_PIDTBSDINFO, 0, &info, 128) == 128) |
| return strdup (info.pbi_comm); |
| # endif |
| |
| #endif |
| |
| #if defined _AIX /* AIX */ |
| |
| /* Reference: https://www.ibm.com/support/knowledgecenter/en/ssw_aix_61/com.ibm.aix.basetrf1/getprocs.htm |
| */ |
| struct procentry64 procs; |
| if (getprocs64 (&procs, sizeof procs, NULL, 0, &pid, 1) > 0) |
| return strdup (procs.pi_comm); |
| |
| #endif |
| |
| #if defined __hpux /* HP-UX */ |
| |
| char *p; |
| struct pst_status status; |
| if (pstat_getproc (&status, sizeof status, 0, pid) > 0) |
| { |
| char *ucomm = status.pst_ucomm; |
| char *cmd = status.pst_cmd; |
| if (strlen (ucomm) < PST_UCOMMLEN - 1) |
| p = ucomm; |
| else |
| { |
| /* ucomm is truncated to length PST_UCOMMLEN - 1. |
| Look at cmd instead. */ |
| char *space = strchr (cmd, ' '); |
| if (space != NULL) |
| *space = '\0'; |
| p = strrchr (cmd, '/'); |
| if (p != NULL) |
| p++; |
| else |
| p = cmd; |
| if (strlen (p) > PST_UCOMMLEN - 1 |
| && memcmp (p, ucomm, PST_UCOMMLEN - 1) == 0) |
| /* p is less truncated than ucomm. */ |
| ; |
| else |
| p = ucomm; |
| } |
| p = strdup (p); |
| } |
| else |
| { |
| # if !defined __LP64__ |
| /* Support for 32-bit programs running in 64-bit HP-UX. |
| The documented way to do this is to use the same source code |
| as above, but in a compilation unit where '#define _PSTAT64 1' |
| is in effect. I prefer a single compilation unit; the struct |
| size and the offsets are not going to change. */ |
| char status64[1216]; |
| if (__pstat_getproc64 (status64, sizeof status64, 0, pid) > 0) |
| { |
| char *ucomm = status64 + 288; |
| char *cmd = status64 + 168; |
| if (strlen (ucomm) < PST_UCOMMLEN - 1) |
| p = ucomm; |
| else |
| { |
| /* ucomm is truncated to length PST_UCOMMLEN - 1. |
| Look at cmd instead. */ |
| char *space = strchr (cmd, ' '); |
| if (space != NULL) |
| *space = '\0'; |
| p = strrchr (cmd, '/'); |
| if (p != NULL) |
| p++; |
| else |
| p = cmd; |
| if (strlen (p) > PST_UCOMMLEN - 1 |
| && memcmp (p, ucomm, PST_UCOMMLEN - 1) == 0) |
| /* p is less truncated than ucomm. */ |
| ; |
| else |
| p = ucomm; |
| } |
| p = strdup (p); |
| } |
| else |
| # endif |
| p = NULL; |
| } |
| if (p != NULL) |
| return strdup (p); |
| |
| #endif |
| |
| #if defined __sgi /* IRIX */ |
| |
| char filename[12 + 10 + 1]; |
| int fd; |
| |
| sprintf (filename, "/proc/pinfo/%u", pid); |
| fd = open (filename, O_RDONLY); |
| if (0 <= fd) |
| { |
| prpsinfo_t buf; |
| int ioctl_ok = 0 <= ioctl (fd, PIOCPSINFO, &buf); |
| close (fd); |
| if (ioctl_ok) |
| { |
| char *name = buf.pr_fname; |
| size_t namesize = sizeof buf.pr_fname; |
| /* It may not be NUL-terminated. */ |
| char *namenul = memchr (name, '\0', namesize); |
| size_t namelen = namenul ? namenul - name : namesize; |
| char *namecopy = malloc (namelen + 1); |
| if (namecopy) |
| { |
| namecopy[namelen] = '\0'; |
| return memcpy (namecopy, name, namelen); |
| } |
| } |
| } |
| |
| #endif |
| |
| #if defined __CYGWIN__ /* Cygwin */ |
| |
| struct external_pinfo *info = |
| (struct external_pinfo *) cygwin_internal (CW_GETPINFO, pid); |
| if (info != NULL) |
| { |
| const char *name = info->progname; |
| size_t namesize = sizeof (info->progname); |
| /* It may not be NUL-terminated. */ |
| const char *namenul = memchr (name, '\0', namesize); |
| size_t namelen = namenul ? namenul - name : namesize; |
| |
| /* Find the portion after the last backslash. |
| Cygwin does not have memrchr(). */ |
| { |
| const char *backslash = memchr (name, '\\', namelen); |
| if (backslash != NULL) |
| { |
| const char *name_end = name + namelen; |
| for (;;) |
| { |
| const char *next_backslash = |
| memchr (backslash + 1, '\\', name_end - (backslash + 1)); |
| if (next_backslash == NULL) |
| break; |
| backslash = next_backslash; |
| } |
| name = backslash + 1; |
| namelen = name_end - name; |
| } |
| } |
| |
| { |
| char *namecopy = malloc (namelen + 1); |
| if (namecopy) |
| { |
| namecopy[namelen] = '\0'; |
| return memcpy (namecopy, name, namelen); |
| } |
| } |
| } |
| |
| #endif |
| |
| #if defined __BEOS__ || defined __HAIKU__ /* BeOS, Haiku */ |
| |
| team_info info; |
| if (_get_team_info (pid, &info, sizeof (info)) == B_OK) |
| { |
| const char *name = info.args; |
| size_t namesize = sizeof (info.args); |
| /* It may not be NUL-terminated. */ |
| const char *namenul = memchr (name, '\0', namesize); |
| size_t namelen = namenul ? namenul - name : namesize; |
| |
| /* Take the portion up to the first space. */ |
| { |
| const char *space = memchr (name, ' ', namelen); |
| if (space != NULL) |
| namelen = space - name; |
| } |
| |
| /* Find the portion after the last slash. */ |
| { |
| const char *slash = memchr (name, '/', namelen); |
| if (slash != NULL) |
| { |
| const char *name_end = name + namelen; |
| for (;;) |
| { |
| const char *next_slash = |
| memchr (slash + 1, '/', name_end - (slash + 1)); |
| if (next_slash == NULL) |
| break; |
| slash = next_slash; |
| } |
| name = slash + 1; |
| namelen = name_end - name; |
| } |
| } |
| |
| { |
| char *namecopy = malloc (namelen + 1); |
| if (namecopy) |
| { |
| namecopy[namelen] = '\0'; |
| return memcpy (namecopy, name, namelen); |
| } |
| } |
| } |
| |
| #endif |
| |
| return NULL; |
| } |
| |
| #ifdef TEST |
| |
| #include <stdlib.h> |
| #include <unistd.h> |
| |
| /* Usage: ./a.out |
| or: ./a.out PID |
| */ |
| int |
| main (int argc, char *argv[]) |
| { |
| char *arg = argv[1]; |
| pid_t pid = (arg != NULL ? atoi (arg) : getpid ()); |
| char *progname = get_progname_of (pid); |
| printf ("PID=%lu COMMAND=%s\n", |
| (unsigned long) pid, progname != NULL ? progname : "(null)"); |
| free (progname); |
| return 0; |
| } |
| |
| /* |
| * Local Variables: |
| * compile-command: "gcc -ggdb -DTEST -Wall -I.. get_progname_of.c" |
| * End: |
| */ |
| |
| #endif |