1 /*
2  * Copyright (C) 2019, Matthias Clasen
3  *
4  * This file is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License as
6  * published by the Free Software Foundation, version 3.0 of the
7  * License.
8  *
9  * This file is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  *
17  * SPDX-License-Identifier: LGPL-3.0-only
18  */
19 
20 #include "config.h"
21 
22 #include "trash.h"
23 
24 #define GNU_SOURCE 1
25 
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <fcntl.h>
29 
30 #include <glib/gstdio.h>
31 #include <gio/gunixfdlist.h>
32 
33 #include "portal-private.h"
34 
35 typedef struct {
36   XdpPortal *portal;
37   char *path;
38   GTask *task;
39 } TrashCall;
40 
41 static void
trash_call_free(TrashCall * call)42 trash_call_free (TrashCall *call)
43 {
44   g_object_unref (call->portal);
45   g_object_unref (call->task);
46   g_free (call->path);
47 
48   g_free (call);
49 }
50 
51 #ifndef O_PATH
52 #define O_PATH 0
53 #endif
54 
55 static void
file_trashed(GObject * bus,GAsyncResult * result,gpointer data)56 file_trashed (GObject      *bus,
57               GAsyncResult *result,
58               gpointer      data)
59 {
60   GError *error = NULL;
61   g_autoptr(GVariant) ret = NULL;
62   TrashCall *call = data;
63 
64   ret = g_dbus_connection_call_with_unix_fd_list_finish (G_DBUS_CONNECTION (bus),
65                                                          NULL,
66                                                          result,
67                                                          &error);
68   if (error)
69     g_task_return_error (call->task, error);
70   else
71     {
72       guint retval;
73 
74       g_variant_get (ret, "(u)", &retval);
75 
76       if (retval == 1)
77         g_task_return_boolean (call->task, TRUE);
78       else
79         g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to trash");
80     }
81 
82   trash_call_free (call);
83 }
84 
85 static void
trash_file(TrashCall * call)86 trash_file (TrashCall *call)
87 {
88   g_autoptr(GUnixFDList) fd_list = NULL;
89   int fd, fd_in;
90   GCancellable *cancellable;
91 
92   fd = g_open (call->path, O_PATH | O_CLOEXEC);
93   if (fd == -1)
94     {
95       g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to open '%s'", call->path);
96       trash_call_free (call);
97       return;
98     }
99 
100   fd_list = g_unix_fd_list_new_from_array (&fd, 1);
101   fd = -1;
102   fd_in = 0;
103 
104   cancellable = g_task_get_cancellable (call->task);
105 
106   g_dbus_connection_call_with_unix_fd_list (call->portal->bus,
107                                             PORTAL_BUS_NAME,
108                                             PORTAL_OBJECT_PATH,
109                                             "org.freedesktop.portal.Trash",
110                                             "TrashFile",
111                                             g_variant_new ("(h)", fd_in),
112                                             NULL,
113                                             G_DBUS_CALL_FLAGS_NONE,
114                                             -1,
115                                             fd_list,
116                                             cancellable,
117                                             file_trashed,
118                                             call);
119 
120 }
121 
122 /**
123  * xdp_portal_trash_file:
124  * @portal: a [class@Portal]
125  * @path: the path for a local file
126  * @cancellable: (nullable): optional [class@Gio.Cancellable]
127  * @callback: (scope async): a callback to call when the request is done
128  * @data: (closure): data to pass to @callback
129  *
130  * Sends the file at @path to the trash can.
131  */
132 void
xdp_portal_trash_file(XdpPortal * portal,const char * path,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer data)133 xdp_portal_trash_file (XdpPortal           *portal,
134                        const char          *path,
135                        GCancellable        *cancellable,
136                        GAsyncReadyCallback  callback,
137                        gpointer             data)
138 
139 {
140   TrashCall *call;
141 
142   g_return_if_fail (XDP_IS_PORTAL (portal));
143   g_return_if_fail (path != NULL);
144 
145   call = g_new0 (TrashCall, 1);
146   call->portal = g_object_ref (portal);
147   call->path = g_strdup (path);
148   call->task = g_task_new (portal, cancellable, callback, data);
149   g_task_set_source_tag (call->task, xdp_portal_trash_file);
150 
151   trash_file (call);
152 }
153 
154 /**
155  * xdp_portal_trash_file_finish:
156  * @portal: a [class@Portal]
157  * @result: a [iface@Gio.AsyncResult]
158  * @error: return location for an error
159  *
160  * Finishes the trash-file request, and returns
161  * the result in the form of a boolean.
162  *
163  * Returns: `TRUE` if the call succeeded
164  */
165 gboolean
xdp_portal_trash_file_finish(XdpPortal * portal,GAsyncResult * result,GError ** error)166 xdp_portal_trash_file_finish (XdpPortal *portal,
167                               GAsyncResult *result,
168                               GError **error)
169 {
170   g_return_val_if_fail (XDP_IS_PORTAL (portal), FALSE);
171   g_return_val_if_fail (g_task_is_valid (result, portal), FALSE);
172   g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == xdp_portal_trash_file, FALSE);
173 
174   return g_task_propagate_boolean (G_TASK (result), error);
175 }
176