[PATCHv2,2/8] gdb: split apart two different types of filename completion

Message ID 2b1ef90dda0f76377cc7f953310ebfc7665d37ed.1713603416.git.aburgess@redhat.com
State New
Headers
Series Further filename completion improvements |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 success Testing passed
linaro-tcwg-bot/tcwg_gdb_build--master-arm success Testing passed
linaro-tcwg-bot/tcwg_gdb_check--master-arm success Testing passed
linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 success Testing passed

Commit Message

Andrew Burgess April 20, 2024, 9:10 a.m. UTC
  Unfortunately we have two different types of filename completion in
GDB.

The majority of commands have what I call unquoted filename
completion, this is for commands like 'set logging file ...', 'target
core ...', and 'add-auto-load-safe-path ...'.  For these commands
everything after the command name (that is not a command option) is
treated as a single filename.  If the filename contains white space
then this does not need to be escaped, nor does the filename need to
be quoted.  In fact, the filename argument is not de-quoted, and does
not have any escaping removed, so if a user does try to add such
things, they will be treated as part of the filename.  As an example:

  (gdb) target core "/path/that contains/some white space"

Will look for a directory calls '"' (double quotes) in the local
directory.

A small number of commands do de-quote and remove escapes from
filename arguments.  These command accept what I call quoted and
escaped filenames.  Right now these are the commands that specify the
file for GDB to debug, so:

  file
  exec-file
  symbol-file
  add-symbol-file
  remove-symbol-file

As an example of this in action:

  (gdb) file "/path/that contains/some white space"

In this case GDB would load the file:

  /path/that contains/some white space

Current filename completion always assumes that filenames can be
quoted, though escaping doesn't work in completion right now.  But the
assumption that quoting is allowed is clearly wrong.

This commit splits filename completion into two.  There is now a
filename completer for unquoted filenames, and a second completer to
handle quoted and escaped filenames.

To handle completion of unquoted filenames the completion needs to be
performed during the brkchars phase of completion, which is why almost
every place we use filename_completer has to change in this commit.

I've also renamed the 'filename_completer' function to reflect that it
now handles filenames that can be quoted.

The filename completion test has been extended to cover more cases.

The 'advance_to_filename_complete_word_point' function is no longer
used after this commit and so I've deleted it.
---
 gdb/auto-load.c                               |   4 +-
 gdb/breakpoint.c                              |   5 +-
 gdb/cli/cli-cmds.c                            |   8 +-
 gdb/cli/cli-decode.c                          |  12 +-
 gdb/cli/cli-dump.c                            |   6 +-
 gdb/compile/compile.c                         |   3 +-
 gdb/completer.c                               |  61 ++++---
 gdb/completer.h                               |  29 +++-
 gdb/corefile.c                                |   4 +-
 gdb/corelow.c                                 |   4 +-
 gdb/dwarf2/index-write.c                      |   3 +-
 gdb/exec.c                                    |   7 +-
 gdb/guile/scm-cmd.c                           |   2 +-
 gdb/infcmd.c                                  |  15 +-
 gdb/inferior.c                                |   2 +-
 gdb/jit.c                                     |   2 +-
 gdb/python/py-cmd.c                           |   2 +-
 gdb/record-full.c                             |   5 +-
 gdb/record.c                                  |   2 +-
 gdb/skip.c                                    |   2 +-
 gdb/source.c                                  |   3 +-
 gdb/symfile.c                                 |   7 +-
 gdb/target-descriptions.c                     |   7 +-
 gdb/target.c                                  |   4 +-
 gdb/target.h                                  |   9 +-
 .../gdb.base/filename-completion.exp          | 156 +++++++++++++-----
 gdb/tracectf.c                                |   3 +-
 gdb/tracefile-tfile.c                         |   4 +-
 28 files changed, 248 insertions(+), 123 deletions(-)
  

Patch

diff --git a/gdb/auto-load.c b/gdb/auto-load.c
index db6d6ae0f73..0d57bf92fb7 100644
--- a/gdb/auto-load.c
+++ b/gdb/auto-load.c
@@ -1630,7 +1630,7 @@  This option has security implications for untrusted inferiors."),
 See the commands 'set auto-load safe-path' and 'show auto-load safe-path' to\n\
 access the current full list setting."),
 		 &cmdlist);
-  set_cmd_completer (cmd, filename_completer);
+  set_cmd_completer_handle_brkchars (cmd, filename_completer_handle_brkchars);
 
   cmd = add_cmd ("add-auto-load-scripts-directory", class_support,
 		 add_auto_load_dir,
@@ -1639,7 +1639,7 @@  access the current full list setting."),
 See the commands 'set auto-load scripts-directory' and\n\
 'show auto-load scripts-directory' to access the current full list setting."),
 		 &cmdlist);
-  set_cmd_completer (cmd, filename_completer);
+  set_cmd_completer_handle_brkchars (cmd, filename_completer_handle_brkchars);
 
   add_setshow_boolean_cmd ("auto-load", class_maintenance,
 			   &debug_auto_load, _("\
diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index 6d8adc62664..946b44df11c 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -15093,14 +15093,15 @@  This includes all types of breakpoints (breakpoints, watchpoints,\n\
 catchpoints, tracepoints).  Use the 'source' command in another debug\n\
 session to restore them."),
 	       &save_cmdlist);
-  set_cmd_completer (c, filename_completer);
+  set_cmd_completer_handle_brkchars (c, filename_completer_handle_brkchars);
 
   cmd_list_element *save_tracepoints_cmd
     = add_cmd ("tracepoints", class_trace, save_tracepoints_command, _("\
 Save current tracepoint definitions as a script.\n\
 Use the 'source' command in another debug session to restore them."),
 	       &save_cmdlist);
-  set_cmd_completer (save_tracepoints_cmd, filename_completer);
+  set_cmd_completer_handle_brkchars (save_tracepoints_cmd,
+				     filename_completer_handle_brkchars);
 
   c = add_com_alias ("save-tracepoints", save_tracepoints_cmd, class_trace, 0);
   deprecate_cmd (c, "save tracepoints");
diff --git a/gdb/cli/cli-cmds.c b/gdb/cli/cli-cmds.c
index 3afe2178199..0a537ca9661 100644
--- a/gdb/cli/cli-cmds.c
+++ b/gdb/cli/cli-cmds.c
@@ -2619,7 +2619,7 @@  The debugger's current working directory specifies where scripts and other\n\
 files that can be loaded by GDB are located.\n\
 In order to change the inferior's current working directory, the recommended\n\
 way is to use the \"set cwd\" command."), &cmdlist);
-  set_cmd_completer (c, filename_completer);
+  set_cmd_completer_handle_brkchars (c, filename_completer_handle_brkchars);
 
   add_com ("echo", class_support, echo_command, _("\
 Print a constant string.  Give string as argument.\n\
@@ -2785,7 +2785,7 @@  the previous command number shown."),
     = add_com ("shell", class_support, shell_command, _("\
 Execute the rest of the line as a shell command.\n\
 With no arguments, run an inferior shell."));
-  set_cmd_completer (shell_cmd, filename_completer);
+  set_cmd_completer_handle_brkchars (shell_cmd, filename_completer_handle_brkchars);
 
   add_com_alias ("!", shell_cmd, class_support, 0);
 
@@ -2874,7 +2874,7 @@  you must type \"disassemble 'foo.c'::bar\" and not \"disassemble foo.c:bar\"."))
 
   c = add_com ("make", class_support, make_command, _("\
 Run the ``make'' program using the rest of the line as arguments."));
-  set_cmd_completer (c, filename_completer);
+  set_cmd_completer_handle_brkchars (c, filename_completer_handle_brkchars);
   c = add_cmd ("user", no_class, show_user, _("\
 Show definitions of non-python/scheme user defined commands.\n\
 Argument is the name of the user defined command.\n\
@@ -2958,5 +2958,5 @@  Note that the file \"%s\" is read automatically in this way\n\
 when GDB is started."), GDBINIT).release ();
   c = add_cmd ("source", class_support, source_command,
 	       source_help_text, &cmdlist);
-  set_cmd_completer (c, filename_completer);
+  set_cmd_completer_handle_brkchars (c, filename_completer_handle_brkchars);
 }
diff --git a/gdb/cli/cli-decode.c b/gdb/cli/cli-decode.c
index d9a2ab40575..a48b18bbd87 100644
--- a/gdb/cli/cli-decode.c
+++ b/gdb/cli/cli-decode.c
@@ -866,7 +866,8 @@  add_setshow_filename_cmd (const char *name, enum command_class theclass,
 					 nullptr, nullptr, set_func,
 					 show_func, set_list, show_list);
 
-  set_cmd_completer (commands.set, filename_completer);
+  set_cmd_completer_handle_brkchars (commands.set,
+				     filename_completer_handle_brkchars);
 
   return commands;
 }
@@ -890,7 +891,8 @@  add_setshow_filename_cmd (const char *name, command_class theclass,
 						 nullptr, show_func, set_list,
 						 show_list);
 
-  set_cmd_completer (cmds.set, filename_completer);
+  set_cmd_completer_handle_brkchars (cmds.set,
+				     filename_completer_handle_brkchars);
 
   return cmds;
 }
@@ -1015,7 +1017,8 @@  add_setshow_optional_filename_cmd (const char *name, enum command_class theclass
 					 nullptr, nullptr, set_func, show_func,
 					 set_list, show_list);
 
-  set_cmd_completer (commands.set, filename_completer);
+  set_cmd_completer_handle_brkchars (commands.set,
+				     filename_completer_handle_brkchars);
 
   return commands;
 }
@@ -1039,7 +1042,8 @@  add_setshow_optional_filename_cmd (const char *name, command_class theclass,
 				       set_func, get_func, nullptr, show_func,
 				       set_list,show_list);
 
-  set_cmd_completer (cmds.set, filename_completer);
+  set_cmd_completer_handle_brkchars (cmds.set,
+				     filename_completer_handle_brkchars);
 
   return cmds;
 }
diff --git a/gdb/cli/cli-dump.c b/gdb/cli/cli-dump.c
index 9b44c6edcf2..aaacdd6ede1 100644
--- a/gdb/cli/cli-dump.c
+++ b/gdb/cli/cli-dump.c
@@ -348,7 +348,7 @@  add_dump_command (const char *name,
   struct dump_context *d;
 
   c = add_cmd (name, all_commands, descr, &dump_cmdlist);
-  c->completer =  filename_completer;
+  c->completer_handle_brkchars = filename_completer_handle_brkchars;
   d = XNEW (struct dump_context);
   d->func = func;
   d->mode = FOPEN_WB;
@@ -356,7 +356,7 @@  add_dump_command (const char *name,
   c->func = call_dump_func;
 
   c = add_cmd (name, all_commands, descr, &append_cmdlist);
-  c->completer =  filename_completer;
+  c->completer_handle_brkchars = filename_completer_handle_brkchars;
   d = XNEW (struct dump_context);
   d->func = func;
   d->mode = FOPEN_AB;
@@ -705,6 +705,6 @@  Arguments are FILE OFFSET START END where all except FILE are optional.\n\
 OFFSET will be added to the base address of the file (default zero).\n\
 If START and END are given, only the file contents within that range\n\
 (file relative) will be restored to target memory."));
-  c->completer = filename_completer;
+  set_cmd_completer_handle_brkchars (c, filename_completer_handle_brkchars);
   /* FIXME: completers for other commands.  */
 }
diff --git a/gdb/compile/compile.c b/gdb/compile/compile.c
index 2d97a1b2005..062418b3791 100644
--- a/gdb/compile/compile.c
+++ b/gdb/compile/compile.c
@@ -327,8 +327,7 @@  compile_file_command_completer (struct cmd_list_element *ignore,
       (tracker, &text, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_ERROR, group))
     return;
 
-  word = advance_to_filename_complete_word_point (tracker, text);
-  filename_completer (ignore, tracker, text, word);
+  filename_completer_handle_brkchars (ignore, tracker, text, word);
 }
 
 /* Handle the input from the 'compile code' command.  The
diff --git a/gdb/completer.c b/gdb/completer.c
index 171d1ca8c0e..c300e42f588 100644
--- a/gdb/completer.c
+++ b/gdb/completer.c
@@ -203,15 +203,15 @@  noop_completer (struct cmd_list_element *ignore,
 {
 }
 
-/* Complete on filenames.  */
+/* Generate filename completions of WORD, storing the completions into
+   TRACKER.  This is used for generating completions for commands that
+   only accept unquoted filenames as well as for commands that accept
+   quoted and escaped filenames.  */
 
-void
-filename_completer (struct cmd_list_element *ignore,
-		    completion_tracker &tracker,
-		    const char *text, const char *word)
+static void
+filename_completer_generate_completions (completion_tracker &tracker,
+					 const char *word)
 {
-  rl_completer_quote_characters = gdb_completer_file_name_quote_characters;
-
   int subsequent_name = 0;
   while (1)
     {
@@ -249,13 +249,23 @@  filename_completer (struct cmd_list_element *ignore,
     }
 }
 
-/* The corresponding completer_handle_brkchars
-   implementation.  */
+/* Complete on filenames.  */
+
+void
+filename_maybe_quoted_completer (struct cmd_list_element *ignore,
+				 completion_tracker &tracker,
+				 const char *text, const char *word)
+{
+  rl_completer_quote_characters = gdb_completer_file_name_quote_characters;
+  filename_completer_generate_completions (tracker, word);
+}
+
+/* The corresponding completer_handle_brkchars implementation.  */
 
 static void
-filename_completer_handle_brkchars (struct cmd_list_element *ignore,
-				    completion_tracker &tracker,
-				    const char *text, const char *word)
+filename_maybe_quoted_completer_handle_brkchars
+	(struct cmd_list_element *ignore, completion_tracker &tracker,
+	 const char *text, const char *word)
 {
   set_rl_completer_word_break_characters
     (gdb_completer_file_name_break_characters);
@@ -263,6 +273,18 @@  filename_completer_handle_brkchars (struct cmd_list_element *ignore,
   rl_completer_quote_characters = gdb_completer_file_name_quote_characters;
 }
 
+/* See completer.h.  */
+
+void
+filename_completer_handle_brkchars
+	(struct cmd_list_element *ignore, completion_tracker &tracker,
+	 const char *text, const char *word)
+{
+  gdb_assert (word == nullptr);
+  tracker.set_use_custom_word_point (true);
+  filename_completer_generate_completions (tracker, text);
+}
+
 /* Find the bounds of the current word for completion purposes, and
    return a pointer to the end of the word.  This mimics (and is a
    modified version of) readline's _rl_find_completion_word internal
@@ -443,17 +465,6 @@  advance_to_expression_complete_word_point (completion_tracker &tracker,
 
 /* See completer.h.  */
 
-const char *
-advance_to_filename_complete_word_point (completion_tracker &tracker,
-					 const char *text)
-{
-  const char *brk_chars = gdb_completer_file_name_break_characters;
-  const char *quote_chars = gdb_completer_file_name_quote_characters;
-  return advance_to_completion_word (tracker, brk_chars, quote_chars, text);
-}
-
-/* See completer.h.  */
-
 bool
 completion_tracker::completes_to_completion_word (const char *word)
 {
@@ -1877,8 +1888,8 @@  default_completer_handle_brkchars (struct cmd_list_element *ignore,
 completer_handle_brkchars_ftype *
 completer_handle_brkchars_func_for_completer (completer_ftype *fn)
 {
-  if (fn == filename_completer)
-    return filename_completer_handle_brkchars;
+  if (fn == filename_maybe_quoted_completer)
+    return filename_maybe_quoted_completer_handle_brkchars;
 
   if (fn == location_completer)
     return location_completer_handle_brkchars;
diff --git a/gdb/completer.h b/gdb/completer.h
index 98a12f3907c..fa21156bd1f 100644
--- a/gdb/completer.h
+++ b/gdb/completer.h
@@ -563,12 +563,6 @@  extern completion_result
 const char *advance_to_expression_complete_word_point
   (completion_tracker &tracker, const char *text);
 
-/* Assuming TEXT is an filename, find the completion word point for
-   TEXT, emulating the algorithm readline uses to find the word
-   point.  */
-extern const char *advance_to_filename_complete_word_point
-  (completion_tracker &tracker, const char *text);
-
 extern void noop_completer (struct cmd_list_element *,
 			    completion_tracker &tracker,
 			    const char *, const char *);
@@ -577,6 +571,29 @@  extern void filename_completer (struct cmd_list_element *,
 				completion_tracker &tracker,
 				const char *, const char *);
 
+/* Filename completer that can be registered for the brkchars phase of
+   completion.  This should be used by commands that don't allow the
+   filename to be quoted, and whitespace does not need to be escaped.
+
+   NOTE: If you are considering using this function as your commands
+   completer, then consider updating your function to use gdb_argv or
+   extract_string_maybe_quoted to allow for possibly quoted filenames
+   instead.  You would then use filename_maybe_quoted_completer for
+   filename completion.  The benefit of this is that in the future it is
+   possible to add additional arguments to your new command.  */
+
+extern void filename_completer_handle_brkchars
+	(struct cmd_list_element *ignore, completion_tracker &tracker,
+	 const char *text, const char *word);
+
+/* Filename completer for commands where the filename argument can be
+   quoted, and whitespace within an unquoted filename should be escaped
+   with a backslash.  */
+
+extern void filename_maybe_quoted_completer (struct cmd_list_element *,
+					     completion_tracker &tracker,
+					     const char *, const char *);
+
 extern void expression_completer (struct cmd_list_element *,
 				  completion_tracker &tracker,
 				  const char *, const char *);
diff --git a/gdb/corefile.c b/gdb/corefile.c
index 16cd60f7106..04a1021ee2b 100644
--- a/gdb/corefile.c
+++ b/gdb/corefile.c
@@ -404,9 +404,9 @@  Use FILE as core dump for examining memory and registers.\n\
 Usage: core-file FILE\n\
 No arg means have no core file.  This command has been superseded by the\n\
 `target core' and `detach' commands."), &cmdlist);
-  set_cmd_completer (core_file_cmd, filename_completer);
+  set_cmd_completer_handle_brkchars (core_file_cmd,
+				     filename_completer_handle_brkchars);
 
-  
   set_show_commands set_show_gnutarget
     = add_setshow_string_noescape_cmd ("gnutarget", class_files,
 				       &gnutarget_string, _("\
diff --git a/gdb/corelow.c b/gdb/corelow.c
index f4e8273d962..ff66d808120 100644
--- a/gdb/corelow.c
+++ b/gdb/corelow.c
@@ -1509,7 +1509,9 @@  void _initialize_corelow ();
 void
 _initialize_corelow ()
 {
-  add_target (core_target_info, core_target_open, filename_completer);
+  struct cmd_list_element *c
+    = add_target (core_target_info, core_target_open);
+  set_cmd_completer_handle_brkchars (c, filename_completer_handle_brkchars);
   add_cmd ("core-file-backed-mappings", class_maintenance,
 	   maintenance_print_core_file_backed_mappings,
 	   _("Print core file's file-backed mappings."),
diff --git a/gdb/dwarf2/index-write.c b/gdb/dwarf2/index-write.c
index 3f812285995..4f79c42bbf7 100644
--- a/gdb/dwarf2/index-write.c
+++ b/gdb/dwarf2/index-write.c
@@ -1620,8 +1620,7 @@  gdb_save_index_cmd_completer (struct cmd_list_element *ignore,
       (tracker, &text, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_OPERAND, grp))
     return;
 
-  word = advance_to_filename_complete_word_point (tracker, text);
-  filename_completer (ignore, tracker, text, word);
+  filename_completer_handle_brkchars (ignore, tracker, text, word);
 }
 
 /* Implementation of the `save gdb-index' command.
diff --git a/gdb/exec.c b/gdb/exec.c
index 98ad81fb99a..5210e6bc4ea 100644
--- a/gdb/exec.c
+++ b/gdb/exec.c
@@ -1067,14 +1067,14 @@  and it is the program executed when you use the `run' command.\n\
 If FILE cannot be found as specified, your execution directory path\n\
 ($PATH) is searched for a command of that name.\n\
 No arg means to have no executable file and no symbols."), &cmdlist);
-  set_cmd_completer (c, filename_completer);
+  set_cmd_completer (c, filename_maybe_quoted_completer);
 
   c = add_cmd ("exec-file", class_files, exec_file_command, _("\
 Use FILE as program for getting contents of pure memory.\n\
 If FILE cannot be found as specified, your execution directory path\n\
 is searched for a command of that name.\n\
 No arg means have no executable file."), &cmdlist);
-  set_cmd_completer (c, filename_completer);
+  set_cmd_completer (c, filename_maybe_quoted_completer);
 
   add_com ("section", class_files, set_section_command, _("\
 Change the base address of section SECTION of the exec file to ADDR.\n\
@@ -1112,5 +1112,6 @@  will be loaded as well."),
 			show_exec_file_mismatch_command,
 			&setlist, &showlist);
 
-  add_target (exec_target_info, exec_target_open, filename_completer);
+  c = add_target (exec_target_info, exec_target_open);
+  set_cmd_completer_handle_brkchars (c, filename_completer_handle_brkchars);
 }
diff --git a/gdb/guile/scm-cmd.c b/gdb/guile/scm-cmd.c
index d75d2b63c8c..037813250a9 100644
--- a/gdb/guile/scm-cmd.c
+++ b/gdb/guile/scm-cmd.c
@@ -110,7 +110,7 @@  struct cmdscm_completer
 static const struct cmdscm_completer cmdscm_completers[] =
 {
   { "COMPLETE_NONE", noop_completer },
-  { "COMPLETE_FILENAME", filename_completer },
+  { "COMPLETE_FILENAME", filename_maybe_quoted_completer },
   { "COMPLETE_LOCATION", location_completer },
   { "COMPLETE_COMMAND", command_completer },
   { "COMPLETE_SYMBOL", symbol_completer },
diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index 10a964a90d7..568dfe3693b 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -3113,7 +3113,8 @@  Follow this command with any number of args, to be passed to the program."),
 				       get_args_value,
 				       show_args_command,
 				       &setlist, &showlist);
-  set_cmd_completer (args_set_show.set, filename_completer);
+  set_cmd_completer_handle_brkchars (args_set_show.set,
+				     filename_completer_handle_brkchars);
 
   auto cwd_set_show
     = add_setshow_string_noescape_cmd ("cwd", class_run, _("\
@@ -3129,7 +3130,8 @@  working directory."),
 				       set_cwd_value, get_inferior_cwd,
 				       show_cwd_command,
 				       &setlist, &showlist);
-  set_cmd_completer (cwd_set_show.set, filename_completer);
+  set_cmd_completer_handle_brkchars (cwd_set_show.set,
+				     filename_completer_handle_brkchars);
 
   c = add_cmd ("environment", no_class, environment_info, _("\
 The environment to give the program, or one variable's value.\n\
@@ -3163,7 +3165,7 @@  This path is equivalent to the $PATH shell variable.  It is a list of\n\
 directories, separated by colons.  These directories are searched to find\n\
 fully linked executable files and separately compiled object files as \
 needed."));
-  set_cmd_completer (c, filename_completer);
+  set_cmd_completer_handle_brkchars (c, filename_completer_handle_brkchars);
 
   c = add_cmd ("paths", no_class, path_info, _("\
 Current search path for finding object files.\n\
@@ -3313,18 +3315,19 @@  Specifying -a and an ignore count simultaneously is an error."));
     = add_com ("run", class_run, run_command, _("\
 Start debugged program.\n"
 RUN_ARGS_HELP));
-  set_cmd_completer (run_cmd, filename_completer);
+  set_cmd_completer_handle_brkchars (run_cmd,
+				     filename_completer_handle_brkchars);
   add_com_alias ("r", run_cmd, class_run, 1);
 
   c = add_com ("start", class_run, start_command, _("\
 Start the debugged program stopping at the beginning of the main procedure.\n"
 RUN_ARGS_HELP));
-  set_cmd_completer (c, filename_completer);
+  set_cmd_completer_handle_brkchars (c, filename_completer_handle_brkchars);
 
   c = add_com ("starti", class_run, starti_command, _("\
 Start the debugged program stopping at the first instruction.\n"
 RUN_ARGS_HELP));
-  set_cmd_completer (c, filename_completer);
+  set_cmd_completer_handle_brkchars (c, filename_completer_handle_brkchars);
 
   add_com ("interrupt", class_run, interrupt_command,
 	   _("Interrupt the execution of the debugged program.\n\
diff --git a/gdb/inferior.c b/gdb/inferior.c
index 4e1d789d1ba..c883f020585 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -1112,7 +1112,7 @@  as main program.\n\
 By default, the new inferior inherits the current inferior's connection.\n\
 If -no-connection is specified, the new inferior begins with\n\
 no target connection yet."));
-  set_cmd_completer (c, filename_completer);
+  set_cmd_completer_handle_brkchars (c, filename_completer_handle_brkchars);
 
   add_com ("remove-inferiors", no_class, remove_inferior_command, _("\
 Remove inferior ID (or list of IDs).\n\
diff --git a/gdb/jit.c b/gdb/jit.c
index 3843b84b0e6..aa0b5289d36 100644
--- a/gdb/jit.c
+++ b/gdb/jit.c
@@ -1327,7 +1327,7 @@  Usage: jit-reader-load FILE\n\
 Try to load file FILE as a debug info reader (and unwinder) for\n\
 JIT compiled code.  The file is loaded from " JIT_READER_DIR ",\n\
 relocated relative to the GDB executable if required."));
-      set_cmd_completer (c, filename_completer);
+      set_cmd_completer_handle_brkchars (c, filename_completer_handle_brkchars);
 
       c = add_com ("jit-reader-unload", no_class,
 		   jit_reader_unload_command, _("\
diff --git a/gdb/python/py-cmd.c b/gdb/python/py-cmd.c
index f18a8e8eaa9..dec11f75c08 100644
--- a/gdb/python/py-cmd.c
+++ b/gdb/python/py-cmd.c
@@ -39,7 +39,7 @@  struct cmdpy_completer
 static const struct cmdpy_completer completers[] =
 {
   { "COMPLETE_NONE", noop_completer },
-  { "COMPLETE_FILENAME", filename_completer },
+  { "COMPLETE_FILENAME", filename_maybe_quoted_completer },
   { "COMPLETE_LOCATION", location_completer },
   { "COMPLETE_COMMAND", command_completer },
   { "COMPLETE_SYMBOL", symbol_completer },
diff --git a/gdb/record-full.c b/gdb/record-full.c
index 2e67cf5b428..c13b5b8d644 100644
--- a/gdb/record-full.c
+++ b/gdb/record-full.c
@@ -2888,12 +2888,13 @@  _initialize_record_full ()
 	       _("Restore the execution log from a file.\n\
 Argument is filename.  File must be created with 'record save'."),
 	       &record_full_cmdlist);
-  set_cmd_completer (record_full_restore_cmd, filename_completer);
+  set_cmd_completer_handle_brkchars (record_full_restore_cmd,
+				     filename_completer_handle_brkchars);
 
   /* Deprecate the old version without "full" prefix.  */
   c = add_alias_cmd ("restore", record_full_restore_cmd, class_obscure, 1,
 		     &record_cmdlist);
-  set_cmd_completer (c, filename_completer);
+  set_cmd_completer_handle_brkchars (c, filename_completer_handle_brkchars);
   deprecate_cmd (c, "record full restore");
 
   add_setshow_prefix_cmd ("full", class_support,
diff --git a/gdb/record.c b/gdb/record.c
index 5b1093dd12e..daa3d5bd1e3 100644
--- a/gdb/record.c
+++ b/gdb/record.c
@@ -820,7 +820,7 @@  A size of \"unlimited\" means unlimited lines.  The default is 10."),
 Usage: record save [FILENAME]\n\
 Default filename is 'gdb_record.PROCESS_ID'."),
 	       &record_cmdlist);
-  set_cmd_completer (c, filename_completer);
+  set_cmd_completer_handle_brkchars (c, filename_completer_handle_brkchars);
 
   cmd_list_element *record_delete_cmd
     =  add_cmd ("delete", class_obscure, cmd_record_delete,
diff --git a/gdb/skip.c b/gdb/skip.c
index f2818eccb34..c932598d243 100644
--- a/gdb/skip.c
+++ b/gdb/skip.c
@@ -683,7 +683,7 @@  Ignore a file while stepping.\n\
 Usage: skip file [FILE-NAME]\n\
 If no filename is given, ignore the current file."),
 	       &skiplist);
-  set_cmd_completer (c, filename_completer);
+  set_cmd_completer_handle_brkchars (c, filename_completer_handle_brkchars);
 
   c = add_cmd ("function", class_breakpoint, skip_function_command, _("\
 Ignore a function while stepping.\n\
diff --git a/gdb/source.c b/gdb/source.c
index 432301e2a71..7c0cea08aa3 100644
--- a/gdb/source.c
+++ b/gdb/source.c
@@ -1916,7 +1916,8 @@  directory in which the source file was compiled into object code.\n\
 With no argument, reset the search path to $cdir:$cwd, the default."),
 	       &cmdlist);
 
-  set_cmd_completer (directory_cmd, filename_completer);
+  set_cmd_completer_handle_brkchars (directory_cmd,
+				     filename_completer_handle_brkchars);
 
   add_setshow_optional_filename_cmd ("directories",
 				     class_files,
diff --git a/gdb/symfile.c b/gdb/symfile.c
index 2a7d41dc974..b9c21d33ac0 100644
--- a/gdb/symfile.c
+++ b/gdb/symfile.c
@@ -3870,7 +3870,7 @@  Usage: symbol-file [-readnow | -readnever] [-o OFF] FILE\n\
 OFF is an optional offset which is added to each section address.\n\
 The `file' command can also load symbol tables, as well as setting the file\n\
 to execute.\n" READNOW_READNEVER_HELP), &cmdlist);
-  set_cmd_completer (c, filename_completer);
+  set_cmd_completer (c, filename_maybe_quoted_completer);
 
   c = add_cmd ("add-symbol-file", class_files, add_symbol_file_command, _("\
 Load symbols from FILE, assuming FILE has been dynamically loaded.\n\
@@ -3884,7 +3884,7 @@  OFF is an optional offset which is added to the default load addresses\n\
 of all sections for which no other address was specified.\n"
 READNOW_READNEVER_HELP),
 	       &cmdlist);
-  set_cmd_completer (c, filename_completer);
+  set_cmd_completer (c, filename_maybe_quoted_completer);
 
   c = add_cmd ("remove-symbol-file", class_files,
 	       remove_symbol_file_command, _("\
@@ -3894,6 +3894,7 @@  Usage: remove-symbol-file FILENAME\n\
 The file to remove can be identified by its filename or by an address\n\
 that lies within the boundaries of this symbol file in memory."),
 	       &cmdlist);
+  set_cmd_completer (c, filename_maybe_quoted_completer);
 
   c = add_cmd ("load", class_files, load_command, _("\
 Dynamically load FILE into the running program.\n\
@@ -3902,7 +3903,7 @@  Usage: load [FILE] [OFFSET]\n\
 An optional load OFFSET may also be given as a literal address.\n\
 When OFFSET is provided, FILE must also be provided.  FILE can be provided\n\
 on its own."), &cmdlist);
-  set_cmd_completer (c, filename_completer);
+  set_cmd_completer_handle_brkchars (c, filename_completer_handle_brkchars);
 
   cmd_list_element *overlay_cmd
     = add_basic_prefix_cmd ("overlay", class_support,
diff --git a/gdb/target-descriptions.c b/gdb/target-descriptions.c
index 8aca5cf719b..dead63823d1 100644
--- a/gdb/target-descriptions.c
+++ b/gdb/target-descriptions.c
@@ -1762,8 +1762,7 @@  maint_print_c_tdesc_cmd_completer (struct cmd_list_element *ignore,
       (tracker, &text, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_ERROR, grp))
     return;
 
-  word = advance_to_filename_complete_word_point (tracker, text);
-  filename_completer (ignore, tracker, text, word);
+  filename_completer_handle_brkchars (ignore, tracker, text, word);
 }
 
 /* Implement the maintenance print xml-tdesc command.  */
@@ -1947,7 +1946,7 @@  that feature within an already existing target_desc object."), grp);
   cmd = add_cmd ("xml-tdesc", class_maintenance, maint_print_xml_tdesc_cmd, _("\
 Print the current target description as an XML file."),
 		 &maintenanceprintlist);
-  set_cmd_completer (cmd, filename_completer);
+  set_cmd_completer_handle_brkchars (cmd, filename_completer_handle_brkchars);
 
   cmd = add_cmd ("xml-descriptions", class_maintenance,
 		 maintenance_check_xml_descriptions, _("\
@@ -1956,5 +1955,5 @@  Check the target descriptions created in GDB equal the descriptions\n\
 created from XML files in the directory.\n\
 The parameter is the directory name."),
 		 &maintenancechecklist);
-  set_cmd_completer (cmd, filename_completer);
+  set_cmd_completer_handle_brkchars (cmd, filename_completer_handle_brkchars);
 }
diff --git a/gdb/target.c b/gdb/target.c
index 107a84b3ca1..949daf82b0a 100644
--- a/gdb/target.c
+++ b/gdb/target.c
@@ -829,7 +829,7 @@  open_target (const char *args, int from_tty, struct cmd_list_element *command)
 
 /* See target.h.  */
 
-void
+struct cmd_list_element *
 add_target (const target_info &t, target_open_ftype *func,
 	    completer_ftype *completer)
 {
@@ -853,6 +853,8 @@  information on the arguments for a particular protocol, type\n\
   c->func = open_target;
   if (completer != NULL)
     set_cmd_completer (c, completer);
+
+  return c;
 }
 
 /* See target.h.  */
diff --git a/gdb/target.h b/gdb/target.h
index c9eaff16346..fe8a9c7a60b 100644
--- a/gdb/target.h
+++ b/gdb/target.h
@@ -2370,11 +2370,12 @@  typedef void target_open_ftype (const char *args, int from_tty);
 
 /* Add the target described by INFO to the list of possible targets
    and add a new command 'target $(INFO->shortname)'.  Set COMPLETER
-   as the command's completer if not NULL.  */
+   as the command's completer if not NULL.  Return the new target
+   command.  */
 
-extern void add_target (const target_info &info,
-			target_open_ftype *func,
-			completer_ftype *completer = NULL);
+extern struct cmd_list_element *add_target
+	(const target_info &info, target_open_ftype *func,
+	 completer_ftype *completer = NULL);
 
 /* Adds a command ALIAS for the target described by INFO and marks it
    deprecated.  This is useful for maintaining backwards compatibility
diff --git a/gdb/testsuite/gdb.base/filename-completion.exp b/gdb/testsuite/gdb.base/filename-completion.exp
index b700977cec5..1ccaaff9afc 100644
--- a/gdb/testsuite/gdb.base/filename-completion.exp
+++ b/gdb/testsuite/gdb.base/filename-completion.exp
@@ -23,8 +23,16 @@  load_lib completion-support.exp
 #
 # root/			[ DIRECTORY ]
 #   aaa/		[ DIRECTORY ]
+#     aa bb		[ FILE ]
+#     aa cc		[ FILE ]
+#   aaa/		[ DIRECTORY ]
 #   bb1/		[ DIRECTORY ]
 #   bb2/		[ DIRECTORY ]
+#     dir 1/		[ DIRECTORY ]
+#       unique file	[ FILE ]
+#     dir 2/		[ DIRECTORY ]
+#       file 1		[ FILE ]
+#       file 2		[ FILE ]
 #   cc1/		[ DIRECTORY ]
 #   cc2			[ FILE ]
 proc setup_directory_tree {} {
@@ -36,68 +44,139 @@  proc setup_directory_tree {} {
     remote_exec host "mkdir -p ${root}/bb2"
     remote_exec host "mkdir -p ${root}/cc1"
     remote_exec host "touch ${root}/cc2"
-
     remote_exec host "touch \"${root}/aaa/aa bb\""
     remote_exec host "touch \"${root}/aaa/aa cc\""
+    remote_exec host "mkdir -p \"${root}/bb2/dir 1\""
+    remote_exec host "mkdir -p \"${root}/bb2/dir 2\""
+    remote_exec host "touch \"${root}/bb2/dir 1/unique file\""
+    remote_exec host "touch \"${root}/bb2/dir 2/file 1\""
+    remote_exec host "touch \"${root}/bb2/dir 2/file 2\""
 
     return $root
 }
 
-# Run filename completetion tests.  ROOT is the base directory as
-# returned from setup_directory_tree, though, if ROOT is a
-# sub-directory of the user's home directory ROOT might have been
-# modified to replace the $HOME prefix with a single "~" character.
-proc run_tests { root } {
-
-    # Completing 'thread apply all ...' commands uses a custom word
-    # point.  At one point we had a bug where doing this would break
-    # completion of quoted filenames that contained white space.
-    test_gdb_complete_unique "thread apply all hel" \
-	"thread apply all help" " " false \
-	"complete a 'thread apply all' command"
-
-    foreach_with_prefix qc [list "" "'" "\""] {
-	test_gdb_complete_none "file ${qc}${root}/xx" \
+# Run filename completetion tests for those command that accept quoting and
+# escaping of the filename argument.
+#
+# ROOT is the base directory as returned from setup_directory_tree, though,
+# if ROOT is a sub-directory of the user's home directory ROOT might have
+# been modified to replace the $HOME prefix with a single "~" character.
+proc run_quoting_and_escaping_tests { root } {
+    # Test all the commands which allow quoting of filenames, and
+    # which require whitespace to be escaped in unquoted filenames.
+    foreach_with_prefix cmd { file exec-file symbol-file add-symbol-file \
+				  remove-symbol-file } {
+	gdb_start
+
+	# Completing 'thread apply all ...' commands uses a custom word
+	# point.  At one point we had a bug where doing this would break
+	# completion of quoted filenames that contained white space.
+	test_gdb_complete_unique "thread apply all hel" \
+	    "thread apply all help" " " false \
+	    "complete a 'thread apply all' command"
+
+	foreach_with_prefix qc [list "" "'" "\""] {
+	    test_gdb_complete_none "$cmd ${qc}${root}/xx" \
+		"expand a non-existent filename"
+
+	    test_gdb_complete_unique "$cmd ${qc}${root}/a" \
+		"$cmd ${qc}${root}/aaa/" "" false \
+		"expand a unique filename"
+
+	    test_gdb_complete_multiple "$cmd ${qc}${root}/" \
+		"b" "b" {
+		    "bb1/"
+		    "bb2/"
+		} "" "${qc}" false \
+		"expand multiple directory names"
+
+	    test_gdb_complete_multiple "$cmd ${qc}${root}/" \
+		"c" "c" {
+		    "cc1/"
+		    "cc2"
+		} "" "${qc}" false \
+		"expand mixed directory and file names"
+
+	    # GDB does not currently escape word break characters
+	    # (e.g. white space) correctly in unquoted filenames.
+	    if { $qc ne "" } {
+		set sp " "
+
+		test_gdb_complete_multiple "$cmd ${qc}${root}/aaa/" \
+		    "a" "a${sp}" {
+			"aa bb"
+			"aa cc"
+		    } "" "${qc}" false \
+		    "expand filenames containing spaces"
+	    }
+	}
+
+	gdb_exit
+    }
+}
+
+# Run filename completetion tests for a sample of commands that take an
+# unquoted, unescaped filename as an argument.  Only a sample of commands
+# are (currently) tested as there's a lot of commands that accept this style
+# of filename argument.
+#
+# ROOT is the base directory as returned from setup_directory_tree, though,
+# if ROOT is a sub-directory of the user's home directory ROOT might have
+# been modified to replace the $HOME prefix with a single "~" character.
+proc run_unquoted_tests { root } {
+    # Test all the commands which allow quoting of filenames, and
+    # which require whitespace to be escaped in unquoted filenames.
+    foreach_with_prefix cmd { "set logging file" "target core" \
+				  "add-auto-load-safe-path" } {
+	gdb_start
+
+	test_gdb_complete_none "$cmd ${root}/xx" \
 	    "expand a non-existent filename"
 
-	test_gdb_complete_unique "file ${qc}${root}/a" \
-	    "file ${qc}${root}/aaa/" "" false \
+	test_gdb_complete_unique "$cmd ${root}/a" \
+	    "$cmd ${root}/aaa/" "" false \
 	    "expand a unique filename"
 
-	test_gdb_complete_multiple "file ${qc}${root}/" \
+	test_gdb_complete_unique "$cmd ${root}/bb2/dir 1/uni" \
+	    "$cmd ${root}/bb2/dir 1/unique file" " " false \
+	    "expand a unique filename containing whitespace"
+
+	test_gdb_complete_multiple "$cmd ${root}/" \
 	    "b" "b" {
 		"bb1/"
 		"bb2/"
-	    } "" "${qc}" false \
+	    } "" "" false \
 	    "expand multiple directory names"
 
-	test_gdb_complete_multiple "file ${qc}${root}/" \
+	test_gdb_complete_multiple "$cmd ${root}/" \
 	    "c" "c" {
 		"cc1/"
 		"cc2"
-	    } "" "${qc}" false \
+	    } "" "" false \
 	    "expand mixed directory and file names"
 
-	# GDB does not currently escape word break characters
-	# (e.g. white space) correctly in unquoted filenames.
-	if { $qc ne "" } {
-	    set sp " "
-
-	    test_gdb_complete_multiple "file ${qc}${root}/aaa/" \
-		"a" "a${sp}" {
-		    "aa bb"
-		    "aa cc"
-		} "" "${qc}" false \
-		"expand filenames containing spaces"
-	}
+	test_gdb_complete_multiple "$cmd ${root}/aaa/" \
+	    "a" "a " {
+		"aa bb"
+		"aa cc"
+	    } "" "" false \
+	    "expand filenames containing spaces"
+
+	test_gdb_complete_multiple "$cmd ${root}/bb2/dir 2/" \
+	    "fi" "le " {
+		"file 1"
+		"file 2"
+	    } "" "" false \
+	    "expand filenames containing spaces in path"
+
+	gdb_exit
     }
 }
 
-gdb_start
-
 set root [setup_directory_tree]
 
-run_tests $root
+run_quoting_and_escaping_tests $root
+run_unquoted_tests $root
 
 # This test relies on using the $HOME directory.  We could make this
 # work for remote hosts, but right now, this isn't supported.
@@ -114,7 +193,8 @@  if {![is_remote host]} {
 
 	with_test_prefix "with tilde" {
 	    # And rerun the tests.
-	    run_tests $tilde_root
+	    run_quoting_and_escaping_tests $tilde_root
+	    run_unquoted_tests $tilde_root
 	}
     }
 }
diff --git a/gdb/tracectf.c b/gdb/tracectf.c
index 282a8250ac1..94b8a283ac6 100644
--- a/gdb/tracectf.c
+++ b/gdb/tracectf.c
@@ -1721,6 +1721,7 @@  void
 _initialize_ctf ()
 {
 #if HAVE_LIBBABELTRACE
-  add_target (ctf_target_info, ctf_target_open, filename_completer);
+  struct cmd_list_element *c = add_target (ctf_target_info, ctf_target_open);
+  set_cmd_completer_handle_brkchars (c, filename_completer_handle_brkchars);
 #endif
 }
diff --git a/gdb/tracefile-tfile.c b/gdb/tracefile-tfile.c
index 79af963b049..14909daa1cc 100644
--- a/gdb/tracefile-tfile.c
+++ b/gdb/tracefile-tfile.c
@@ -1119,5 +1119,7 @@  void _initialize_tracefile_tfile ();
 void
 _initialize_tracefile_tfile ()
 {
-  add_target (tfile_target_info, tfile_target_open, filename_completer);
+  struct cmd_list_element *c =
+    add_target (tfile_target_info, tfile_target_open);
+  set_cmd_completer_handle_brkchars (c, filename_completer_handle_brkchars);
 }