[PATCH 0/3] binutils: read from stdin if input file is -

classic Classic list List threaded Threaded
7 messages Options
Reply | Threaded
Open this post in threaded view
|

[PATCH 0/3] binutils: read from stdin if input file is -

Ahmad Fatoum
Changes behavior of dlltool, nlmconv, nm, objcopy, objdump and size
to allow niftiness like:

    printf "\xCC" | objdump -D -bbinary -mi386 -

Affects the layout of struct bfd as well. Is this ok?
Decided against adding an .is_stdin bit, because the thought of
inadvertently deleting files due to mismatched libbfd was unpleasant

Copyright assignment is in place. `make -C binutils check` runs through.

--

bfd/
        * bfd.c (struct bfd): Add optional temp_filename field that points
        at file to be removed when closing bfd.
        * bfd-in2.h: Regenerate.
        * opencls.c (bfd_open): read stdin if filename == NULL
binutils/
        * nm.c:      const-qualify read-only string parameters
    * nm.c:      read from stdin if input file is -
    * dlltool.c: read from stdin if input file is -
    * nlmconv.c: read from stdin if input file is -
    * objcopy.c: read from stdin if input file is -
    * objdump.c: read from stdin if input file is -
    * size.c:    read from stdin if input file is -
 

Ahmad Fatoum (3):
  bfd_fopen: read from stdin if filename == NULL
  nm: const-qualify read-only string parameters
  binutils: read from stdin if input file is -

 bfd/bfd-in2.h      |  3 +++
 bfd/bfd.c          |  3 +++
 bfd/opncls.c       | 66 ++++++++++++++++++++++++++++++++++++++++++++++++------
 binutils/dlltool.c |  9 +++++++-
 binutils/nlmconv.c |  5 ++++-
 binutils/nm.c      | 56 +++++++++++++++++++++++++--------------------
 binutils/objcopy.c | 38 ++++++++++++++++++++++++-------
 binutils/objdump.c | 10 +++++++--
 binutils/size.c    | 10 +++++++--
 9 files changed, 155 insertions(+), 45 deletions(-)

--
2.16.1

Reply | Threaded
Open this post in threaded view
|

[PATCH 1/3] bfd_fopen: read from stdin if filename == NULL

Ahmad Fatoum
This is done by slurping stdin into a temporary file.
---
 bfd/bfd-in2.h |  3 +++
 bfd/bfd.c     |  3 +++
 bfd/opncls.c  | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++-------
 3 files changed, 65 insertions(+), 7 deletions(-)

diff --git a/bfd/bfd-in2.h b/bfd/bfd-in2.h
index 42991e7848..597c2226bc 100644
--- a/bfd/bfd-in2.h
+++ b/bfd/bfd-in2.h
@@ -6769,6 +6769,9 @@ struct bfd
   /* The filename the application opened the BFD with.  */
   const char *filename;
 
+  /* If non-NULL, name of a temporary file that can be removed on destruction.  */
+  const char *temp_filename;
+
   /* A pointer to the target jump table.  */
   const struct bfd_target *xvec;
 
diff --git a/bfd/bfd.c b/bfd/bfd.c
index 985c825c69..8fe07468e5 100644
--- a/bfd/bfd.c
+++ b/bfd/bfd.c
@@ -62,6 +62,9 @@ CODE_FRAGMENT
 .  {* The filename the application opened the BFD with.  *}
 .  const char *filename;
 .
+.  {* If non-NULL, name of a temporary file that can be removed on destruction.  *}
+.  const char *temp_filename;
+.
 .  {* A pointer to the target jump table.  *}
 .  const struct bfd_target *xvec;
 .
diff --git a/bfd/opncls.c b/bfd/opncls.c
index 16b568c8ab..5861cc2bd8 100644
--- a/bfd/opncls.c
+++ b/bfd/opncls.c
@@ -63,6 +63,8 @@ _bfd_new_bfd (void)
   if (nbfd == NULL)
     return NULL;
 
+  nbfd->temp_filename = NULL;
+
   if (bfd_use_reserved_id)
     {
       nbfd->id = --bfd_reserved_id_counter;
@@ -128,6 +130,11 @@ _bfd_delete_bfd (bfd *abfd)
 
   if (abfd->filename)
     free ((char *) abfd->filename);
+  if (abfd->temp_filename)
+    {
+      remove (abfd->temp_filename);
+      XDELETEVEC ((char *) abfd->temp_filename);
+    }
   free (abfd->arelt_data);
   free (abfd);
 }
@@ -173,8 +180,9 @@ DESCRIPTION
  Open the file @var{filename} with the target @var{target}.
  Return a pointer to the created BFD.  If @var{fd} is not -1,
  then <<fdopen>> is used to open the file; otherwise, <<fopen>>
- is used.  @var{mode} is passed directly to <<fopen>> or
- <<fdopen>>.
+ is used. If @var{filename} is <<NULL>>, standard input is read
+ into a temporary file which is then opened.
+ @var{mode} is passed directly to <<fopen>> or <<fdopen>>.
 
  Calls <<bfd_find_target>>, so @var{target} is interpreted as by
  that function.
@@ -216,12 +224,53 @@ bfd_fopen (const char *filename, const char *target, const char *mode, int fd)
 
 #ifdef HAVE_FDOPEN
   if (fd != -1)
-    nbfd->iostream = fdopen (fd, mode);
+    {
+      nbfd->iostream = fdopen (fd, mode);
+    }
   else
 #endif
-    nbfd->iostream = _bfd_real_fopen (filename, mode);
+    {
+      if (filename)
+        {
+          nbfd->iostream = _bfd_real_fopen (filename, mode);
+        }
+      else /* slurp in stdin */
+        {
+          FILE *fp;
+          size_t nbytes;
+          char buffer[256];
+          nbfd->temp_filename = make_temp_file (NULL);
+          fp = _bfd_real_fopen (nbfd->temp_filename, "w");
+          if (!fp)
+            goto fopen_fail;
+
+          while ((nbytes = fread (buffer, 1, sizeof (buffer), stdin)) > 0)
+            {
+              if (fwrite (buffer, 1, nbytes, fp) != nbytes)
+                break;
+            }
+
+          if (ferror (fp) || ferror (stdin))
+            {
+              bfd_set_input_error (nbfd, bfd_error_on_input);
+              fclose (fp);
+              _bfd_delete_bfd (nbfd);
+              return NULL;
+            }
+
+          filename = "<stdin>";
+          nbfd->iostream = _bfd_real_fopen (nbfd->temp_filename, mode);
+          fclose (fp);
+          if (remove (nbfd->temp_filename) == 0)
+            {
+              XDELETEVEC ((char *) nbfd->temp_filename);
+              nbfd->temp_filename = NULL;
+            }
+        }
+    }
   if (nbfd->iostream == NULL)
     {
+     fopen_fail:
       bfd_set_error (bfd_error_system_call);
       _bfd_delete_bfd (nbfd);
       return NULL;
@@ -269,7 +318,9 @@ SYNOPSIS
 
 DESCRIPTION
  Open the file @var{filename} (using <<fopen>>) with the target
- @var{target}.  Return a pointer to the created BFD.
+ @var{target}. If @var{filename} is <<NULL>>, standard input is
+ read into a temporary file which is then openend.
+ Return a pointer to the created BFD.
 
  Calls <<bfd_find_target>>, so @var{target} is interpreted as by
  that function.
@@ -304,8 +355,9 @@ SYNOPSIS
 
 DESCRIPTION
  <<bfd_fdopenr>> is to <<bfd_fopenr>> much like <<fdopen>> is to
- <<fopen>>.  It opens a BFD on a file already described by the
- @var{fd} supplied.
+ <<fopen>>.  If @var{filename} is <<NULL>>, standard input is
+ read into a temporary file which is then openend.
+ It opens a BFD on a file already described by the @var{fd} supplied.
 
  When the file is later <<bfd_close>>d, the file descriptor will
  be closed.  If the caller desires that this file descriptor be
--
2.16.1

Reply | Threaded
Open this post in threaded view
|

[PATCH 2/3] nm: const-qualify read-only string parameters

Ahmad Fatoum
In reply to this post by Ahmad Fatoum
---
 binutils/nm.c | 42 +++++++++++++++++++++---------------------
 1 file changed, 21 insertions(+), 21 deletions(-)

diff --git a/binutils/nm.c b/binutils/nm.c
index e46fffc796..696eb7817e 100644
--- a/binutils/nm.c
+++ b/binutils/nm.c
@@ -77,15 +77,15 @@ struct extended_symbol_info
   (sym->elfinfo ? sym->elfinfo->internal_elf_sym.st_size: sym->ssize)
 
 /* The output formatting functions.  */
-static void print_object_filename_bsd (char *);
-static void print_object_filename_sysv (char *);
-static void print_object_filename_posix (char *);
-static void print_archive_filename_bsd (char *);
-static void print_archive_filename_sysv (char *);
-static void print_archive_filename_posix (char *);
-static void print_archive_member_bsd (char *, const char *);
-static void print_archive_member_sysv (char *, const char *);
-static void print_archive_member_posix (char *, const char *);
+static void print_object_filename_bsd (const char *);
+static void print_object_filename_sysv (const char *);
+static void print_object_filename_posix (const char *);
+static void print_archive_filename_bsd (const char *);
+static void print_archive_filename_sysv (const char *);
+static void print_archive_filename_posix (const char *);
+static void print_archive_member_bsd (const char *, const char *);
+static void print_archive_member_sysv (const char *, const char *);
+static void print_archive_member_posix (const char *, const char *);
 static void print_symbol_filename_bsd (bfd *, bfd *);
 static void print_symbol_filename_sysv (bfd *, bfd *);
 static void print_symbol_filename_posix (bfd *, bfd *);
@@ -98,13 +98,13 @@ static void print_symbol_info_posix (struct extended_symbol_info *, bfd *);
 struct output_fns
   {
     /* Print the name of an object file given on the command line.  */
-    void (*print_object_filename) (char *);
+    void (*print_object_filename) (const char *);
 
     /* Print the name of an archive file given on the command line.  */
-    void (*print_archive_filename) (char *);
+    void (*print_archive_filename) (const char *);
 
     /* Print the name of an archive member file.  */
-    void (*print_archive_member) (char *, const char *);
+    void (*print_archive_member) (const char *, const char *);
 
     /* Print the name of the file (and archive, if there is one)
        containing a symbol.  */
@@ -1353,14 +1353,14 @@ display_file (char *filename)
 /* Print the name of an object file given on the command line.  */
 
 static void
-print_object_filename_bsd (char *filename)
+print_object_filename_bsd (const char *filename)
 {
   if (filename_per_file && !filename_per_symbol)
     printf ("\n%s:\n", filename);
 }
 
 static void
-print_object_filename_sysv (char *filename)
+print_object_filename_sysv (const char *filename)
 {
   if (undefined_only)
     printf (_("\n\nUndefined symbols from %s:\n\n"), filename);
@@ -1375,7 +1375,7 @@ Name                  Value           Class        Type         Size
 }
 
 static void
-print_object_filename_posix (char *filename)
+print_object_filename_posix (const char *filename)
 {
   if (filename_per_file && !filename_per_symbol)
     printf ("%s:\n", filename);
@@ -1384,26 +1384,26 @@ print_object_filename_posix (char *filename)
 /* Print the name of an archive file given on the command line.  */
 
 static void
-print_archive_filename_bsd (char *filename)
+print_archive_filename_bsd (const char *filename)
 {
   if (filename_per_file)
     printf ("\n%s:\n", filename);
 }
 
 static void
-print_archive_filename_sysv (char *filename ATTRIBUTE_UNUSED)
+print_archive_filename_sysv (const char *filename ATTRIBUTE_UNUSED)
 {
 }
 
 static void
-print_archive_filename_posix (char *filename ATTRIBUTE_UNUSED)
+print_archive_filename_posix (const char *filename ATTRIBUTE_UNUSED)
 {
 }
 
 /* Print the name of an archive member file.  */
 
 static void
-print_archive_member_bsd (char *archive ATTRIBUTE_UNUSED,
+print_archive_member_bsd (const char *archive ATTRIBUTE_UNUSED,
   const char *filename)
 {
   if (!filename_per_symbol)
@@ -1411,7 +1411,7 @@ print_archive_member_bsd (char *archive ATTRIBUTE_UNUSED,
 }
 
 static void
-print_archive_member_sysv (char *archive, const char *filename)
+print_archive_member_sysv (const char *archive, const char *filename)
 {
   if (undefined_only)
     printf (_("\n\nUndefined symbols from %s[%s]:\n\n"), archive, filename);
@@ -1426,7 +1426,7 @@ Name                  Value           Class        Type         Size
 }
 
 static void
-print_archive_member_posix (char *archive, const char *filename)
+print_archive_member_posix (const char *archive, const char *filename)
 {
   if (!filename_per_symbol)
     printf ("%s[%s]:\n", archive, filename);
--
2.16.1

Reply | Threaded
Open this post in threaded view
|

[PATCH 3/3] binutils: read from stdin if input file is -

Ahmad Fatoum
In reply to this post by Ahmad Fatoum
This changes the behavior of:
dlltool, nlmconv, nm, objcopy, objdump and size

Opening files named - now requires prefixing ./ or similar.
---
 binutils/dlltool.c |  9 ++++++++-
 binutils/nlmconv.c |  5 ++++-
 binutils/nm.c      | 14 +++++++++++---
 binutils/objcopy.c | 38 ++++++++++++++++++++++++++++++--------
 binutils/objdump.c | 10 ++++++++--
 binutils/size.c    | 10 ++++++++--
 6 files changed, 69 insertions(+), 17 deletions(-)

diff --git a/binutils/dlltool.c b/binutils/dlltool.c
index 189907a0b0..37d244e0e7 100644
--- a/binutils/dlltool.c
+++ b/binutils/dlltool.c
@@ -1679,7 +1679,14 @@ scan_open_obj_file (bfd *abfd)
 static void
 scan_obj_file (const char *filename)
 {
-  bfd * f = bfd_openr (filename, 0);
+  bfd *f;
+  const char *filename_bfd = filename;
+  if (strcmp (filename_bfd, "-") == 0)
+    {
+      filename_bfd = NULL;
+      filename = "<stdin>";
+    }
+  f = bfd_openr (filename_bfd, 0);
 
   if (!f)
     /* xgettext:c-format */
diff --git a/binutils/nlmconv.c b/binutils/nlmconv.c
index 68941f80d0..ad9f04d1dd 100644
--- a/binutils/nlmconv.c
+++ b/binutils/nlmconv.c
@@ -199,6 +199,7 @@ main (int argc, char **argv)
   int len;
   char *modname;
   char **matching;
+  bfd_boolean read_stdin = FALSE;
 
 #if defined (HAVE_SETLOCALE) && defined (HAVE_LC_MESSAGES)
   setlocale (LC_MESSAGES, "");
@@ -261,6 +262,8 @@ main (int argc, char **argv)
     {
       input_file = argv[optind];
       ++optind;
+      if (strcmp (input_file, "-") == 0)
+        read_stdin = TRUE;
       if (optind < argc)
  {
   output_file = argv[optind];
@@ -329,7 +332,7 @@ main (int argc, char **argv)
       show_usage (stderr, 1);
     }
 
-  inbfd = bfd_openr (input_file, input_format);
+  inbfd = bfd_openr (read_stdin ? NULL : input_file, input_format);
   if (inbfd == NULL)
     bfd_fatal (input_file);
 
diff --git a/binutils/nm.c b/binutils/nm.c
index 696eb7817e..77739a42f7 100644
--- a/binutils/nm.c
+++ b/binutils/nm.c
@@ -1299,11 +1299,19 @@ display_file (char *filename)
   bfd_boolean retval = TRUE;
   bfd *file;
   char **matching;
+  const char *filename_bfd = filename;
 
-  if (get_file_size (filename) < 1)
-    return FALSE;
+  if (strcmp (filename_bfd, "-") == 0)
+    {
+      filename_bfd= NULL;
+      filename = "<stdin>";
+    }
+  else if (get_file_size (filename) < 1)
+    {
+      return FALSE;
+    }
 
-  file = bfd_openr (filename, target ? target : plugin_target);
+  file = bfd_openr (filename_bfd, target ? target : plugin_target);
   if (file == NULL)
     {
       bfd_nonfatal (filename);
diff --git a/binutils/objcopy.c b/binutils/objcopy.c
index 8cdf27a87e..a7e5405646 100644
--- a/binutils/objcopy.c
+++ b/binutils/objcopy.c
@@ -3420,9 +3420,15 @@ copy_file (const char *input_filename, const char *output_filename,
   bfd *ibfd;
   char **obj_matching;
   char **core_matching;
-  off_t size = get_file_size (input_filename);
+  off_t size;
+  const char *filename_bfd = input_filename;
 
-  if (size < 1)
+  if (strcmp (filename_bfd, "-") == 0)
+    {
+        filename_bfd = NULL;
+        input_filename = "<stdin>";
+    }
+  else if ((size = get_file_size (input_filename)) < 1)
     {
       if (size == 0)
  non_fatal (_("error: the input file '%s' is empty"),
@@ -3433,7 +3439,7 @@ copy_file (const char *input_filename, const char *output_filename,
 
   /* To allow us to do "strip *" without dying on the first
      non-object file, failures are nonfatal.  */
-  ibfd = bfd_openr (input_filename, input_target);
+  ibfd = bfd_openr (filename_bfd, input_target);
   if (ibfd == NULL)
     {
       bfd_nonfatal_message (input_filename, NULL, NULL, NULL);
@@ -4685,6 +4691,7 @@ copy_main (int argc, char *argv[])
   bfd_boolean show_version = FALSE;
   bfd_boolean change_warn = TRUE;
   bfd_boolean formats_info = FALSE;
+  bfd_boolean read_stdin = FALSE;
   int c;
   struct stat statbuf;
   const bfd_arch_info_type *input_arch = NULL;
@@ -5393,6 +5400,8 @@ copy_main (int argc, char *argv[])
     copy_usage (stderr, 1);
 
   input_filename = argv[optind];
+  if (strcmp (input_filename, "-") == 0)
+      read_stdin = TRUE;
   if (optind + 1 < argc)
     output_filename = argv[optind + 1];
 
@@ -5459,9 +5468,13 @@ copy_main (int argc, char *argv[])
     }
 
   if (preserve_dates)
-    if (stat (input_filename, & statbuf) < 0)
-      fatal (_("warning: could not locate '%s'.  System error message: %s"),
-     input_filename, strerror (errno));
+    {
+      if (read_stdin)
+        fatal (_("warning: can't preserve date of input: <stdin>"));
+      if (stat (input_filename, & statbuf) < 0)
+        fatal (_("warning: could not locate '%s'.  System error message: %s"),
+               input_filename, strerror (errno));
+    }
 
   /* If there is no destination file, or the source and destination files
      are the same, then create a temp and rename the result into the input.  */
@@ -5481,8 +5494,17 @@ copy_main (int argc, char *argv[])
       if (preserve_dates)
  set_times (tmpname, &statbuf);
       if (tmpname != output_filename)
- status = (smart_rename (tmpname, input_filename,
- preserve_dates) != 0);
+        {
+          if (read_stdin)
+            {
+               /* Might be nifty to dump the object to stdout if no destination file was
+                * specified. This requires ensuring nothing else writes to stdout though */
+               unlink_if_ordinary (tmpname);
+               fatal (_("error: source file <stdin> can't be reused as destination file"));
+            }
+
+          status = (smart_rename (tmpname, input_filename, preserve_dates) != 0);
+       }
     }
   else
     unlink_if_ordinary (tmpname);
diff --git a/binutils/objdump.c b/binutils/objdump.c
index 37a9f0d2e1..f3797e420b 100644
--- a/binutils/objdump.c
+++ b/binutils/objdump.c
@@ -3758,14 +3758,20 @@ static void
 display_file (char *filename, char *target, bfd_boolean last_file)
 {
   bfd *file;
+  const char *filename_bfd = filename;
 
-  if (get_file_size (filename) < 1)
+  if (strcmp (filename_bfd, "-") == 0)
+    {
+      filename_bfd = NULL;
+      filename = "<stdin>";
+    }
+  else if (get_file_size (filename) < 1)
     {
       exit_status = 1;
       return;
     }
 
-  file = bfd_openr (filename, target);
+  file = bfd_openr (filename_bfd, target);
   if (file == NULL)
     {
       nonfatal (filename);
diff --git a/binutils/size.c b/binutils/size.c
index 47f14fce33..82080d8bee 100644
--- a/binutils/size.c
+++ b/binutils/size.c
@@ -385,14 +385,20 @@ static void
 display_file (char *filename)
 {
   bfd *file;
+  const char *filename_bfd = filename;
 
-  if (get_file_size (filename) < 1)
+  if (strcmp (filename_bfd, "-") == 0)
+    {
+      filename_bfd = NULL;
+      filename = "<stdin>";
+    }
+  else if (get_file_size (filename) < 1)
     {
       return_code = 1;
       return;
     }
 
-  file = bfd_openr (filename, target);
+  file = bfd_openr (filename_bfd, target);
   if (file == NULL)
     {
       bfd_nonfatal (filename);
--
2.16.1

Reply | Threaded
Open this post in threaded view
|

Re: [PATCH 0/3] binutils: read from stdin if input file is -

Nick Clifton
In reply to this post by Ahmad Fatoum
Hi Ahmad,

> Changes behavior of dlltool, nlmconv, nm, objcopy, objdump and size
> to allow niftiness like:
>
>     printf "\xCC" | objdump -D -bbinary -mi386 -
>
> Affects the layout of struct bfd as well. Is this ok?

Yes, but I would like to request a couple of additions:

  * Please add a patch to the binutils/NEWS file to mention
    this new behaviour.

  * Please update the binutils/doc/binutils.texi file to
    document the new behaviour.

  * It would be really nice if you could create a new test
    (or two) in the binutils testsuite to verify that the
    new behaviour works.  I am not sure how difficult this
    will be however.

One other thing.  In patch 1:

+      XDELETEVEC ((char *) abfd->temp_filename);

I think it would be nicer to just use free() here, rather
than XDELETEVEC, since make_temp_name does just return a malloc'ed
buffer.  Speaking of which:

+          nbfd->temp_filename = make_temp_file (NULL);

You need to check to see if make_temp_file() returned NULL here...

Cheers
  Nick
Reply | Threaded
Open this post in threaded view
|

Re: [PATCH 0/3] binutils: read from stdin if input file is -

Ahmad Fatoum
> On 20Feb 2018, at 15:31, Nick Clifton <[hidden email]> wrote:
>
> Hi Ahmad,
>
>> Changes behavior of dlltool, nlmconv, nm, objcopy, objdump and size
>> to allow niftiness like:
>>
>>    printf "\xCC" | objdump -D -bbinary -mi386 -
>>
>> Affects the layout of struct bfd as well. Is this ok?
>
> Yes, but I would like to request a couple of additions:
>
>  * Please add a patch to the binutils/NEWS file to mention
>    this new behaviour.
>
>  * Please update the binutils/doc/binutils.texi file to
>    document the new behaviour.
Will do.

>
>  * It would be really nice if you could create a new test
>    (or two) in the binutils testsuite to verify that the
>    new behaviour works.  I am not sure how difficult this
>    will be however.
Will see what I can do.

>
> One other thing.  In patch 1:
>
> +      XDELETEVEC ((char *) abfd->temp_filename);
>
> I think it would be nicer to just use free() here, rather
> than XDELETEVEC, since make_temp_name does just return a malloc'ed
> buffer.
As make_temp_name uses XNEWVEC I thought it'd be safer to use its XDELETEVEC counterpart (which is, for now!, #defined as free),
but I can change it.

> Speaking of which:
>
> +          nbfd->temp_filename = make_temp_file (NULL);
>
> You need to check to see if make_temp_file() returned NULL here...
make_temp_file aborts the process (via xmalloc) if it fails to allocate, so NULL check is superfluous.

Cheers
Ahmad
Reply | Threaded
Open this post in threaded view
|

Re: [PATCH 0/3] binutils: read from stdin if input file is -

Nick Clifton
Hi Ahmad,

>> Speaking of which:
>>
>> +          nbfd->temp_filename = make_temp_file (NULL);
>>
>> You need to check to see if make_temp_file() returned NULL here...
> make_temp_file aborts the process (via xmalloc) if it fails to allocate, so NULL check is superfluous.

Ah - I was going by the comment in libiberty.h:

  /* Return a temporary file name or NULL if unable to create one.  */

  extern char *make_temp_file (const char *) ATTRIBUTE_MALLOC;

And the libiberty info documentation:

  -- Replacement: char* make_temp_file (const char *SUFFIX)

     Return a temporary file name (as a string) or 'NULL' if unable to
     create one.  SUFFIX is a suffix to append to the file name.  The
     string is 'malloc'ed, and the temporary file has been created.

I guess that the header file and documentation are out of date.

Cheers
  Nick