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 https://opensource.org/licenses/CDDL-1.0.
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 <string.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
zfs_project_item_alloc(list_t * head,const char * name)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
zfs_project_sanity_check(const char * name,zfs_project_control_t * zpc,struct stat * st)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
zfs_project_load_projid(const char * name,zfs_project_control_t * zpc)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
zfs_project_handle_one(const char * name,zfs_project_control_t * zpc)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
zfs_project_handle_dir(const char * name,zfs_project_control_t * zpc,list_t * head)207 zfs_project_handle_dir(const char *name, zfs_project_control_t *zpc,
208 list_t *head)
209 {
210 struct dirent *ent;
211 DIR *dir;
212 int ret = 0;
213
214 dir = opendir(name);
215 if (dir == NULL) {
216 if (errno == ENOENT && zpc->zpc_ignore_noent)
217 return (0);
218
219 ret = -errno;
220 (void) fprintf(stderr, gettext("failed to opendir %s: %s\n"),
221 name, strerror(errno));
222 return (ret);
223 }
224
225 /* Non-top item, ignore the case of being removed or renamed by race. */
226 zpc->zpc_ignore_noent = B_TRUE;
227 errno = 0;
228 while (!ret && (ent = readdir(dir)) != NULL) {
229 char *fullname;
230
231 /* skip "." and ".." */
232 if (strcmp(ent->d_name, ".") == 0 ||
233 strcmp(ent->d_name, "..") == 0)
234 continue;
235
236 if (strlen(ent->d_name) + strlen(name) + 1 >= PATH_MAX) {
237 errno = ENAMETOOLONG;
238 break;
239 }
240
241 if (asprintf(&fullname, "%s/%s", name, ent->d_name) == -1) {
242 errno = ENOMEM;
243 break;
244 }
245
246 ret = zfs_project_handle_one(fullname, zpc);
247 if (!ret && zpc->zpc_recursive && ent->d_type == DT_DIR)
248 zfs_project_item_alloc(head, fullname);
249
250 free(fullname);
251 }
252
253 if (errno && !ret) {
254 ret = -errno;
255 (void) fprintf(stderr, gettext("failed to readdir %s: %s\n"),
256 name, strerror(errno));
257 }
258
259 closedir(dir);
260 return (ret);
261 }
262
263 int
zfs_project_handle(const char * name,zfs_project_control_t * zpc)264 zfs_project_handle(const char *name, zfs_project_control_t *zpc)
265 {
266 zfs_project_item_t *zpi;
267 struct stat st;
268 list_t head;
269 int ret;
270
271 ret = zfs_project_sanity_check(name, zpc, &st);
272 if (ret)
273 return (ret);
274
275 if ((zpc->zpc_op == ZFS_PROJECT_OP_SET ||
276 zpc->zpc_op == ZFS_PROJECT_OP_CHECK) &&
277 zpc->zpc_expected_projid == ZFS_INVALID_PROJID) {
278 ret = zfs_project_load_projid(name, zpc);
279 if (ret)
280 return (ret);
281 }
282
283 zpc->zpc_ignore_noent = B_FALSE;
284 ret = zfs_project_handle_one(name, zpc);
285 if (ret || !S_ISDIR(st.st_mode) || zpc->zpc_dironly ||
286 (!zpc->zpc_recursive &&
287 zpc->zpc_op != ZFS_PROJECT_OP_LIST &&
288 zpc->zpc_op != ZFS_PROJECT_OP_CHECK))
289 return (ret);
290
291 list_create(&head, sizeof (zfs_project_item_t),
292 offsetof(zfs_project_item_t, zpi_list));
293 zfs_project_item_alloc(&head, name);
294 while ((zpi = list_remove_head(&head)) != NULL) {
295 if (!ret)
296 ret = zfs_project_handle_dir(zpi->zpi_name, zpc, &head);
297 free(zpi);
298 }
299
300 return (ret);
301 }
302