[7/7] Implement vFile:setfs in gdbserver

Message ID 1429186791-6867-8-git-send-email-gbenson@redhat.com
State Superseded
Headers

Commit Message

Gary Benson April 16, 2015, 12:19 p.m. UTC
  gdb/gdbserver/ChangeLog:

	* target.h (struct target_ops) <call_with_fs_of>: New field.
	* linux-low.c (nat/linux-namespaces.h): New include.
	(linux_low_call_with_fs_of): New function.
	* linux-low.c (linux_target_ops): Initialize call_with_fs_of.
	* hostio.c (hostio_fs_pid): New static variable.
	(handle_setfs): New function.
	(enter_filesystem): Likewise.
	(open_closure): New structure.
	(handle_open_1): New function.
	(handle_open): Update to use enter_filesystem.
	(unlink_closure): New structure.
	(handle_unlink_1): New function.
	(handle_unlink): Update to use enter_filesystem.
	(readlink_closure): New structure.
	(handle_readlink_1): New function.
	(handle_readlink): Update to use enter_filesystem.
	(handle_vFile): Handle vFile:setfs packets.
---
 gdb/gdbserver/ChangeLog   |   20 ++++++
 gdb/gdbserver/hostio.c    |  152 ++++++++++++++++++++++++++++++++++++++++----
 gdb/gdbserver/linux-low.c |   10 +++
 gdb/gdbserver/target.h    |    6 ++
 4 files changed, 174 insertions(+), 14 deletions(-)
  

Comments

Pedro Alves April 17, 2015, 3:30 p.m. UTC | #1
On 04/16/2015 01:19 PM, Gary Benson wrote:
> +/* Handle a "vFile:setfs:" packet.  */
> +
> +static void
> +handle_setfs (char *own_buf)
> +{
> +  char *p;
> +  int pid;
> +
> +  p = own_buf + strlen ("vFile:setfs:");
> +
> +  if (require_int (&p, &pid)
> +      || pid < 0
> +      || require_end (p))
> +    {
> +      hostio_packet_error (own_buf);
> +      return;
> +    }
> +
> +  hostio_fs_pid = pid;
> +
> +  hostio_reply (own_buf, 0);
> +}

This should probably return empty if
the_target->call_with_fs_of is NULL, to disable the packet
in GDB?

>  int
>  handle_vFile (char *own_buf, int packet_len, int *new_packet_len)
>  {
> -  if (startswith (own_buf, "vFile:open:"))
> +  if (the_target->call_with_fs_of != NULL
> +      && startswith (own_buf, "vFile:setfs:"))
> +    handle_setfs (own_buf);
> +  else if (startswith (own_buf, "vFile:open:"))

Ah, I see now that it's handled here.  I'd prefer moving that check
inside the handle_setfs function instead so that the "setfs"
specifics handling is all localized.

> +struct open_closure
> +{
> +  char filename[HOSTIO_PATH_MAX];
> +  int flags;
> +  int mode;

mode_t mode.

(please check for the the same issue on the native side.)

> +  int return_value;
> +};

> +/* Implementation of target_ops->call_with_fs_of.  */
> +
> +static int
> +linux_low_call_with_fs_of (int pid, void (*func) (void *), void *arg)
> +{
> +  return linux_ns_enter (pid, LINUX_NS_MNT, func, arg);
> +}
> +

So back on linux_ns_enter's naming ---

I find the abstraction here a bit odd.  It seems to be me that
a function that does:

 #1 - select filesystem/namespace
 #2 - call foo
 #3 - restore filesystem/namespace

should be a function in common code, and that the only
target-specific part is selecting a filesystem/namespace.
That is, simplifying, something like:

/* Cause the filesystem to appear as it does to process PID and
   call FUNC with argument ARG, restoring the filesystem to its
   original state afterwards.  Return nonzero if FUNC was called,
   zero otherwise (and set ERRNO). */

int
call_with_fs_of (int pid, void (*func) (void *), void *arg)
{
  int ret;

  target->select_fs (pid);
  ret = func ();
  target->select_fs (0);
  return ret;
}

Was there a reason this wasn't done that way?

(maybe make target->select_fs() return the previous
namespace, instead of passing 0 on the second
call.)

Thanks,
Pedro Alves
  
Gary Benson April 17, 2015, 4:05 p.m. UTC | #2
Pedro Alves wrote:
> On 04/16/2015 01:19 PM, Gary Benson wrote:
> > +/* Handle a "vFile:setfs:" packet.  */
> > +
> > +static void
> > +handle_setfs (char *own_buf)
> > +{
[snip]
> > +}
> 
> This should probably return empty if the_target->call_with_fs_of is
> NULL, to disable the packet in GDB?
> 
> >  int
> >  handle_vFile (char *own_buf, int packet_len, int *new_packet_len)
> >  {
> > -  if (startswith (own_buf, "vFile:open:"))
> > +  if (the_target->call_with_fs_of != NULL
> > +      && startswith (own_buf, "vFile:setfs:"))
> > +    handle_setfs (own_buf);
> > +  else if (startswith (own_buf, "vFile:open:"))
> 
> Ah, I see now that it's handled here.  I'd prefer moving that
> check inside the handle_setfs function instead so that the "setfs"
> specifics handling is all localized.

Ok.

> > +struct open_closure
> > +{
> > +  char filename[HOSTIO_PATH_MAX];
> > +  int flags;
> > +  int mode;
> 
> mode_t mode.
> 
> (please check for the the same issue on the native side.)

It's there in both places.  Is mode_t ok to use in gdbserver?
I don't see it anywhere...

> > +  int return_value;
> > +};
> 
> > +/* Implementation of target_ops->call_with_fs_of.  */
> > +
> > +static int
> > +linux_low_call_with_fs_of (int pid, void (*func) (void *), void *arg)
> > +{
> > +  return linux_ns_enter (pid, LINUX_NS_MNT, func, arg);
> > +}
> > +
> 
> So back on linux_ns_enter's naming ---
> 
> I find the abstraction here a bit odd.  It seems to be me that
> a function that does:
> 
>  #1 - select filesystem/namespace
>  #2 - call foo
>  #3 - restore filesystem/namespace
> 
> should be a function in common code, and that the only
> target-specific part is selecting a filesystem/namespace.
> That is, simplifying, something like:
> 
> /* Cause the filesystem to appear as it does to process PID and
>    call FUNC with argument ARG, restoring the filesystem to its
>    original state afterwards.  Return nonzero if FUNC was called,
>    zero otherwise (and set ERRNO). */
> 
> int
> call_with_fs_of (int pid, void (*func) (void *), void *arg)
> {
>   int ret;
> 
>   target->select_fs (pid);
>   ret = func ();
>   target->select_fs (0);
>   return ret;
> }
> 
> Was there a reason this wasn't done that way?
> 
> (maybe make target->select_fs() return the previous
> namespace, instead of passing 0 on the second
> call.)

I'll have a go at doing it that way.

Thanks,
Gary
  
Gary Benson April 17, 2015, 4:29 p.m. UTC | #3
Gary Benson wrote:
> Pedro Alves wrote:
> > So back on linux_ns_enter's naming ---
> > 
> > I find the abstraction here a bit odd.  It seems to be me that
> > a function that does:
> > 
> >  #1 - select filesystem/namespace
> >  #2 - call foo
> >  #3 - restore filesystem/namespace
> > 
> > should be a function in common code, and that the only
> > target-specific part is selecting a filesystem/namespace.
> > That is, simplifying, something like:
> > 
> > /* Cause the filesystem to appear as it does to process PID and
> >    call FUNC with argument ARG, restoring the filesystem to its
> >    original state afterwards.  Return nonzero if FUNC was called,
> >    zero otherwise (and set ERRNO). */
> > 
> > int
> > call_with_fs_of (int pid, void (*func) (void *), void *arg)
> > {
> >   int ret;
> > 
> >   target->select_fs (pid);
> >   ret = func ();
> >   target->select_fs (0);
> >   return ret;
> > }
> > 
> > Was there a reason this wasn't done that way?
> > 
> > (maybe make target->select_fs() return the previous
> > namespace, instead of passing 0 on the second
> > call.)
> 
> I'll have a go at doing it that way.

It is kind of uglier doing it that way.  It would need to look
something more like this:

  struct cleanup **old_chain;

  if (target->select_fs (pid, &old_chain) != 0)
     return failure_return_value;
  ret = func ();
  do_cleanups (old_chain);
  return ret;

linux_ns_enter has to open file descriptors for its own namespace
and the one it wants to enter.  Its own namespace file descriptor
is what it needs to return, and it has to be held open: you can't
flip namespace and then open your own descriptor to flip back.
So the thing target->select_fs would have to return would be an
open file descriptor (to then pass to target->restore_fs, which
would take a file descriptor argument rather than an PID) but
that would be putting a Linuxism into the target vector.

Cheers,
Gary
  
Pedro Alves April 17, 2015, 5:09 p.m. UTC | #4
On 04/17/2015 05:29 PM, Gary Benson wrote:
> Gary Benson wrote:
>> Pedro Alves wrote:
>>> So back on linux_ns_enter's naming ---
>>>
>>> I find the abstraction here a bit odd.  It seems to be me that
>>> a function that does:
>>>
>>>  #1 - select filesystem/namespace
>>>  #2 - call foo
>>>  #3 - restore filesystem/namespace
>>>
>>> should be a function in common code, and that the only
>>> target-specific part is selecting a filesystem/namespace.
>>> That is, simplifying, something like:
>>>
>>> /* Cause the filesystem to appear as it does to process PID and
>>>    call FUNC with argument ARG, restoring the filesystem to its
>>>    original state afterwards.  Return nonzero if FUNC was called,
>>>    zero otherwise (and set ERRNO). */
>>>
>>> int
>>> call_with_fs_of (int pid, void (*func) (void *), void *arg)
>>> {
>>>   int ret;
>>>
>>>   target->select_fs (pid);
>>>   ret = func ();
>>>   target->select_fs (0);
>>>   return ret;
>>> }
>>>
>>> Was there a reason this wasn't done that way?
>>>
>>> (maybe make target->select_fs() return the previous
>>> namespace, instead of passing 0 on the second
>>> call.)
>>
>> I'll have a go at doing it that way.
> 
> It is kind of uglier doing it that way.  It would need to look
> something more like this:
> 
>   struct cleanup **old_chain;
> 
>   if (target->select_fs (pid, &old_chain) != 0)
>      return failure_return_value;
>   ret = func ();
>   do_cleanups (old_chain);
>   return ret;
> 
> linux_ns_enter has to open file descriptors for its own namespace
> and the one it wants to enter.  Its own namespace file descriptor
> is what it needs to return, and it has to be held open: you can't
> flip namespace and then open your own descriptor to flip back.
> So the thing target->select_fs would have to return would be an
> open file descriptor (to then pass to target->restore_fs, which
> would take a file descriptor argument rather than an PID) but
> that would be putting a Linuxism into the target vector.

Seems to me we can fix that by returning an opaque closure
instead then.  I don't think that's ugly at all.  E.g.,:

int
call_with_fs_of (int pid, void (*func) (void *), void *arg)
{
  int ret = failure_return_value;

   restore_token = target->select_fs (pid);
   if (restore_token == NULL)
     return failure_return_value;
   TRY
     {
       ret = func ();
     }
   CATCH (ex, RETURN_MASK_ALL)
     {
       target->restore_fs (restore_token);
       throw_exception (ex);
     }
   END_CATCH
   return ret;
}

The reason I think this target interface is better is that
it also allows open coding the select_fs/restore_fs calls.
Which in turn would allow getting rid of all those closures in
the other patch.  For example, like this:

/* On exception, restore the filesystem, and rethrow.  */
#define CATCH_RESTORE_FS(restore_token) \
   CATCH (ex, RETURN_MASK_ALL) \
     { \
       linux_restore_namespace (restore_token); \
       throw_exception (ex); \
     } \
   END_CATCH

static int
linux_nat_fileio_unlink (struct target_ops *ops,
		         const char *filename, int *target_errno)
{
  void *restore_token;
  int ret = -1;

  restore_token = linux_nat_enter_fs ();

  TRY
    {
      ret = super_fileio_unlink (ops, filename, target_errno);
    }
  CATCH_RESTORE_FS(restore_token)

  return ret;
}

static char *
linux_nat_fileio_readlink (struct target_ops *ops,
			   const char *filename, int *target_errno)
{
  void *restore_token;
  char *ret = NULL;

  restore_token = linux_nat_enter_fs ();

  TRY
    {
      ret = super_fileio_readlink (ops, filename, target_errno);
    }
  CATCH_RESTORE_FS(restore_token)

  return ret;
}


etc., which I find _much_ more readable than:


+/* Arguments and return value of to_fileio_unlink.  */
+
+struct unlink_closure
+{
+  struct target_ops *ops;
+  const char *filename;
+  int *target_errno;
+  int return_value;
+};
+
+/* Helper for linux_nat_fileio_unlink.  */
+
+static void
+linux_nat_fileio_unlink_1 (void *arg)
+{
+  struct unlink_closure *clo = (struct unlink_closure *) arg;
+
+  clo->return_value = super_fileio_unlink (clo->ops, clo->filename,
+					   clo->target_errno);
+}
+
+/* Implementation of to_fileio_unlink.  */
+
+static int
+linux_nat_fileio_unlink (struct target_ops *ops,
+			   const char *filename, int *target_errno)
+{
+  struct unlink_closure clo =
+    {
+      /* Arguments.  */
+      ops, filename, target_errno,
+
+      /* Failure return value.  */
+      -1
+    };
+
+  linux_nat_enter_fs (linux_nat_fileio_unlink_1, &clo, target_errno);
+
+  return clo.return_value;
+}
+
+/* Arguments and return value of to_fileio_readlink.  */
+
+struct readlink_closure
+{
+  struct target_ops *ops;
+  const char *filename;
+  int *target_errno;
+  char *return_value;
+};
+
+/* Helper for linux_nat_fileio_readlink.  */
+
+static void
+linux_nat_fileio_readlink_1 (void *arg)
+{
+  struct readlink_closure *clo = (struct readlink_closure *) arg;
+
+  clo->return_value = super_fileio_readlink (clo->ops, clo->filename,
+					     clo->target_errno);
+}
+
+/* Implementation of to_fileio_readlink.  */
+
+static char *
+linux_nat_fileio_readlink (struct target_ops *ops,
+			   const char *filename, int *target_errno)
+{
+  struct readlink_closure clo =
+    {
+      /* Arguments.  */
+      ops, filename, target_errno,
+
+      /* Failure return value.  */
+      NULL
+    };
+
+  linux_nat_enter_fs (linux_nat_fileio_readlink_1, &clo, target_errno);
+
+  return clo.return_value;
+}

Thanks,
Pedro Alves
  

Patch

diff --git a/gdb/gdbserver/hostio.c b/gdb/gdbserver/hostio.c
index b03b5ad..9a1e443 100644
--- a/gdb/gdbserver/hostio.c
+++ b/gdb/gdbserver/hostio.c
@@ -243,6 +243,51 @@  hostio_reply_with_data (char *own_buf, char *buffer, int len,
   return input_index;
 }
 
+/* Process ID of inferior whose filesystem hostio functions
+   that take FILENAME arguments will use.  Zero means to use
+   our own filesystem.  */
+
+static int hostio_fs_pid = 0;
+
+/* Handle a "vFile:setfs:" packet.  */
+
+static void
+handle_setfs (char *own_buf)
+{
+  char *p;
+  int pid;
+
+  p = own_buf + strlen ("vFile:setfs:");
+
+  if (require_int (&p, &pid)
+      || pid < 0
+      || require_end (p))
+    {
+      hostio_packet_error (own_buf);
+      return;
+    }
+
+  hostio_fs_pid = pid;
+
+  hostio_reply (own_buf, 0);
+}
+
+/* Cause the filesystem to appear as it does to the process specified
+   by the most recent valid "vFile:setfs:" packet and call FUNC with
+   argument ARG, restoring the filesystem to its original state
+   afterwards.  Return nonzero if FUNC was called, zero otherwise (and
+   set ERRNO). */
+
+static int
+enter_filesystem (void (*func) (void *), void *arg)
+{
+  if (hostio_fs_pid != 0 && the_target->call_with_fs_of != NULL)
+    return the_target->call_with_fs_of (hostio_fs_pid, func, arg);
+
+  func (arg);
+  return 1;
+}
+
 static int
 fileio_open_flags_to_host (int fileio_open_flags, int *open_flags_p)
 {
@@ -275,23 +320,45 @@  fileio_open_flags_to_host (int fileio_open_flags, int *open_flags_p)
   return 0;
 }
 
+/* Arguments and return value of open(2).  */
+
+struct open_closure
+{
+  char filename[HOSTIO_PATH_MAX];
+  int flags;
+  int mode;
+  int return_value;
+};
+
+/* Helper for handle_open.  */
+
+static void
+handle_open_1 (void *arg)
+{
+  struct open_closure *clo = (struct open_closure *) arg;
+
+  clo->return_value = open (clo->filename, clo->flags, clo->mode);
+}
+
+/* Handle a "vFile:open:" packet.  */
+
 static void
 handle_open (char *own_buf)
 {
-  char filename[HOSTIO_PATH_MAX];
   char *p;
-  int fileio_flags, mode, flags, fd;
+  int fileio_flags, fd;
   struct fd_list *new_fd;
+  struct open_closure clo;
 
   p = own_buf + strlen ("vFile:open:");
 
-  if (require_filename (&p, filename)
+  if (require_filename (&p, clo.filename)
       || require_comma (&p)
       || require_int (&p, &fileio_flags)
       || require_comma (&p)
-      || require_int (&p, &mode)
+      || require_int (&p, &clo.mode)
       || require_end (p)
-      || fileio_open_flags_to_host (fileio_flags, &flags))
+      || fileio_open_flags_to_host (fileio_flags, &clo.flags))
     {
       hostio_packet_error (own_buf);
       return;
@@ -299,7 +366,11 @@  handle_open (char *own_buf)
 
   /* We do not need to convert MODE, since the fileio protocol
      uses the standard values.  */
-  fd = open (filename, flags, mode);
+
+  if (enter_filesystem (handle_open_1, &clo))
+    fd = clo.return_value;
+  else
+    fd = -1;
 
   if (fd == -1)
     {
@@ -487,23 +558,46 @@  handle_close (char *own_buf)
   hostio_reply (own_buf, ret);
 }
 
+/* Arguments and return value of unlink(2).  */
+
+struct unlink_closure
+{
+  char filename[HOSTIO_PATH_MAX];
+  int return_value;
+};
+
+/* Helper for handle_unlink.  */
+
+static void
+handle_unlink_1 (void *arg)
+{
+  struct unlink_closure *clo = (struct unlink_closure *) arg;
+
+  clo->return_value = unlink (clo->filename);
+}
+
+/* Handle a "vFile:unlink:" packet.  */
+
 static void
 handle_unlink (char *own_buf)
 {
-  char filename[HOSTIO_PATH_MAX];
+  struct unlink_closure clo;
   char *p;
   int ret;
 
   p = own_buf + strlen ("vFile:unlink:");
 
-  if (require_filename (&p, filename)
+  if (require_filename (&p, clo.filename)
       || require_end (p))
     {
       hostio_packet_error (own_buf);
       return;
     }
 
-  ret = unlink (filename);
+  if (enter_filesystem (handle_unlink_1, &clo))
+    ret = clo.return_value;
+  else
+    ret = -1;
 
   if (ret == -1)
     {
@@ -514,30 +608,57 @@  handle_unlink (char *own_buf)
   hostio_reply (own_buf, ret);
 }
 
+/* Arguments and return value of readlink(2).  */
+
+struct readlink_closure
+{
+  char filename[HOSTIO_PATH_MAX];
+  char linkname[HOSTIO_PATH_MAX];
+  int return_value;
+};
+
+/* Helper for handle_readlink.  */
+
+static void
+handle_readlink_1 (void *arg)
+{
+  struct readlink_closure *clo = (struct readlink_closure *) arg;
+
+  clo->return_value = readlink (clo->filename, clo->linkname,
+				sizeof (clo->linkname) - 1);
+}
+
+/* Handle a "vFile:readlink:" packet.  */
+
 static void
 handle_readlink (char *own_buf, int *new_packet_len)
 {
-  char filename[HOSTIO_PATH_MAX], linkname[HOSTIO_PATH_MAX];
+  struct readlink_closure clo;
   char *p;
   int ret, bytes_sent;
 
   p = own_buf + strlen ("vFile:readlink:");
 
-  if (require_filename (&p, filename)
+  if (require_filename (&p, clo.filename)
       || require_end (p))
     {
       hostio_packet_error (own_buf);
       return;
     }
 
-  ret = readlink (filename, linkname, sizeof (linkname) - 1);
+  if (enter_filesystem (handle_readlink_1, &clo))
+    ret = clo.return_value;
+  else
+    ret = -1;
+
   if (ret == -1)
     {
       hostio_error (own_buf);
       return;
     }
 
-  bytes_sent = hostio_reply_with_data (own_buf, linkname, ret, new_packet_len);
+  bytes_sent = hostio_reply_with_data (own_buf, clo.linkname,
+				       ret, new_packet_len);
 
   /* If the response does not fit into a single packet, do not attempt
      to return a partial response, but simply fail.  */
@@ -550,7 +671,10 @@  handle_readlink (char *own_buf, int *new_packet_len)
 int
 handle_vFile (char *own_buf, int packet_len, int *new_packet_len)
 {
-  if (startswith (own_buf, "vFile:open:"))
+  if (the_target->call_with_fs_of != NULL
+      && startswith (own_buf, "vFile:setfs:"))
+    handle_setfs (own_buf);
+  else if (startswith (own_buf, "vFile:open:"))
     handle_open (own_buf);
   else if (startswith (own_buf, "vFile:pread:"))
     handle_pread (own_buf, new_packet_len);
diff --git a/gdb/gdbserver/linux-low.c b/gdb/gdbserver/linux-low.c
index 74f754d..4f01dbd 100644
--- a/gdb/gdbserver/linux-low.c
+++ b/gdb/gdbserver/linux-low.c
@@ -51,6 +51,7 @@ 
    definition of elf_fpregset_t.  */
 #include <elf.h>
 #endif
+#include "nat/linux-namespaces.h"
 
 #ifndef SPUFS_MAGIC
 #define SPUFS_MAGIC 0x23c9b64e
@@ -6349,6 +6350,14 @@  current_lwp_ptid (void)
   return ptid_of (current_thread);
 }
 
+/* Implementation of target_ops->call_with_fs_of.  */
+
+static int
+linux_low_call_with_fs_of (int pid, void (*func) (void *), void *arg)
+{
+  return linux_ns_enter (pid, LINUX_NS_MNT, func, arg);
+}
+
 static struct target_ops linux_target_ops = {
   linux_create_inferior,
   linux_attach,
@@ -6434,6 +6443,7 @@  static struct target_ops linux_target_ops = {
 #endif
   linux_supports_range_stepping,
   linux_proc_pid_to_exec_file,
+  linux_low_call_with_fs_of,
 };
 
 static void
diff --git a/gdb/gdbserver/target.h b/gdb/gdbserver/target.h
index 6280c26..c766a2c 100644
--- a/gdb/gdbserver/target.h
+++ b/gdb/gdbserver/target.h
@@ -402,6 +402,12 @@  struct target_ops
      string should be copied into a buffer by the client if the string
      will not be immediately used, or if it must persist.  */
   char *(*pid_to_exec_file) (int pid);
+
+  /* Cause the filesystem to appear as it does to process PID and
+     call FUNC with argument ARG, restoring the filesystem to its
+     original state afterwards.  Return nonzero if FUNC was called,
+     zero otherwise (and set ERRNO). */
+  int (*call_with_fs_of) (int pid, void (*func) (void *), void *arg);
 };
 
 extern struct target_ops *the_target;