xref: /freebsd/sys/contrib/openzfs/cmd/zfs/zfs_project.c (revision 8ccc0d235c226d84112561d453c49904398d085c)
1 // SPDX-License-Identifier: CDDL-1.0
2 /*
3  * CDDL HEADER START
4  *
5  * The contents of this file are subject to the terms of the
6  * Common Development and Distribution License (the "License").
7  * You may not use this file except in compliance with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or https://opensource.org/licenses/CDDL-1.0.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 
23 /*
24  * Copyright (c) 2017, Intle Corporation. All rights reserved.
25  */
26 
27 #include <errno.h>
28 #include <getopt.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <unistd.h>
33 #include <fcntl.h>
34 #include <dirent.h>
35 #include <stddef.h>
36 #include <libintl.h>
37 #include <sys/stat.h>
38 #include <sys/types.h>
39 #include <sys/list.h>
40 #include <sys/zfs_project.h>
41 
42 #include "zfs_util.h"
43 #include "zfs_projectutil.h"
44 
45 typedef struct zfs_project_item {
46 	list_node_t	zpi_list;
47 	char		zpi_name[0];
48 } zfs_project_item_t;
49 
50 static void
51 zfs_project_item_alloc(list_t *head, const char *name)
52 {
53 	zfs_project_item_t *zpi;
54 
55 	zpi = safe_malloc(sizeof (zfs_project_item_t) + strlen(name) + 1);
56 	strcpy(zpi->zpi_name, name);
57 	list_insert_tail(head, zpi);
58 }
59 
60 static int
61 zfs_project_sanity_check(const char *name, zfs_project_control_t *zpc,
62     struct stat *st)
63 {
64 	int ret;
65 
66 	ret = stat(name, st);
67 	if (ret) {
68 		(void) fprintf(stderr, gettext("failed to stat %s: %s\n"),
69 		    name, strerror(errno));
70 		return (ret);
71 	}
72 
73 	if (!S_ISREG(st->st_mode) && !S_ISDIR(st->st_mode)) {
74 		(void) fprintf(stderr, gettext("only support project quota on "
75 		    "regular file or directory\n"));
76 		return (-1);
77 	}
78 
79 	if (!S_ISDIR(st->st_mode)) {
80 		if (zpc->zpc_dironly) {
81 			(void) fprintf(stderr, gettext(
82 			    "'-d' option on non-dir target %s\n"), name);
83 			return (-1);
84 		}
85 
86 		if (zpc->zpc_recursive) {
87 			(void) fprintf(stderr, gettext(
88 			    "'-r' option on non-dir target %s\n"), name);
89 			return (-1);
90 		}
91 	}
92 
93 	return (0);
94 }
95 
96 static int
97 zfs_project_load_projid(const char *name, zfs_project_control_t *zpc)
98 {
99 	zfsxattr_t fsx;
100 	int ret, fd;
101 
102 	fd = open(name, O_RDONLY | O_NOCTTY);
103 	if (fd < 0) {
104 		(void) fprintf(stderr, gettext("failed to open %s: %s\n"),
105 		    name, strerror(errno));
106 		return (fd);
107 	}
108 
109 	ret = ioctl(fd, ZFS_IOC_FSGETXATTR, &fsx);
110 	if (ret)
111 		(void) fprintf(stderr,
112 		    gettext("failed to get xattr for %s: %s\n"),
113 		    name, strerror(errno));
114 	else
115 		zpc->zpc_expected_projid = fsx.fsx_projid;
116 
117 	close(fd);
118 	return (ret);
119 }
120 
121 static int
122 zfs_project_handle_one(const char *name, zfs_project_control_t *zpc)
123 {
124 	zfsxattr_t fsx;
125 	int ret, fd;
126 
127 	fd = open(name, O_RDONLY | O_NOCTTY);
128 	if (fd < 0) {
129 		if (errno == ENOENT && zpc->zpc_ignore_noent)
130 			return (0);
131 
132 		(void) fprintf(stderr, gettext("failed to open %s: %s\n"),
133 		    name, strerror(errno));
134 		return (fd);
135 	}
136 
137 	ret = ioctl(fd, ZFS_IOC_FSGETXATTR, &fsx);
138 	if (ret) {
139 		(void) fprintf(stderr,
140 		    gettext("failed to get xattr for %s: %s\n"),
141 		    name, strerror(errno));
142 		goto out;
143 	}
144 
145 	switch (zpc->zpc_op) {
146 	case ZFS_PROJECT_OP_LIST:
147 		(void) printf("%5u %c %s\n", fsx.fsx_projid,
148 		    (fsx.fsx_xflags & FS_XFLAG_PROJINHERIT) ? 'P' : '-', name);
149 		goto out;
150 	case ZFS_PROJECT_OP_CHECK:
151 		if (fsx.fsx_projid == zpc->zpc_expected_projid &&
152 		    fsx.fsx_xflags & FS_XFLAG_PROJINHERIT)
153 			goto out;
154 
155 		if (!zpc->zpc_newline) {
156 			char c = '\0';
157 
158 			(void) printf("%s%c", name, c);
159 			goto out;
160 		}
161 
162 		if (fsx.fsx_projid != zpc->zpc_expected_projid)
163 			(void) printf("%s - project ID is not set properly "
164 			    "(%u/%u)\n", name, fsx.fsx_projid,
165 			    (uint32_t)zpc->zpc_expected_projid);
166 
167 		if (!(fsx.fsx_xflags & FS_XFLAG_PROJINHERIT))
168 			(void) printf("%s - project inherit flag is not set\n",
169 			    name);
170 
171 		goto out;
172 	case ZFS_PROJECT_OP_CLEAR:
173 		if (!(fsx.fsx_xflags & FS_XFLAG_PROJINHERIT) &&
174 		    (zpc->zpc_keep_projid ||
175 		    fsx.fsx_projid == ZFS_DEFAULT_PROJID))
176 			goto out;
177 
178 		fsx.fsx_xflags &= ~FS_XFLAG_PROJINHERIT;
179 		if (!zpc->zpc_keep_projid)
180 			fsx.fsx_projid = ZFS_DEFAULT_PROJID;
181 		break;
182 	case ZFS_PROJECT_OP_SET:
183 		if (fsx.fsx_projid == zpc->zpc_expected_projid &&
184 		    (!zpc->zpc_set_flag ||
185 		    fsx.fsx_xflags & FS_XFLAG_PROJINHERIT))
186 			goto out;
187 
188 		fsx.fsx_projid = zpc->zpc_expected_projid;
189 		if (zpc->zpc_set_flag)
190 			fsx.fsx_xflags |= FS_XFLAG_PROJINHERIT;
191 		break;
192 	default:
193 		ASSERT(0);
194 		break;
195 	}
196 
197 	ret = ioctl(fd, ZFS_IOC_FSSETXATTR, &fsx);
198 	if (ret) {
199 		(void) fprintf(stderr,
200 		    gettext("failed to set xattr for %s: %s\n"),
201 		    name, strerror(errno));
202 
203 		if (errno == ENOTSUP) {
204 			char *kver = zfs_version_kernel();
205 			/*
206 			 * Special case: a module/userspace version mismatch can
207 			 * return ENOTSUP due to us fixing the XFLAGs bits in
208 			 * #17884.  In that case give a hint to the user that
209 			 * they should take action to make the versions match.
210 			 */
211 			if (strcmp(kver, ZFS_META_ALIAS) != 0) {
212 				fprintf(stderr,
213 				    gettext("Warning: The zfs module version "
214 				    "(%s) and userspace\nversion (%s) do not "
215 				    "match up.  This may be the\ncause of the "
216 				    "\"Operation not supported\" error.\n"),
217 				    kver, ZFS_META_ALIAS);
218 			}
219 		}
220 	}
221 
222 out:
223 	close(fd);
224 	return (ret);
225 }
226 
227 static int
228 zfs_project_handle_dir(const char *name, zfs_project_control_t *zpc,
229     list_t *head)
230 {
231 	struct dirent *ent;
232 	DIR *dir;
233 	int ret = 0;
234 
235 	dir = opendir(name);
236 	if (dir == NULL) {
237 		if (errno == ENOENT && zpc->zpc_ignore_noent)
238 			return (0);
239 
240 		ret = -errno;
241 		(void) fprintf(stderr, gettext("failed to opendir %s: %s\n"),
242 		    name, strerror(errno));
243 		return (ret);
244 	}
245 
246 	/* Non-top item, ignore the case of being removed or renamed by race. */
247 	zpc->zpc_ignore_noent = B_TRUE;
248 	errno = 0;
249 	while (!ret && (ent = readdir(dir)) != NULL) {
250 		char *fullname;
251 
252 		/* skip "." and ".." */
253 		if (strcmp(ent->d_name, ".") == 0 ||
254 		    strcmp(ent->d_name, "..") == 0)
255 			continue;
256 
257 		if (strlen(ent->d_name) + strlen(name) + 1 >= PATH_MAX) {
258 			errno = ENAMETOOLONG;
259 			break;
260 		}
261 
262 		if (asprintf(&fullname, "%s/%s", name, ent->d_name) == -1) {
263 			errno = ENOMEM;
264 			break;
265 		}
266 
267 		ret = zfs_project_handle_one(fullname, zpc);
268 		if (!ret && zpc->zpc_recursive && ent->d_type == DT_DIR)
269 			zfs_project_item_alloc(head, fullname);
270 
271 		free(fullname);
272 	}
273 
274 	if (errno && !ret) {
275 		ret = -errno;
276 		(void) fprintf(stderr, gettext("failed to readdir %s: %s\n"),
277 		    name, strerror(errno));
278 	}
279 
280 	closedir(dir);
281 	return (ret);
282 }
283 
284 int
285 zfs_project_handle(const char *name, zfs_project_control_t *zpc)
286 {
287 	zfs_project_item_t *zpi;
288 	struct stat st;
289 	list_t head;
290 	int ret;
291 
292 	ret = zfs_project_sanity_check(name, zpc, &st);
293 	if (ret)
294 		return (ret);
295 
296 	if ((zpc->zpc_op == ZFS_PROJECT_OP_SET ||
297 	    zpc->zpc_op == ZFS_PROJECT_OP_CHECK) &&
298 	    zpc->zpc_expected_projid == ZFS_INVALID_PROJID) {
299 		ret = zfs_project_load_projid(name, zpc);
300 		if (ret)
301 			return (ret);
302 	}
303 
304 	zpc->zpc_ignore_noent = B_FALSE;
305 	ret = zfs_project_handle_one(name, zpc);
306 	if (ret || !S_ISDIR(st.st_mode) || zpc->zpc_dironly ||
307 	    (!zpc->zpc_recursive &&
308 	    zpc->zpc_op != ZFS_PROJECT_OP_LIST &&
309 	    zpc->zpc_op != ZFS_PROJECT_OP_CHECK))
310 		return (ret);
311 
312 	list_create(&head, sizeof (zfs_project_item_t),
313 	    offsetof(zfs_project_item_t, zpi_list));
314 	zfs_project_item_alloc(&head, name);
315 	while ((zpi = list_remove_head(&head)) != NULL) {
316 		if (!ret)
317 			ret = zfs_project_handle_dir(zpi->zpi_name, zpc, &head);
318 		free(zpi);
319 	}
320 
321 	return (ret);
322 }
323