[M120 Migration]Fix for crash during chrome exit
[platform/framework/web/chromium-efl.git] / chrome / browser / platform_util_linux.cc
1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/platform_util.h"
6
7 #include <fcntl.h>
8
9 #include <string>
10 #include <vector>
11
12 #include "base/callback_list.h"
13 #include "base/containers/contains.h"
14 #include "base/functional/bind.h"
15 #include "base/functional/callback.h"
16 #include "base/logging.h"
17 #include "base/memory/raw_ptr.h"
18 #include "base/no_destructor.h"
19 #include "base/posix/eintr_wrapper.h"
20 #include "base/process/kill.h"
21 #include "base/process/launch.h"
22 #include "base/strings/string_util.h"
23 #include "base/strings/utf_string_conversions.h"
24 #include "base/threading/scoped_blocking_call.h"
25 #include "chrome/browser/lifetime/termination_notification.h"
26 #include "chrome/browser/platform_util_internal.h"
27 #include "chrome/browser/profiles/profile.h"
28 // This file gets pulled in in Chromecast builds, which causes "gn check" to
29 // complain as Chromecast doesn't use (or depend on) //components/dbus.
30 // TODO(crbug.com/1215474): Eliminate //chrome being visible in the GN structure
31 // on Chromecast and remove the nogncheck below.
32 #include "components/dbus/thread_linux/dbus_thread_linux.h"  // nogncheck
33 #include "content/public/browser/browser_thread.h"
34 #include "dbus/bus.h"
35 #include "dbus/message.h"
36 #include "dbus/object_proxy.h"
37 #include "third_party/abseil-cpp/absl/types/optional.h"
38 #include "url/gurl.h"
39
40 using content::BrowserThread;
41
42 namespace platform_util {
43
44 namespace {
45
46 const char kMethodListActivatableNames[] = "ListActivatableNames";
47 const char kMethodNameHasOwner[] = "NameHasOwner";
48
49 const char kFreedesktopFileManagerName[] = "org.freedesktop.FileManager1";
50 const char kFreedesktopFileManagerPath[] = "/org/freedesktop/FileManager1";
51
52 const char kMethodShowItems[] = "ShowItems";
53
54 const char kFreedesktopPortalName[] = "org.freedesktop.portal.Desktop";
55 const char kFreedesktopPortalPath[] = "/org/freedesktop/portal/desktop";
56 const char kFreedesktopPortalOpenURI[] = "org.freedesktop.portal.OpenURI";
57
58 const char kMethodOpenDirectory[] = "OpenDirectory";
59
60 class ShowItemHelper {
61  public:
62   static ShowItemHelper& GetInstance() {
63     static base::NoDestructor<ShowItemHelper> instance;
64     return *instance;
65   }
66
67   ShowItemHelper()
68       : browser_shutdown_subscription_(
69             browser_shutdown::AddAppTerminatingCallback(
70                 base::BindOnce(&ShowItemHelper::OnAppTerminating,
71                                base::Unretained(this)))) {}
72
73   ShowItemHelper(const ShowItemHelper&) = delete;
74   ShowItemHelper& operator=(const ShowItemHelper&) = delete;
75
76   void ShowItemInFolder(Profile* profile, const base::FilePath& full_path) {
77 #if defined(USE_DBUS)
78     if (!bus_) {
79       // Sets up the D-Bus connection.
80       dbus::Bus::Options bus_options;
81       bus_options.bus_type = dbus::Bus::SESSION;
82       bus_options.connection_type = dbus::Bus::PRIVATE;
83       bus_options.dbus_task_runner = dbus_thread_linux::GetTaskRunner();
84       bus_ = base::MakeRefCounted<dbus::Bus>(bus_options);
85     }
86
87     if (!dbus_proxy_) {
88       dbus_proxy_ = bus_->GetObjectProxy(DBUS_SERVICE_DBUS,
89                                          dbus::ObjectPath(DBUS_PATH_DBUS));
90     }
91
92     if (prefer_filemanager_interface_.has_value()) {
93       if (prefer_filemanager_interface_.value()) {
94         VLOG(1) << "Using FileManager1 to show folder";
95         ShowItemUsingFileManager(profile, full_path);
96       } else {
97         VLOG(1) << "Using OpenURI to show folder";
98         ShowItemUsingFreedesktopPortal(profile, full_path);
99       }
100     } else {
101       CheckFileManagerRunning(profile, full_path);
102     }
103 #endif
104   }
105
106  private:
107   void OnAppTerminating() {
108     DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
109     // The browser process is about to exit. Clean up while we still can.
110 #if defined(USE_DBUS)
111     object_proxy_ = nullptr;
112     dbus_proxy_ = nullptr;
113     if (bus_)
114       bus_->ShutdownOnDBusThreadAndBlock();
115     bus_.reset();
116 #endif
117   }
118
119 #if defined(USE_DBUS)
120   void CheckFileManagerRunning(Profile* profile,
121                                const base::FilePath& full_path) {
122     dbus::MethodCall method_call(DBUS_INTERFACE_DBUS, kMethodNameHasOwner);
123     dbus::MessageWriter writer(&method_call);
124     writer.AppendString(kFreedesktopFileManagerName);
125
126     dbus_proxy_->CallMethod(
127         &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
128         base::BindOnce(&ShowItemHelper::CheckFileManagerRunningResponse,
129                        weak_ptr_factory_.GetWeakPtr(), profile, full_path));
130   }
131
132   void CheckFileManagerRunningResponse(Profile* profile,
133                                        const base::FilePath& full_path,
134                                        dbus::Response* response) {
135     if (prefer_filemanager_interface_.has_value()) {
136       ShowItemInFolder(profile, full_path);
137       return;
138     }
139
140     bool is_running = false;
141
142     if (!response) {
143       LOG(ERROR) << "Failed to call " << kMethodNameHasOwner;
144     } else {
145       dbus::MessageReader reader(response);
146       bool owned = false;
147
148       if (!reader.PopBool(&owned)) {
149         LOG(ERROR) << "Failed to read " << kMethodNameHasOwner << " resposne";
150       } else if (owned) {
151         is_running = true;
152       }
153     }
154
155     if (is_running) {
156       prefer_filemanager_interface_ = true;
157       ShowItemInFolder(profile, full_path);
158     } else {
159       CheckFileManagerActivatable(profile, full_path);
160     }
161   }
162
163   void CheckFileManagerActivatable(Profile* profile,
164                                    const base::FilePath& full_path) {
165     dbus::MethodCall method_call(DBUS_INTERFACE_DBUS,
166                                  kMethodListActivatableNames);
167     dbus_proxy_->CallMethod(
168         &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
169         base::BindOnce(&ShowItemHelper::CheckFileManagerActivatableResponse,
170                        weak_ptr_factory_.GetWeakPtr(), profile, full_path));
171   }
172
173   void CheckFileManagerActivatableResponse(Profile* profile,
174                                            const base::FilePath& full_path,
175                                            dbus::Response* response) {
176     if (prefer_filemanager_interface_.has_value()) {
177       ShowItemInFolder(profile, full_path);
178       return;
179     }
180
181     bool is_activatable = false;
182
183     if (!response) {
184       LOG(ERROR) << "Failed to call " << kMethodListActivatableNames;
185     } else {
186       dbus::MessageReader reader(response);
187       std::vector<std::string> names;
188       if (!reader.PopArrayOfStrings(&names)) {
189         LOG(ERROR) << "Failed to read " << kMethodListActivatableNames
190                    << " response";
191       } else if (base::Contains(names, kFreedesktopFileManagerName)) {
192         is_activatable = true;
193       }
194     }
195
196     prefer_filemanager_interface_ = is_activatable;
197     ShowItemInFolder(profile, full_path);
198   }
199
200   void ShowItemUsingFreedesktopPortal(Profile* profile,
201                                       const base::FilePath& full_path) {
202     if (!object_proxy_) {
203       object_proxy_ = bus_->GetObjectProxy(
204           kFreedesktopPortalName, dbus::ObjectPath(kFreedesktopPortalPath));
205     }
206
207     base::ScopedFD fd(
208         HANDLE_EINTR(open(full_path.value().c_str(), O_RDONLY | O_CLOEXEC)));
209     if (!fd.is_valid()) {
210       PLOG(ERROR) << "Failed to open " << full_path << " for URI portal";
211
212       // At least open the parent folder, as long as we're not in the unit
213       // tests.
214       if (internal::AreShellOperationsAllowed()) {
215         OpenItem(profile, full_path.DirName(), OPEN_FOLDER,
216                  OpenOperationCallback());
217       }
218
219       return;
220     }
221
222     dbus::MethodCall open_directory_call(kFreedesktopPortalOpenURI,
223                                          kMethodOpenDirectory);
224     dbus::MessageWriter writer(&open_directory_call);
225
226     writer.AppendString("");
227
228     // Note that AppendFileDescriptor() duplicates the fd, so we shouldn't
229     // release ownership of it here.
230     writer.AppendFileDescriptor(fd.get());
231
232     dbus::MessageWriter options_writer(nullptr);
233     writer.OpenArray("{sv}", &options_writer);
234     writer.CloseContainer(&options_writer);
235
236     ShowItemUsingBusCall(&open_directory_call, profile, full_path);
237   }
238
239   void ShowItemUsingFileManager(Profile* profile,
240                                 const base::FilePath& full_path) {
241     if (!object_proxy_) {
242       object_proxy_ =
243           bus_->GetObjectProxy(kFreedesktopFileManagerName,
244                                dbus::ObjectPath(kFreedesktopFileManagerPath));
245     }
246
247     dbus::MethodCall show_items_call(kFreedesktopFileManagerName,
248                                      kMethodShowItems);
249     dbus::MessageWriter writer(&show_items_call);
250
251     writer.AppendArrayOfStrings(
252         {"file://" + full_path.value()});  // List of file(s) to highlight.
253     writer.AppendString({});               // startup-id
254
255     ShowItemUsingBusCall(&show_items_call, profile, full_path);
256   }
257
258   void ShowItemUsingBusCall(dbus::MethodCall* call,
259                             Profile* profile,
260                             const base::FilePath& full_path) {
261     // Skip opening the folder during browser tests, to avoid leaving an open
262     // file explorer window behind.
263     if (!internal::AreShellOperationsAllowed())
264       return;
265
266     object_proxy_->CallMethod(
267         call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
268         base::BindOnce(&ShowItemHelper::ShowItemInFolderResponse,
269                        weak_ptr_factory_.GetWeakPtr(), profile, full_path,
270                        call->GetMember()));
271   }
272
273   void ShowItemInFolderResponse(Profile* profile,
274                                 const base::FilePath& full_path,
275                                 const std::string& method,
276                                 dbus::Response* response) {
277     if (response)
278       return;
279
280     LOG(ERROR) << "Error calling " << method;
281     // If the bus call fails, at least open the parent folder.
282     OpenItem(profile, full_path.DirName(), OPEN_FOLDER,
283              OpenOperationCallback());
284   }
285
286   scoped_refptr<dbus::Bus> bus_;
287
288   // These proxy objects are owned by `bus_`.
289   raw_ptr<dbus::ObjectProxy> dbus_proxy_ = nullptr;
290   raw_ptr<dbus::ObjectProxy> object_proxy_ = nullptr;
291
292   absl::optional<bool> prefer_filemanager_interface_;
293
294   base::CallbackListSubscription browser_shutdown_subscription_;
295   base::WeakPtrFactory<ShowItemHelper> weak_ptr_factory_{this};
296 #endif
297 };
298
299 void RunCommand(const std::string& command,
300                 const base::FilePath& working_directory,
301                 const std::string& arg) {
302   std::vector<std::string> argv;
303   argv.push_back(command);
304   argv.push_back(arg);
305
306   base::LaunchOptions options;
307   options.current_directory = working_directory;
308   options.allow_new_privs = true;
309   // xdg-open can fall back on mailcap which eventually might plumb through
310   // to a command that needs a terminal.  Set the environment variable telling
311   // it that we definitely don't have a terminal available and that it should
312   // bring up a new terminal if necessary.  See "man mailcap".
313   options.environment["MM_NOTTTY"] = "1";
314
315   // In Google Chrome, we do not let GNOME's bug-buddy intercept our crashes.
316   // However, we do not want this environment variable to propagate to external
317   // applications. See http://crbug.com/24120
318   char* disable_gnome_bug_buddy = getenv("GNOME_DISABLE_CRASH_DIALOG");
319   if (disable_gnome_bug_buddy &&
320       disable_gnome_bug_buddy == std::string("SET_BY_GOOGLE_CHROME"))
321     options.environment["GNOME_DISABLE_CRASH_DIALOG"] = std::string();
322
323   base::Process process = base::LaunchProcess(argv, options);
324   if (process.IsValid())
325     base::EnsureProcessGetsReaped(std::move(process));
326 }
327
328 void XDGOpen(const base::FilePath& working_directory, const std::string& path) {
329   RunCommand("xdg-open", working_directory, path);
330 }
331
332 void XDGEmail(const std::string& email) {
333   RunCommand("xdg-email", base::FilePath(), email);
334 }
335
336 }  // namespace
337
338 namespace internal {
339
340 void PlatformOpenVerifiedItem(const base::FilePath& path, OpenItemType type) {
341   // May result in an interactive dialog.
342   base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
343                                                 base::BlockingType::MAY_BLOCK);
344   switch (type) {
345     case OPEN_FILE:
346       XDGOpen(path.DirName(), path.value());
347       break;
348     case OPEN_FOLDER:
349       // The utility process checks the working directory prior to the
350       // invocation of xdg-open by changing the current directory into it. This
351       // operation only succeeds if |path| is a directory. Opening "." from
352       // there ensures that the target of the operation is a directory.  Note
353       // that there remains a TOCTOU race where the directory could be unlinked
354       // between the time the utility process changes into the directory and the
355       // time the application invoked by xdg-open inspects the path by name.
356       XDGOpen(path, ".");
357       break;
358   }
359 }
360
361 }  // namespace internal
362
363 void ShowItemInFolder(Profile* profile, const base::FilePath& full_path) {
364   DCHECK_CURRENTLY_ON(BrowserThread::UI);
365   ShowItemHelper::GetInstance().ShowItemInFolder(profile, full_path);
366 }
367
368 void OpenExternal(const GURL& url) {
369   DCHECK_CURRENTLY_ON(BrowserThread::UI);
370   if (url.SchemeIs("mailto"))
371     XDGEmail(url.spec());
372   else
373     XDGOpen(base::FilePath(), url.spec());
374 }
375
376 }  // namespace platform_util