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