446cf2
commit e0f1a58f3d1f4f55591b524e9dcff23cc98a509e
446cf2
Author: Florian Weimer <fweimer@redhat.com>
446cf2
Date:   Thu Oct 8 10:57:10 2020 +0200
446cf2
446cf2
    elf: Implement ld.so --help
446cf2
    
446cf2
    --help processing is deferred to the point where the executable has
446cf2
    been loaded, so that it is possible to eventually include information
446cf2
    from the main executable in the help output.
446cf2
    
446cf2
    As suggested in the GNU command-line interface guidelines, the help
446cf2
    message is printed to standard output, and the exit status is
446cf2
    successful.
446cf2
    
446cf2
    Handle usage errors closer to the GNU command-line interface
446cf2
    guidelines.
446cf2
    
446cf2
    Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
446cf2
446cf2
diff --git a/elf/dl-main.h b/elf/dl-main.h
446cf2
index 79c9c40056504f80..ac7249a580214860 100644
446cf2
--- a/elf/dl-main.h
446cf2
+++ b/elf/dl-main.h
446cf2
@@ -63,6 +63,7 @@ struct audit_list
446cf2
 enum rtld_mode
446cf2
   {
446cf2
     rtld_mode_normal, rtld_mode_list, rtld_mode_verify, rtld_mode_trace,
446cf2
+    rtld_mode_help,
446cf2
   };
446cf2
 
446cf2
 /* Aggregated state information extracted from environment variables
446cf2
@@ -101,6 +102,11 @@ call_init_paths (const struct dl_main_state *state)
446cf2
 }
446cf2
 
446cf2
 /* Print ld.so usage information and exit.  */
446cf2
-_Noreturn void _dl_usage (void) attribute_hidden;
446cf2
+_Noreturn void _dl_usage (const char *argv0, const char *wrong_option)
446cf2
+  attribute_hidden;
446cf2
+
446cf2
+/* Print ld.so --help output and exit.  */
446cf2
+_Noreturn void _dl_help (const char *argv0, struct dl_main_state *state)
446cf2
+  attribute_hidden;
446cf2
 
446cf2
 #endif /* _DL_MAIN */
446cf2
diff --git a/elf/dl-usage.c b/elf/dl-usage.c
446cf2
index f3d89d22b71d7d12..c1820dca2fa117ee 100644
446cf2
--- a/elf/dl-usage.c
446cf2
+++ b/elf/dl-usage.c
446cf2
@@ -19,12 +19,24 @@
446cf2
 #include <dl-cache.h>
446cf2
 #include <dl-main.h>
446cf2
 #include <ldsodefs.h>
446cf2
+#include <unistd.h>
446cf2
 
446cf2
 void
446cf2
-_dl_usage (void)
446cf2
+_dl_usage (const char *argv0, const char *wrong_option)
446cf2
 {
446cf2
-  _dl_fatal_printf ("\
446cf2
-Usage: ld.so [OPTION]... EXECUTABLE-FILE [ARGS-FOR-PROGRAM...]\n\
446cf2
+  if (wrong_option != NULL)
446cf2
+    _dl_error_printf ("%s: unrecognized option '%s'\n", argv0, wrong_option);
446cf2
+  else
446cf2
+    _dl_error_printf ("%s: missing program name\n", argv0);
446cf2
+  _dl_error_printf ("Try '%s --help' for more information.\n", argv0);
446cf2
+  _exit (EXIT_FAILURE);
446cf2
+}
446cf2
+
446cf2
+void
446cf2
+_dl_help (const char *argv0, struct dl_main_state *state)
446cf2
+{
446cf2
+  _dl_printf ("\
446cf2
+Usage: %s [OPTION]... EXECUTABLE-FILE [ARGS-FOR-PROGRAM...]\n\
446cf2
 You have invoked `ld.so', the helper program for shared library executables.\n\
446cf2
 This program usually lives in the file `/lib/ld.so', and special directives\n\
446cf2
 in executable files using ELF shared libraries tell the system's program\n\
446cf2
@@ -47,5 +59,9 @@ of this helper program; chances are you did not intend to run this program.\n\
446cf2
                         in LIST\n\
446cf2
   --audit LIST          use objects named in LIST as auditors\n\
446cf2
   --preload LIST        preload objects named in LIST\n\
446cf2
-  --argv0 STRING        set argv[0] to STRING before running\n");
446cf2
+  --argv0 STRING        set argv[0] to STRING before running\n\
446cf2
+  --help                display this help and exit\n\
446cf2
+",
446cf2
+              argv0);
446cf2
+  _exit (EXIT_SUCCESS);
446cf2
 }
446cf2
diff --git a/elf/rtld.c b/elf/rtld.c
446cf2
index 8e91cee41b62b894..b92641cb1c2d99a6 100644
446cf2
--- a/elf/rtld.c
446cf2
+++ b/elf/rtld.c
446cf2
@@ -1145,6 +1145,7 @@ dl_main (const ElfW(Phdr) *phdr,
446cf2
   /* Set up a flag which tells we are just starting.  */
446cf2
   _dl_starting_up = 1;
446cf2
 
446cf2
+  const char *ld_so_name = _dl_argv[0];
446cf2
   if (*user_entry == (ElfW(Addr)) ENTRY_POINT)
446cf2
     {
446cf2
       /* Ho ho.  We are not the program interpreter!  We are the program
446cf2
@@ -1172,8 +1173,12 @@ dl_main (const ElfW(Phdr) *phdr,
446cf2
       while (_dl_argc > 1)
446cf2
 	if (! strcmp (_dl_argv[1], "--list"))
446cf2
 	  {
446cf2
-	    state.mode = rtld_mode_list;
446cf2
-	    GLRO(dl_lazy) = -1;	/* This means do no dependency analysis.  */
446cf2
+	    if (state.mode != rtld_mode_help)
446cf2
+	      {
446cf2
+	       state.mode = rtld_mode_list;
446cf2
+		/* This means do no dependency analysis.  */
446cf2
+		GLRO(dl_lazy) = -1;
446cf2
+	      }
446cf2
 
446cf2
 	    ++_dl_skip_args;
446cf2
 	    --_dl_argc;
446cf2
@@ -1181,7 +1186,8 @@ dl_main (const ElfW(Phdr) *phdr,
446cf2
 	  }
446cf2
 	else if (! strcmp (_dl_argv[1], "--verify"))
446cf2
 	  {
446cf2
-	    state.mode = rtld_mode_verify;
446cf2
+	    if (state.mode != rtld_mode_help)
446cf2
+	      state.mode = rtld_mode_verify;
446cf2
 
446cf2
 	    ++_dl_skip_args;
446cf2
 	    --_dl_argc;
446cf2
@@ -1236,13 +1242,34 @@ dl_main (const ElfW(Phdr) *phdr,
446cf2
 	    _dl_argc -= 2;
446cf2
 	    _dl_argv += 2;
446cf2
 	  }
446cf2
+	else if (strcmp (_dl_argv[1], "--help") == 0)
446cf2
+	  {
446cf2
+	    state.mode = rtld_mode_help;
446cf2
+	    --_dl_argc;
446cf2
+	    ++_dl_argv;
446cf2
+	  }
446cf2
+	else if (_dl_argv[1][0] == '-' && _dl_argv[1][1] == '-')
446cf2
+	  {
446cf2
+	   if (_dl_argv[1][1] == '\0')
446cf2
+	     /* End of option list.  */
446cf2
+	     break;
446cf2
+	   else
446cf2
+	     /* Unrecognized option.  */
446cf2
+	     _dl_usage (ld_so_name, _dl_argv[1]);
446cf2
+	  }
446cf2
 	else
446cf2
 	  break;
446cf2
 
446cf2
       /* If we have no further argument the program was called incorrectly.
446cf2
 	 Grant the user some education.  */
446cf2
       if (_dl_argc < 2)
446cf2
-	_dl_usage ();
446cf2
+	{
446cf2
+	  if (state.mode == rtld_mode_help)
446cf2
+	    /* --help without an executable is not an error.  */
446cf2
+	    _dl_help (ld_so_name, &state);
446cf2
+	  else
446cf2
+	    _dl_usage (ld_so_name, NULL);
446cf2
+	}
446cf2
 
446cf2
       ++_dl_skip_args;
446cf2
       --_dl_argc;
446cf2
@@ -1267,7 +1294,8 @@ dl_main (const ElfW(Phdr) *phdr,
446cf2
 	    break;
446cf2
 	  }
446cf2
 
446cf2
-      if (__glibc_unlikely (state.mode == rtld_mode_verify))
446cf2
+      if (__glibc_unlikely (state.mode == rtld_mode_verify
446cf2
+			    || state.mode == rtld_mode_help))
446cf2
 	{
446cf2
 	  const char *objname;
446cf2
 	  const char *err_str = NULL;
446cf2
@@ -1280,9 +1308,16 @@ dl_main (const ElfW(Phdr) *phdr,
446cf2
 	  (void) _dl_catch_error (&objname, &err_str, &malloced, map_doit,
446cf2
 				  &args);
446cf2
 	  if (__glibc_unlikely (err_str != NULL))
446cf2
-	    /* We don't free the returned string, the programs stops
446cf2
-	       anyway.  */
446cf2
-	    _exit (EXIT_FAILURE);
446cf2
+	    {
446cf2
+	      /* We don't free the returned string, the programs stops
446cf2
+		 anyway.  */
446cf2
+	      if (state.mode == rtld_mode_help)
446cf2
+		/* Mask the failure to load the main object.  The help
446cf2
+		   message contains less information in this case.  */
446cf2
+		_dl_help (ld_so_name, &state);
446cf2
+	      else
446cf2
+		_exit (EXIT_FAILURE);
446cf2
+	    }
446cf2
 	}
446cf2
       else
446cf2
 	{
446cf2
@@ -1632,6 +1667,11 @@ ERROR: '%s': cannot process note segment.\n", _dl_argv[0]);
446cf2
   audit_list_add_dynamic_tag (&state.audit_list, main_map, DT_AUDIT);
446cf2
   audit_list_add_dynamic_tag (&state.audit_list, main_map, DT_DEPAUDIT);
446cf2
 
446cf2
+  /* At this point, all data has been obtained that is included in the
446cf2
+     --help output.  */
446cf2
+  if (__glibc_unlikely (state.mode == rtld_mode_help))
446cf2
+    _dl_help (ld_so_name, &state);
446cf2
+
446cf2
   /* If we have auditing DSOs to load, do it now.  */
446cf2
   bool need_security_init = true;
446cf2
   if (state.audit_list.length > 0)