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 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 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 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 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 & FS_XFLAG_PROJINHERIT) ? 'P' : '-', name); 149 goto out; 150 case ZFS_PROJECT_OP_CHECK: 151 if (fsx.fsx_projid == zpc->zpc_expected_projid && 152 fsx.fsx_xflags & FS_XFLAG_PROJINHERIT) 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 & FS_XFLAG_PROJINHERIT)) 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 & FS_XFLAG_PROJINHERIT) && 174 (zpc->zpc_keep_projid || 175 fsx.fsx_projid == ZFS_DEFAULT_PROJID)) 176 goto out; 177 178 fsx.fsx_xflags &= ~FS_XFLAG_PROJINHERIT; 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 || 185 fsx.fsx_xflags & FS_XFLAG_PROJINHERIT)) 186 goto out; 187 188 fsx.fsx_projid = zpc->zpc_expected_projid; 189 if (zpc->zpc_set_flag) 190 fsx.fsx_xflags |= FS_XFLAG_PROJINHERIT; 191 break; 192 default: 193 ASSERT(0); 194 break; 195 } 196 197 ret = ioctl(fd, ZFS_IOC_FSSETXATTR, &fsx); 198 if (ret) { 199 (void) fprintf(stderr, 200 gettext("failed to set xattr for %s: %s\n"), 201 name, strerror(errno)); 202 203 if (errno == ENOTSUP) { 204 char *kver = zfs_version_kernel(); 205 /* 206 * Special case: a module/userspace version mismatch can 207 * return ENOTSUP due to us fixing the XFLAGs bits in 208 * #17884. In that case give a hint to the user that 209 * they should take action to make the versions match. 210 */ 211 if (strcmp(kver, ZFS_META_ALIAS) != 0) { 212 fprintf(stderr, 213 gettext("Warning: The zfs module version " 214 "(%s) and userspace\nversion (%s) do not " 215 "match up. This may be the\ncause of the " 216 "\"Operation not supported\" error.\n"), 217 kver, ZFS_META_ALIAS); 218 } 219 } 220 } 221 222 out: 223 close(fd); 224 return (ret); 225 } 226 227 static int 228 zfs_project_handle_dir(const char *name, zfs_project_control_t *zpc, 229 list_t *head) 230 { 231 struct dirent *ent; 232 DIR *dir; 233 int ret = 0; 234 235 dir = opendir(name); 236 if (dir == NULL) { 237 if (errno == ENOENT && zpc->zpc_ignore_noent) 238 return (0); 239 240 ret = -errno; 241 (void) fprintf(stderr, gettext("failed to opendir %s: %s\n"), 242 name, strerror(errno)); 243 return (ret); 244 } 245 246 /* Non-top item, ignore the case of being removed or renamed by race. */ 247 zpc->zpc_ignore_noent = B_TRUE; 248 errno = 0; 249 while (!ret && (ent = readdir(dir)) != NULL) { 250 char *fullname; 251 252 /* skip "." and ".." */ 253 if (strcmp(ent->d_name, ".") == 0 || 254 strcmp(ent->d_name, "..") == 0) 255 continue; 256 257 if (strlen(ent->d_name) + strlen(name) + 1 >= PATH_MAX) { 258 errno = ENAMETOOLONG; 259 break; 260 } 261 262 if (asprintf(&fullname, "%s/%s", name, ent->d_name) == -1) { 263 errno = ENOMEM; 264 break; 265 } 266 267 ret = zfs_project_handle_one(fullname, zpc); 268 if (!ret && zpc->zpc_recursive && ent->d_type == DT_DIR) 269 zfs_project_item_alloc(head, fullname); 270 271 free(fullname); 272 } 273 274 if (errno && !ret) { 275 ret = -errno; 276 (void) fprintf(stderr, gettext("failed to readdir %s: %s\n"), 277 name, strerror(errno)); 278 } 279 280 closedir(dir); 281 return (ret); 282 } 283 284 int 285 zfs_project_handle(const char *name, zfs_project_control_t *zpc) 286 { 287 zfs_project_item_t *zpi; 288 struct stat st; 289 list_t head; 290 int ret; 291 292 ret = zfs_project_sanity_check(name, zpc, &st); 293 if (ret) 294 return (ret); 295 296 if ((zpc->zpc_op == ZFS_PROJECT_OP_SET || 297 zpc->zpc_op == ZFS_PROJECT_OP_CHECK) && 298 zpc->zpc_expected_projid == ZFS_INVALID_PROJID) { 299 ret = zfs_project_load_projid(name, zpc); 300 if (ret) 301 return (ret); 302 } 303 304 zpc->zpc_ignore_noent = B_FALSE; 305 ret = zfs_project_handle_one(name, zpc); 306 if (ret || !S_ISDIR(st.st_mode) || zpc->zpc_dironly || 307 (!zpc->zpc_recursive && 308 zpc->zpc_op != ZFS_PROJECT_OP_LIST && 309 zpc->zpc_op != ZFS_PROJECT_OP_CHECK)) 310 return (ret); 311 312 list_create(&head, sizeof (zfs_project_item_t), 313 offsetof(zfs_project_item_t, zpi_list)); 314 zfs_project_item_alloc(&head, name); 315 while ((zpi = list_remove_head(&head)) != NULL) { 316 if (!ret) 317 ret = zfs_project_handle_dir(zpi->zpi_name, zpc, &head); 318 free(zpi); 319 } 320 321 return (ret); 322 } 323