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
zfs_project_item_alloc(list_t * head,const char * name)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
zfs_project_sanity_check(const char * name,zfs_project_control_t * zpc,struct stat * st)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
zfs_project_load_projid(const char * name,zfs_project_control_t * zpc)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
zfs_project_handle_one(const char * name,zfs_project_control_t * zpc)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
zfs_project_handle_dir(const char * name,zfs_project_control_t * zpc,list_t * head)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
zfs_project_handle(const char * name,zfs_project_control_t * zpc)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