summaryrefslogtreecommitdiff
path: root/gdb/gdbsupport/filestuff.c
diff options
context:
space:
mode:
Diffstat (limited to 'gdb/gdbsupport/filestuff.c')
-rw-r--r--gdb/gdbsupport/filestuff.c503
1 files changed, 503 insertions, 0 deletions
diff --git a/gdb/gdbsupport/filestuff.c b/gdb/gdbsupport/filestuff.c
new file mode 100644
index 00000000000..b8fca1552a4
--- /dev/null
+++ b/gdb/gdbsupport/filestuff.c
@@ -0,0 +1,503 @@
+/* Low-level file-handling.
+ Copyright (C) 2012-2019 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ 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 <http://www.gnu.org/licenses/>. */
+
+#include "common-defs.h"
+#include "filestuff.h"
+#include "gdb_vecs.h"
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <algorithm>
+
+#ifdef USE_WIN32API
+#include <winsock2.h>
+#include <windows.h>
+#define HAVE_SOCKETS 1
+#elif defined HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+/* Define HAVE_F_GETFD if we plan to use F_GETFD. */
+#define HAVE_F_GETFD F_GETFD
+#define HAVE_SOCKETS 1
+#endif
+
+#ifdef HAVE_KINFO_GETFILE
+#include <sys/user.h>
+#include <libutil.h>
+#endif
+
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif /* HAVE_SYS_RESOURCE_H */
+
+#ifndef O_CLOEXEC
+#define O_CLOEXEC 0
+#endif
+
+#ifndef O_NOINHERIT
+#define O_NOINHERIT 0
+#endif
+
+#ifndef SOCK_CLOEXEC
+#define SOCK_CLOEXEC 0
+#endif
+
+
+
+#ifndef HAVE_FDWALK
+
+#include <dirent.h>
+
+/* Replacement for fdwalk, if the system doesn't define it. Walks all
+ open file descriptors (though this implementation may walk closed
+ ones as well, depending on the host platform's capabilities) and
+ call FUNC with ARG. If FUNC returns non-zero, stops immediately
+ and returns the same value. Otherwise, returns zero when
+ finished. */
+
+static int
+fdwalk (int (*func) (void *, int), void *arg)
+{
+ /* Checking __linux__ isn't great but it isn't clear what would be
+ better. There doesn't seem to be a good way to check for this in
+ configure. */
+#ifdef __linux__
+ DIR *dir;
+
+ dir = opendir ("/proc/self/fd");
+ if (dir != NULL)
+ {
+ struct dirent *entry;
+ int result = 0;
+
+ for (entry = readdir (dir); entry != NULL; entry = readdir (dir))
+ {
+ long fd;
+ char *tail;
+
+ errno = 0;
+ fd = strtol (entry->d_name, &tail, 10);
+ if (*tail != '\0' || errno != 0)
+ continue;
+ if ((int) fd != fd)
+ {
+ /* What can we do here really? */
+ continue;
+ }
+
+ if (fd == dirfd (dir))
+ continue;
+
+ result = func (arg, fd);
+ if (result != 0)
+ break;
+ }
+
+ closedir (dir);
+ return result;
+ }
+ /* We may fall through to the next case. */
+#endif
+#ifdef HAVE_KINFO_GETFILE
+ int nfd;
+ gdb::unique_xmalloc_ptr<struct kinfo_file[]> fdtbl
+ (kinfo_getfile (getpid (), &nfd));
+ if (fdtbl != NULL)
+ {
+ for (int i = 0; i < nfd; i++)
+ {
+ if (fdtbl[i].kf_fd >= 0)
+ {
+ int result = func (arg, fdtbl[i].kf_fd);
+ if (result != 0)
+ return result;
+ }
+ }
+ return 0;
+ }
+ /* We may fall through to the next case. */
+#endif
+
+ {
+ int max, fd;
+
+#if defined(HAVE_GETRLIMIT) && defined(RLIMIT_NOFILE)
+ struct rlimit rlim;
+
+ if (getrlimit (RLIMIT_NOFILE, &rlim) == 0 && rlim.rlim_max != RLIM_INFINITY)
+ max = rlim.rlim_max;
+ else
+#endif
+ {
+#ifdef _SC_OPEN_MAX
+ max = sysconf (_SC_OPEN_MAX);
+#else
+ /* Whoops. */
+ return 0;
+#endif /* _SC_OPEN_MAX */
+ }
+
+ for (fd = 0; fd < max; ++fd)
+ {
+ struct stat sb;
+ int result;
+
+ /* Only call FUNC for open fds. */
+ if (fstat (fd, &sb) == -1)
+ continue;
+
+ result = func (arg, fd);
+ if (result != 0)
+ return result;
+ }
+
+ return 0;
+ }
+}
+
+#endif /* HAVE_FDWALK */
+
+
+
+/* A vector holding all the fds open when notice_open_fds was called. We
+ don't use a hashtab because we don't expect there to be many open fds. */
+
+static std::vector<int> open_fds;
+
+/* An fdwalk callback function used by notice_open_fds. It puts the
+ given file descriptor into the vec. */
+
+static int
+do_mark_open_fd (void *ignore, int fd)
+{
+ open_fds.push_back (fd);
+ return 0;
+}
+
+/* See filestuff.h. */
+
+void
+notice_open_fds (void)
+{
+ fdwalk (do_mark_open_fd, NULL);
+}
+
+/* See filestuff.h. */
+
+void
+mark_fd_no_cloexec (int fd)
+{
+ do_mark_open_fd (NULL, fd);
+}
+
+/* See filestuff.h. */
+
+void
+unmark_fd_no_cloexec (int fd)
+{
+ auto it = std::remove (open_fds.begin (), open_fds.end (), fd);
+
+ if (it != open_fds.end ())
+ open_fds.erase (it);
+ else
+ gdb_assert_not_reached (_("fd not found in open_fds"));
+}
+
+/* Helper function for close_most_fds that closes the file descriptor
+ if appropriate. */
+
+static int
+do_close (void *ignore, int fd)
+{
+ for (int val : open_fds)
+ {
+ if (fd == val)
+ {
+ /* Keep this one open. */
+ return 0;
+ }
+ }
+
+ close (fd);
+ return 0;
+}
+
+/* See filestuff.h. */
+
+void
+close_most_fds (void)
+{
+ fdwalk (do_close, NULL);
+}
+
+
+
+/* This is a tri-state flag. When zero it means we haven't yet tried
+ O_CLOEXEC. When positive it means that O_CLOEXEC works on this
+ host. When negative, it means that O_CLOEXEC doesn't work. We
+ track this state because, while gdb might have been compiled
+ against a libc that supplies O_CLOEXEC, there is no guarantee that
+ the kernel supports it. */
+
+static int trust_o_cloexec;
+
+/* Mark FD as close-on-exec, ignoring errors. Update
+ TRUST_O_CLOEXEC. */
+
+static void
+mark_cloexec (int fd)
+{
+#ifdef HAVE_F_GETFD
+ int old = fcntl (fd, F_GETFD, 0);
+
+ if (old != -1)
+ {
+ fcntl (fd, F_SETFD, old | FD_CLOEXEC);
+
+ if (trust_o_cloexec == 0)
+ {
+ if ((old & FD_CLOEXEC) != 0)
+ trust_o_cloexec = 1;
+ else
+ trust_o_cloexec = -1;
+ }
+ }
+#endif /* HAVE_F_GETFD */
+}
+
+/* Depending on TRUST_O_CLOEXEC, mark FD as close-on-exec. */
+
+static void
+maybe_mark_cloexec (int fd)
+{
+ if (trust_o_cloexec <= 0)
+ mark_cloexec (fd);
+}
+
+#ifdef HAVE_SOCKETS
+
+/* Like maybe_mark_cloexec, but for callers that use SOCK_CLOEXEC. */
+
+static void
+socket_mark_cloexec (int fd)
+{
+ if (SOCK_CLOEXEC == 0 || trust_o_cloexec <= 0)
+ mark_cloexec (fd);
+}
+
+#endif
+
+
+
+/* See filestuff.h. */
+
+int
+gdb_open_cloexec (const char *filename, int flags, unsigned long mode)
+{
+ int fd = open (filename, flags | O_CLOEXEC, mode);
+
+ if (fd >= 0)
+ maybe_mark_cloexec (fd);
+
+ return fd;
+}
+
+/* See filestuff.h. */
+
+gdb_file_up
+gdb_fopen_cloexec (const char *filename, const char *opentype)
+{
+ FILE *result;
+ /* Probe for "e" support once. But, if we can tell the operating
+ system doesn't know about close on exec mode "e" without probing,
+ skip it. E.g., the Windows runtime issues an "Invalid parameter
+ passed to C runtime function" OutputDebugString warning for
+ unknown modes. Assume that if O_CLOEXEC is zero, then "e" isn't
+ supported. On MinGW, O_CLOEXEC is an alias of O_NOINHERIT, and
+ "e" isn't supported. */
+ static int fopen_e_ever_failed_einval =
+ O_CLOEXEC == 0 || O_CLOEXEC == O_NOINHERIT;
+
+ if (!fopen_e_ever_failed_einval)
+ {
+ char *copy;
+
+ copy = (char *) alloca (strlen (opentype) + 2);
+ strcpy (copy, opentype);
+ /* This is a glibc extension but we try it unconditionally on
+ this path. */
+ strcat (copy, "e");
+ result = fopen (filename, copy);
+
+ if (result == NULL && errno == EINVAL)
+ {
+ result = fopen (filename, opentype);
+ if (result != NULL)
+ fopen_e_ever_failed_einval = 1;
+ }
+ }
+ else
+ result = fopen (filename, opentype);
+
+ if (result != NULL)
+ maybe_mark_cloexec (fileno (result));
+
+ return gdb_file_up (result);
+}
+
+#ifdef HAVE_SOCKETS
+/* See filestuff.h. */
+
+int
+gdb_socketpair_cloexec (int domain, int style, int protocol,
+ int filedes[2])
+{
+#ifdef HAVE_SOCKETPAIR
+ int result = socketpair (domain, style | SOCK_CLOEXEC, protocol, filedes);
+
+ if (result != -1)
+ {
+ socket_mark_cloexec (filedes[0]);
+ socket_mark_cloexec (filedes[1]);
+ }
+
+ return result;
+#else
+ gdb_assert_not_reached (_("socketpair not available on this host"));
+#endif
+}
+
+/* See filestuff.h. */
+
+int
+gdb_socket_cloexec (int domain, int style, int protocol)
+{
+ int result = socket (domain, style | SOCK_CLOEXEC, protocol);
+
+ if (result != -1)
+ socket_mark_cloexec (result);
+
+ return result;
+}
+#endif
+
+/* See filestuff.h. */
+
+int
+gdb_pipe_cloexec (int filedes[2])
+{
+ int result;
+
+#ifdef HAVE_PIPE2
+ result = pipe2 (filedes, O_CLOEXEC);
+ if (result != -1)
+ {
+ maybe_mark_cloexec (filedes[0]);
+ maybe_mark_cloexec (filedes[1]);
+ }
+#else
+#ifdef HAVE_PIPE
+ result = pipe (filedes);
+ if (result != -1)
+ {
+ mark_cloexec (filedes[0]);
+ mark_cloexec (filedes[1]);
+ }
+#else /* HAVE_PIPE */
+ gdb_assert_not_reached (_("pipe not available on this host"));
+#endif /* HAVE_PIPE */
+#endif /* HAVE_PIPE2 */
+
+ return result;
+}
+
+/* See gdbsupport/filestuff.h. */
+
+bool
+is_regular_file (const char *name, int *errno_ptr)
+{
+ struct stat st;
+ const int status = stat (name, &st);
+
+ /* Stat should never fail except when the file does not exist.
+ If stat fails, analyze the source of error and return true
+ unless the file does not exist, to avoid returning false results
+ on obscure systems where stat does not work as expected. */
+
+ if (status != 0)
+ {
+ if (errno != ENOENT)
+ return true;
+ *errno_ptr = ENOENT;
+ return false;
+ }
+
+ if (S_ISREG (st.st_mode))
+ return true;
+
+ if (S_ISDIR (st.st_mode))
+ *errno_ptr = EISDIR;
+ else
+ *errno_ptr = EINVAL;
+ return false;
+}
+
+/* See gdbsupport/filestuff.h. */
+
+bool
+mkdir_recursive (const char *dir)
+{
+ auto holder = make_unique_xstrdup (dir);
+ char * const start = holder.get ();
+ char *component_start = start;
+ char *component_end = start;
+
+ while (1)
+ {
+ /* Find the beginning of the next component. */
+ while (*component_start == '/')
+ component_start++;
+
+ /* Are we done? */
+ if (*component_start == '\0')
+ return true;
+
+ /* Find the slash or null-terminator after this component. */
+ component_end = component_start;
+ while (*component_end != '/' && *component_end != '\0')
+ component_end++;
+
+ /* Temporarily replace the slash with a null terminator, so we can create
+ the directory up to this component. */
+ char saved_char = *component_end;
+ *component_end = '\0';
+
+ /* If we get EEXIST and the existing path is a directory, then we're
+ happy. If it exists, but it's a regular file and this is not the last
+ component, we'll fail at the next component. If this is the last
+ component, the caller will fail with ENOTDIR when trying to
+ open/create a file under that path. */
+ if (mkdir (start, 0700) != 0)
+ if (errno != EEXIST)
+ return false;
+
+ /* Restore the overwritten char. */
+ *component_end = saved_char;
+ component_start = component_end;
+ }
+}