xref: /freebsd/sys/contrib/openzfs/cmd/zfs/zfs_project.c (revision 61145dc2b94f12f6a47344fb9aac702321880e43)
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
zfs_project_item_alloc(list_t * head,const char * name)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
zfs_project_sanity_check(const char * name,zfs_project_control_t * zpc,struct stat * st)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
zfs_project_load_projid(const char * name,zfs_project_control_t * zpc)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
zfs_project_handle_one(const char * name,zfs_project_control_t * zpc)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 & ZFS_PROJINHERIT_FL) ? 'P' : '-', name);
149 		goto out;
150 	case ZFS_PROJECT_OP_CHECK:
151 		if (fsx.fsx_projid == zpc->zpc_expected_projid &&
152 		    fsx.fsx_xflags & ZFS_PROJINHERIT_FL)
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 & ZFS_PROJINHERIT_FL))
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 & ZFS_PROJINHERIT_FL) &&
174 		    (zpc->zpc_keep_projid ||
175 		    fsx.fsx_projid == ZFS_DEFAULT_PROJID))
176 			goto out;
177 
178 		fsx.fsx_xflags &= ~ZFS_PROJINHERIT_FL;
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 || fsx.fsx_xflags & ZFS_PROJINHERIT_FL))
185 			goto out;
186 
187 		fsx.fsx_projid = zpc->zpc_expected_projid;
188 		if (zpc->zpc_set_flag)
189 			fsx.fsx_xflags |= ZFS_PROJINHERIT_FL;
190 		break;
191 	default:
192 		ASSERT(0);
193 		break;
194 	}
195 
196 	ret = ioctl(fd, ZFS_IOC_FSSETXATTR, &fsx);
197 	if (ret)
198 		(void) fprintf(stderr,
199 		    gettext("failed to set xattr for %s: %s\n"),
200 		    name, strerror(errno));
201 
202 out:
203 	close(fd);
204 	return (ret);
205 }
206 
207 static int
zfs_project_handle_dir(const char * name,zfs_project_control_t * zpc,list_t * head)208 zfs_project_handle_dir(const char *name, zfs_project_control_t *zpc,
209     list_t *head)
210 {
211 	struct dirent *ent;
212 	DIR *dir;
213 	int ret = 0;
214 
215 	dir = opendir(name);
216 	if (dir == NULL) {
217 		if (errno == ENOENT && zpc->zpc_ignore_noent)
218 			return (0);
219 
220 		ret = -errno;
221 		(void) fprintf(stderr, gettext("failed to opendir %s: %s\n"),
222 		    name, strerror(errno));
223 		return (ret);
224 	}
225 
226 	/* Non-top item, ignore the case of being removed or renamed by race. */
227 	zpc->zpc_ignore_noent = B_TRUE;
228 	errno = 0;
229 	while (!ret && (ent = readdir(dir)) != NULL) {
230 		char *fullname;
231 
232 		/* skip "." and ".." */
233 		if (strcmp(ent->d_name, ".") == 0 ||
234 		    strcmp(ent->d_name, "..") == 0)
235 			continue;
236 
237 		if (strlen(ent->d_name) + strlen(name) + 1 >= PATH_MAX) {
238 			errno = ENAMETOOLONG;
239 			break;
240 		}
241 
242 		if (asprintf(&fullname, "%s/%s", name, ent->d_name) == -1) {
243 			errno = ENOMEM;
244 			break;
245 		}
246 
247 		ret = zfs_project_handle_one(fullname, zpc);
248 		if (!ret && zpc->zpc_recursive && ent->d_type == DT_DIR)
249 			zfs_project_item_alloc(head, fullname);
250 
251 		free(fullname);
252 	}
253 
254 	if (errno && !ret) {
255 		ret = -errno;
256 		(void) fprintf(stderr, gettext("failed to readdir %s: %s\n"),
257 		    name, strerror(errno));
258 	}
259 
260 	closedir(dir);
261 	return (ret);
262 }
263 
264 int
zfs_project_handle(const char * name,zfs_project_control_t * zpc)265 zfs_project_handle(const char *name, zfs_project_control_t *zpc)
266 {
267 	zfs_project_item_t *zpi;
268 	struct stat st;
269 	list_t head;
270 	int ret;
271 
272 	ret = zfs_project_sanity_check(name, zpc, &st);
273 	if (ret)
274 		return (ret);
275 
276 	if ((zpc->zpc_op == ZFS_PROJECT_OP_SET ||
277 	    zpc->zpc_op == ZFS_PROJECT_OP_CHECK) &&
278 	    zpc->zpc_expected_projid == ZFS_INVALID_PROJID) {
279 		ret = zfs_project_load_projid(name, zpc);
280 		if (ret)
281 			return (ret);
282 	}
283 
284 	zpc->zpc_ignore_noent = B_FALSE;
285 	ret = zfs_project_handle_one(name, zpc);
286 	if (ret || !S_ISDIR(st.st_mode) || zpc->zpc_dironly ||
287 	    (!zpc->zpc_recursive &&
288 	    zpc->zpc_op != ZFS_PROJECT_OP_LIST &&
289 	    zpc->zpc_op != ZFS_PROJECT_OP_CHECK))
290 		return (ret);
291 
292 	list_create(&head, sizeof (zfs_project_item_t),
293 	    offsetof(zfs_project_item_t, zpi_list));
294 	zfs_project_item_alloc(&head, name);
295 	while ((zpi = list_remove_head(&head)) != NULL) {
296 		if (!ret)
297 			ret = zfs_project_handle_dir(zpi->zpi_name, zpc, &head);
298 		free(zpi);
299 	}
300 
301 	return (ret);
302 }
303