xref: /illumos-gate/usr/src/cmd/zfs/zfs_project.c (revision 8a2b682e57a046b828f37bcde1776f131ef4629f)
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  * Copyright 2019 Joyent, Inc.
25  */
26 
27 #include <errno.h>
28 #include <getopt.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <strings.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 <limits.h>
41 #include <sys/debug.h>
42 #include <sys/stat.h>
43 #include <sys/zfs_project.h>
44 
45 #include "zfs_util.h"
46 #include "zfs_projectutil.h"
47 
48 typedef struct zfs_project_item {
49 	list_node_t	zpi_list;
50 	char		zpi_name[0];
51 } zfs_project_item_t;
52 
53 static void
54 zfs_project_item_alloc(list_t *head, const char *name)
55 {
56 	zfs_project_item_t *zpi;
57 
58 	zpi = safe_malloc(sizeof (zfs_project_item_t) + strlen(name) + 1);
59 	(void) strcpy(zpi->zpi_name, name);
60 	list_insert_tail(head, zpi);
61 }
62 
63 static int
64 zfs_project_sanity_check(const char *name, zfs_project_control_t *zpc,
65     struct stat *st)
66 {
67 	int ret;
68 
69 	ret = stat(name, st);
70 	if (ret) {
71 		(void) fprintf(stderr, gettext("failed to stat %s: %s\n"),
72 		    name, strerror(errno));
73 		return (ret);
74 	}
75 
76 	if (!S_ISREG(st->st_mode) && !S_ISDIR(st->st_mode)) {
77 		(void) fprintf(stderr, gettext("only support project quota on "
78 		    "regular file or directory\n"));
79 		return (-1);
80 	}
81 
82 	if (!S_ISDIR(st->st_mode)) {
83 		if (zpc->zpc_dironly) {
84 			(void) fprintf(stderr, gettext(
85 			    "'-d' option on non-dir target %s\n"), name);
86 			return (-1);
87 		}
88 
89 		if (zpc->zpc_recursive) {
90 			(void) fprintf(stderr, gettext(
91 			    "'-r' option on non-dir target %s\n"), name);
92 			return (-1);
93 		}
94 	}
95 
96 	return (0);
97 }
98 
99 static int
100 zfs_project_load_projid(const char *name, zfs_project_control_t *zpc)
101 {
102 	zfsxattr_t fsx;
103 	int ret, fd;
104 
105 	fd = open(name, O_RDONLY | O_NOCTTY);
106 	if (fd < 0) {
107 		(void) fprintf(stderr, gettext("failed to open %s: %s\n"),
108 		    name, strerror(errno));
109 		return (fd);
110 	}
111 
112 	ret = ioctl(fd, ZFS_IOC_FSGETXATTR, &fsx);
113 	if (ret)
114 		(void) fprintf(stderr,
115 		    gettext("failed to get xattr for %s: %s\n"),
116 		    name, strerror(errno));
117 	else
118 		zpc->zpc_expected_projid = fsx.fsx_projid;
119 
120 	(void) close(fd);
121 	return (ret);
122 }
123 
124 static int
125 zfs_project_handle_one(const char *name, zfs_project_control_t *zpc)
126 {
127 	zfsxattr_t fsx;
128 	int ret, fd;
129 
130 	fd = open(name, O_RDONLY | O_NOCTTY);
131 	if (fd < 0) {
132 		if (errno == ENOENT && zpc->zpc_ignore_noent)
133 			return (0);
134 
135 		(void) fprintf(stderr, gettext("failed to open %s: %s\n"),
136 		    name, strerror(errno));
137 		return (fd);
138 	}
139 
140 	ret = ioctl(fd, ZFS_IOC_FSGETXATTR, &fsx);
141 	if (ret) {
142 		(void) fprintf(stderr,
143 		    gettext("failed to get xattr for %s: %s\n"),
144 		    name, strerror(errno));
145 		goto out;
146 	}
147 
148 	switch (zpc->zpc_op) {
149 	case ZFS_PROJECT_OP_LIST:
150 		(void) printf("%5u %c %s\n", fsx.fsx_projid,
151 		    (fsx.fsx_xflags & ZFS_PROJINHERIT_FL) ? 'P' : '-', name);
152 		goto out;
153 	case ZFS_PROJECT_OP_CHECK:
154 		if (fsx.fsx_projid == zpc->zpc_expected_projid &&
155 		    fsx.fsx_xflags & ZFS_PROJINHERIT_FL)
156 			goto out;
157 
158 		if (!zpc->zpc_newline) {
159 			char c = '\0';
160 
161 			(void) printf("%s%c", name, c);
162 			goto out;
163 		}
164 
165 		if (fsx.fsx_projid != zpc->zpc_expected_projid)
166 			(void) printf("%s - project ID is not set properly "
167 			    "(%u/%u)\n", name, fsx.fsx_projid,
168 			    (uint32_t)zpc->zpc_expected_projid);
169 
170 		if (!(fsx.fsx_xflags & ZFS_PROJINHERIT_FL))
171 			(void) printf("%s - project inherit flag is not set\n",
172 			    name);
173 
174 		goto out;
175 	case ZFS_PROJECT_OP_CLEAR:
176 		if (!(fsx.fsx_xflags & ZFS_PROJINHERIT_FL) &&
177 		    (zpc->zpc_keep_projid ||
178 		    fsx.fsx_projid == ZFS_DEFAULT_PROJID))
179 			goto out;
180 
181 		fsx.fsx_xflags &= ~ZFS_PROJINHERIT_FL;
182 		if (!zpc->zpc_keep_projid)
183 			fsx.fsx_projid = ZFS_DEFAULT_PROJID;
184 		break;
185 	case ZFS_PROJECT_OP_SET:
186 		if (fsx.fsx_projid == zpc->zpc_expected_projid &&
187 		    (!zpc->zpc_set_flag || fsx.fsx_xflags & ZFS_PROJINHERIT_FL))
188 			goto out;
189 
190 		fsx.fsx_projid = zpc->zpc_expected_projid;
191 		if (zpc->zpc_set_flag)
192 			fsx.fsx_xflags |= ZFS_PROJINHERIT_FL;
193 		break;
194 	default:
195 		ASSERT(0);
196 		break;
197 	}
198 
199 	ret = ioctl(fd, ZFS_IOC_FSSETXATTR, &fsx);
200 	if (ret)
201 		(void) fprintf(stderr,
202 		    gettext("failed to set xattr for %s: %s\n"),
203 		    name, strerror(errno));
204 
205 out:
206 	(void) close(fd);
207 	return (ret);
208 }
209 
210 static int
211 zfs_project_handle_dir(const char *name, zfs_project_control_t *zpc,
212     list_t *head)
213 {
214 	char fullname[PATH_MAX];
215 	struct dirent *ent;
216 	DIR *dir;
217 	int ret = 0;
218 
219 	dir = opendir(name);
220 	if (dir == NULL) {
221 		if (errno == ENOENT && zpc->zpc_ignore_noent)
222 			return (0);
223 
224 		ret = -errno;
225 		(void) fprintf(stderr, gettext("failed to opendir %s: %s\n"),
226 		    name, strerror(errno));
227 		return (ret);
228 	}
229 
230 	/* Non-top item, ignore the case of being removed or renamed by race. */
231 	zpc->zpc_ignore_noent = B_TRUE;
232 	errno = 0;
233 	while (!ret && (ent = readdir(dir)) != NULL) {
234 		/* skip "." and ".." */
235 		if (strcmp(ent->d_name, ".") == 0 ||
236 		    strcmp(ent->d_name, "..") == 0)
237 			continue;
238 
239 		if (strlen(ent->d_name) + strlen(name) >=
240 		    sizeof (fullname) + 1) {
241 			errno = ENAMETOOLONG;
242 			break;
243 		}
244 
245 		(void) sprintf(fullname, "%s/%s", name, ent->d_name);
246 		ret = zfs_project_handle_one(fullname, zpc);
247 		if (!ret && zpc->zpc_recursive) {
248 			struct stat64 sb;
249 
250 			if (stat64(fullname, &sb) == 0 &&
251 			    (sb.st_mode & S_IFMT) == S_IFDIR)
252 				zfs_project_item_alloc(head, fullname);
253 		}
254 	}
255 
256 	if (errno && !ret) {
257 		ret = -errno;
258 		(void) fprintf(stderr, gettext("failed to readdir %s: %s\n"),
259 		    name, strerror(errno));
260 	}
261 
262 	(void) closedir(dir);
263 	return (ret);
264 }
265 
266 int
267 zfs_project_handle(const char *name, zfs_project_control_t *zpc)
268 {
269 	zfs_project_item_t *zpi;
270 	struct stat st;
271 	list_t head;
272 	int ret;
273 
274 	ret = zfs_project_sanity_check(name, zpc, &st);
275 	if (ret)
276 		return (ret);
277 
278 	if ((zpc->zpc_op == ZFS_PROJECT_OP_SET ||
279 	    zpc->zpc_op == ZFS_PROJECT_OP_CHECK) &&
280 	    zpc->zpc_expected_projid == ZFS_INVALID_PROJID) {
281 		ret = zfs_project_load_projid(name, zpc);
282 		if (ret)
283 			return (ret);
284 	}
285 
286 	zpc->zpc_ignore_noent = B_FALSE;
287 	ret = zfs_project_handle_one(name, zpc);
288 	if (ret || !S_ISDIR(st.st_mode) || zpc->zpc_dironly ||
289 	    (!zpc->zpc_recursive &&
290 	    zpc->zpc_op != ZFS_PROJECT_OP_LIST &&
291 	    zpc->zpc_op != ZFS_PROJECT_OP_CHECK))
292 		return (ret);
293 
294 	list_create(&head, sizeof (zfs_project_item_t),
295 	    offsetof(zfs_project_item_t, zpi_list));
296 	zfs_project_item_alloc(&head, name);
297 	while ((zpi = list_remove_head(&head)) != NULL) {
298 		if (!ret)
299 			ret = zfs_project_handle_dir(zpi->zpi_name, zpc, &head);
300 		free(zpi);
301 	}
302 
303 	return (ret);
304 }
305