diff --git a/lldb/source/Commands/CommandObjectPlatform.cpp b/lldb/source/Commands/CommandObjectPlatform.cpp --- a/lldb/source/Commands/CommandObjectPlatform.cpp +++ b/lldb/source/Commands/CommandObjectPlatform.cpp @@ -926,6 +926,141 @@ } }; +// "platform get-permissions remote-file-path" +class CommandObjectPlatformGetPermissions : public CommandObjectParsed { +public: + CommandObjectPlatformGetPermissions(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "platform get-permissions", + "Get the file permission bits from the remote end.", + "platform get-permissions ", 0) { + SetHelpLong( + R"(Examples: + +(lldb) platform get-permissions /the/remote/file/path + + Get the file permissions from the remote end with path /the/remote/file/path.)"); + + CommandArgumentEntry arg1; + CommandArgumentData file_arg_remote; + + // Define the first (and only) variant of this arg. + file_arg_remote.arg_type = eArgTypeFilename; + file_arg_remote.arg_repetition = eArgRepeatPlain; + // There is only one variant this argument could be; put it into the + // argument entry. + arg1.push_back(file_arg_remote); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg1); + } + + ~CommandObjectPlatformGetPermissions() override = default; + + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + if (request.GetCursorIndex() != 0) + return; + + CommandCompletions::InvokeCommonCompletionCallbacks( + GetCommandInterpreter(), CommandCompletions::eRemoteDiskFileCompletion, + request, nullptr); + } + + bool DoExecute(Args &args, CommandReturnObject &result) override { + // If the number of arguments is incorrect, issue an error message. + if (args.GetArgumentCount() != 1) { + result.AppendError("required argument missing; specify the source file " + "path as the only argument"); + return false; + } + + PlatformSP platform_sp( + GetDebugger().GetPlatformList().GetSelectedPlatform()); + if (platform_sp) { + std::string remote_file_path(args.GetArgumentAtIndex(0)); + uint32_t permissions; + Status error = platform_sp->GetFilePermissions(FileSpec(remote_file_path), + permissions); + if (error.Success()) { + result.AppendMessageWithFormat( + "File permissions of %s (remote): 0o%04" PRIo32 "\n", + remote_file_path.c_str(), permissions); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else + result.AppendError(error.AsCString()); + } else { + result.AppendError("no platform currently selected\n"); + } + return result.Succeeded(); + } +}; + +// "platform file-exists remote-file-path" +class CommandObjectPlatformFileExists : public CommandObjectParsed { +public: + CommandObjectPlatformFileExists(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "platform file-exists", + "Check if the file exists on the remote end.", + "platform file-exists ", 0) { + SetHelpLong( + R"(Examples: + +(lldb) platform file-exists /the/remote/file/path + + Check if /the/remote/file/path exists on the remote end.)"); + + CommandArgumentEntry arg1; + CommandArgumentData file_arg_remote; + + // Define the first (and only) variant of this arg. + file_arg_remote.arg_type = eArgTypeFilename; + file_arg_remote.arg_repetition = eArgRepeatPlain; + // There is only one variant this argument could be; put it into the + // argument entry. + arg1.push_back(file_arg_remote); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg1); + } + + ~CommandObjectPlatformFileExists() override = default; + + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + if (request.GetCursorIndex() != 0) + return; + + CommandCompletions::InvokeCommonCompletionCallbacks( + GetCommandInterpreter(), CommandCompletions::eRemoteDiskFileCompletion, + request, nullptr); + } + + bool DoExecute(Args &args, CommandReturnObject &result) override { + // If the number of arguments is incorrect, issue an error message. + if (args.GetArgumentCount() != 1) { + result.AppendError("required argument missing; specify the source file " + "path as the only argument"); + return false; + } + + PlatformSP platform_sp( + GetDebugger().GetPlatformList().GetSelectedPlatform()); + if (platform_sp) { + std::string remote_file_path(args.GetArgumentAtIndex(0)); + bool exists = platform_sp->GetFileExists(FileSpec(remote_file_path)); + result.AppendMessageWithFormat( + "File %s (remote) %s\n", + remote_file_path.c_str(), exists ? "exists" : "does not exist"); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendError("no platform currently selected\n"); + } + return result.Succeeded(); + } +}; + // "platform put-file" class CommandObjectPlatformPutFile : public CommandObjectParsed { public: @@ -1759,8 +1894,12 @@ CommandObjectSP(new CommandObjectPlatformMkDir(interpreter))); LoadSubCommand("file", CommandObjectSP(new CommandObjectPlatformFile(interpreter))); + LoadSubCommand("file-exists", + CommandObjectSP(new CommandObjectPlatformFileExists(interpreter))); LoadSubCommand("get-file", CommandObjectSP(new CommandObjectPlatformGetFile( interpreter))); + LoadSubCommand("get-permissions", + CommandObjectSP(new CommandObjectPlatformGetPermissions(interpreter))); LoadSubCommand("get-size", CommandObjectSP(new CommandObjectPlatformGetSize( interpreter))); LoadSubCommand("put-file", CommandObjectSP(new CommandObjectPlatformPutFile( diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp @@ -661,9 +661,10 @@ std::error_code ec; const uint32_t mode = FileSystem::Instance().GetPermissions(file_spec, ec); StreamString response; - response.Printf("F%x", mode); - if (mode == 0 || ec) - response.Printf(",%x", (int)Status(ec).GetError()); + if (mode != llvm::sys::fs::perms_not_known) + response.Printf("F%x", mode); + else + response.Printf("F-1,%x", (int)Status(ec).GetError()); return SendPacketNoLock(response.GetString()); } return SendErrorResponse(23); diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestGDBRemotePlatformFile.py b/lldb/test/API/functionalities/gdb_remote_client/TestGDBRemotePlatformFile.py --- a/lldb/test/API/functionalities/gdb_remote_client/TestGDBRemotePlatformFile.py +++ b/lldb/test/API/functionalities/gdb_remote_client/TestGDBRemotePlatformFile.py @@ -100,3 +100,48 @@ "vFile:fstat:5", "vFile:close:5", ]) + + def test_file_permissions(self): + """Test 'platform get-permissions'""" + + class Responder(MockGDBServerResponder): + def vFile(self, packet): + return "F1a4" + + self.server.responder = Responder() + + self.match("platform get-permissions /some/file.txt", + [r"File permissions of /some/file\.txt \(remote\): 0o0644"]) + self.assertPacketLogContains([ + "vFile:mode:2f736f6d652f66696c652e747874", + ]) + + def test_file_exists(self): + """Test 'platform file-exists'""" + + class Responder(MockGDBServerResponder): + def vFile(self, packet): + return "F,1" + + self.server.responder = Responder() + + self.match("platform file-exists /some/file.txt", + [r"File /some/file\.txt \(remote\) exists"]) + self.assertPacketLogContains([ + "vFile:exists:2f736f6d652f66696c652e747874", + ]) + + def test_file_exists_not(self): + """Test 'platform file-exists' with non-existing file""" + + class Responder(MockGDBServerResponder): + def vFile(self, packet): + return "F,0" + + self.server.responder = Responder() + + self.match("platform file-exists /some/file.txt", + [r"File /some/file\.txt \(remote\) does not exist"]) + self.assertPacketLogContains([ + "vFile:exists:2f736f6d652f66696c652e747874", + ]) diff --git a/lldb/test/API/tools/lldb-server/TestGdbRemotePlatformFile.py b/lldb/test/API/tools/lldb-server/TestGdbRemotePlatformFile.py --- a/lldb/test/API/tools/lldb-server/TestGdbRemotePlatformFile.py +++ b/lldb/test/API/tools/lldb-server/TestGdbRemotePlatformFile.py @@ -176,6 +176,23 @@ context = self.expect_gdbremote_sequence() self.assertEqual(int(context["mode"], 16), test_mode) + @skipIfWindows + @add_test_categories(["llgs"]) + def test_platform_file_mode_fail(self): + server = self.connect_to_debug_monitor() + self.assertIsNotNone(server) + + temp_path = self.getBuildArtifact("nonexist") + + self.do_handshake() + self.test_sequence.add_log_lines( + ["read packet: $vFile:mode:%s#00" % ( + binascii.b2a_hex(temp_path.encode()).decode(),), + {"direction": "send", + "regex": r"^\$F-1,0*2+#[0-9a-fA-F]{2}$"}], + True) + self.expect_gdbremote_sequence() + @skipIfWindows @add_test_categories(["llgs"]) def test_platform_file_exists(self):