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