1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
2 *
3 * Copyright (C) 2017 Red Hat, Inc.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 *
19 * Author: Vratislav Podzimek <vpodzime@redhat.com>
20 *
21 */
22
23 #include <glib.h>
24 #include <stdio.h>
25 #include <sys/stat.h>
26 #include <sys/types.h>
27 #include <fcntl.h>
28 #include <errno.h>
29 #include <unistd.h>
30 #include <dirent.h>
31 #include <blockdev/fs.h>
32
33 #include "udiskslinuxfilesystemhelpers.h"
34 #include "udiskslogging.h"
35
36
37 static gboolean
recursive_chown(const gchar * path,uid_t caller_uid,gid_t caller_gid,gboolean recursive,GError ** error)38 recursive_chown (const gchar *path,
39 uid_t caller_uid,
40 gid_t caller_gid,
41 gboolean recursive,
42 GError **error)
43 {
44 int dirfd;
45 DIR *dir;
46 struct dirent *dirent;
47 GSList *list, *l;
48
49 g_return_val_if_fail (path != NULL, FALSE);
50
51 if (lchown (path, caller_uid, caller_gid) != 0)
52 {
53 g_set_error (error, UDISKS_ERROR, UDISKS_ERROR_FAILED,
54 "Error changing ownership of %s to uid=%u and gid=%u: %m",
55 path, caller_uid, caller_gid);
56 return FALSE;
57 }
58
59 if (! recursive)
60 return TRUE;
61
62 /* read and traverse through the directory */
63 dirfd = open (path, O_DIRECTORY | O_NOFOLLOW);
64 if (dirfd < 0)
65 {
66 if (errno == ENOTDIR)
67 return TRUE;
68 g_set_error (error, UDISKS_ERROR, UDISKS_ERROR_FAILED,
69 "Error opening directory %s: %m", path);
70 return FALSE;
71 }
72
73 dir = fdopendir (dirfd);
74 if (! dir)
75 {
76 g_set_error (error, UDISKS_ERROR, UDISKS_ERROR_FAILED,
77 "Error opening directory %s: %m", path);
78 close (dirfd);
79 return FALSE;
80 }
81
82 /* build a list of filenames to prevent fd exhaustion */
83 list = NULL;
84 while ((errno = 0, dirent = readdir (dir)))
85 if (g_strcmp0 (dirent->d_name, ".") != 0 && g_strcmp0 (dirent->d_name, "..") != 0)
86 list = g_slist_append (list, g_strdup (dirent->d_name));
87 if (!dirent && errno != 0)
88 {
89 g_set_error (error, UDISKS_ERROR, UDISKS_ERROR_FAILED,
90 "Error reading directory %s: %m", path);
91 closedir (dir);
92 g_slist_free_full (list, g_free);
93 return FALSE;
94 }
95 closedir (dir);
96
97 /* recurse into parents */
98 for (l = list; l; l = g_slist_next (l))
99 {
100 gchar *newpath;
101
102 newpath = g_build_filename (path, l->data, NULL);
103 if (! recursive_chown (newpath, caller_uid, caller_gid, TRUE, error))
104 {
105 g_free (newpath);
106 g_slist_free_full (list, g_free);
107 return FALSE;
108 }
109 g_free (newpath);
110 }
111 g_slist_free_full (list, g_free);
112
113 return TRUE;
114 }
115
116 gboolean
take_filesystem_ownership(const gchar * device,const gchar * fstype,uid_t caller_uid,gid_t caller_gid,gboolean recursive,GError ** error)117 take_filesystem_ownership (const gchar *device,
118 const gchar *fstype,
119 uid_t caller_uid,
120 gid_t caller_gid,
121 gboolean recursive,
122 GError **error)
123
124 {
125 gchar *mountpoint = NULL;
126 GError *local_error = NULL;
127 gboolean unmount = FALSE;
128 gboolean success = TRUE;
129
130 mountpoint = bd_fs_get_mountpoint (device, &local_error);
131 if (mountpoint == NULL)
132 {
133 if (local_error != NULL)
134 {
135 g_set_error (error, UDISKS_ERROR, UDISKS_ERROR_FAILED,
136 "Error when getting mountpoint for %s: %s.",
137 device, local_error->message);
138 g_clear_error (&local_error);
139 success = FALSE;
140 goto out;
141 }
142 else
143 {
144 /* device is not mounted, we need to mount it */
145 mountpoint = g_mkdtemp (g_strdup ("/run/udisks2/temp-mount-XXXXXX"));
146 if (mountpoint == NULL)
147 {
148 g_set_error (error, UDISKS_ERROR, UDISKS_ERROR_FAILED,
149 "Cannot create temporary mountpoint.");
150 success = FALSE;
151 goto out;
152 }
153
154 /* TODO: mount to a private mount namespace */
155 if (!bd_fs_mount (device, mountpoint, fstype, NULL, NULL, &local_error))
156 {
157 g_set_error (error, UDISKS_ERROR, UDISKS_ERROR_FAILED,
158 "Cannot mount %s at %s: %s",
159 device, mountpoint, local_error->message);
160 g_clear_error (&local_error);
161 if (g_rmdir (mountpoint) != 0)
162 udisks_warning ("Error removing temporary mountpoint directory %s.", mountpoint);
163 success = FALSE;
164 goto out;
165 }
166 else
167 unmount = TRUE; /* unmount during cleanup */
168 }
169 }
170
171 /* actual chown */
172 success = recursive_chown (mountpoint, caller_uid, caller_gid, recursive, error);
173 if (! success)
174 goto out;
175
176 if (chmod (mountpoint, 0700) != 0)
177 {
178 g_set_error (error, UDISKS_ERROR, UDISKS_ERROR_FAILED,
179 "Cannot chmod %s to mode 0700: %m",
180 mountpoint);
181 success = FALSE;
182 goto out;
183 }
184
185 out:
186 if (unmount)
187 {
188 if (! bd_fs_unmount (mountpoint, FALSE, FALSE, NULL, &local_error))
189 {
190 udisks_warning ("Error unmounting temporary mountpoint %s: %s",
191 mountpoint, local_error->message);
192 g_clear_error (&local_error);
193 }
194 if (g_rmdir (mountpoint) != 0)
195 udisks_warning ("Error removing temporary mountpoint directory %s.", mountpoint);
196 }
197
198 g_free (mountpoint);
199
200 return success;
201 }
202