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