| 1 | |
| 2 | |
| 3 | * Copyright (C) 2017 Red Hat, Inc.
|
| 4 |
|
| 5 |
|
| 6 | #include "fuse_i.h"
|
| 7 |
|
| 8 | #include <linux/uio.h>
|
| 9 | #include <linux/compat.h>
|
| 10 | #include <linux/fileattr.h>
|
| 11 | #include <linux/fsverity.h>
|
| 12 |
|
| 13 | #include <linux/slab.h>
|
| 14 | #define FUSE_VERITY_ENABLE_ARG_MAX_PAGES 256
|
| 15 |
|
| 16 | static ssize_t fuse_send_ioctl(struct fuse_mount *fm, struct fuse_args *args,
|
| 17 | struct fuse_ioctl_out *outarg)
|
| 18 | {
|
| 19 | ssize_t ret;
|
| 20 |
|
| 21 | args->out_args[0].size = sizeof(*outarg);
|
| 22 | args->out_args[0].value = outarg;
|
| 23 |
|
| 24 | ret = fuse_simple_request(fm, args);
|
| 25 |
|
| 26 |
|
| 27 | if (ret == -ENOSYS)
|
| 28 | ret = -ENOTTY;
|
| 29 |
|
| 30 | if (ret >= 0 && outarg->result == -ENOSYS)
|
| 31 | outarg->result = -ENOTTY;
|
| 32 |
|
| 33 | return ret;
|
| 34 | }
|
| 35 |
|
| 36 | |
| 37 | * CUSE servers compiled on 32bit broke on 64bit kernels because the
|
| 38 | * ABI was defined to be 'struct iovec' which is different on 32bit
|
| 39 | * and 64bit. Fortunately we can determine which structure the server
|
| 40 | * used from the size of the reply.
|
| 41 |
|
| 42 | static int fuse_copy_ioctl_iovec_old(struct iovec *dst, void *src,
|
| 43 | size_t transferred, unsigned count,
|
| 44 | bool is_compat)
|
| 45 | {
|
| 46 | #ifdef CONFIG_COMPAT
|
| 47 | if (count * sizeof(struct compat_iovec) == transferred) {
|
| 48 | struct compat_iovec *ciov = src;
|
| 49 | unsigned i;
|
| 50 |
|
| 51 | |
| 52 | * With this interface a 32bit server cannot support
|
| 53 | * non-compat (i.e. ones coming from 64bit apps) ioctl
|
| 54 | * requests
|
| 55 |
|
| 56 | if (!is_compat)
|
| 57 | return -EINVAL;
|
| 58 |
|
| 59 | for (i = 0; i < count; i++) {
|
| 60 | dst[i].iov_base = compat_ptr(ciov[i].iov_base);
|
| 61 | dst[i].iov_len = ciov[i].iov_len;
|
| 62 | }
|
| 63 | return 0;
|
| 64 | }
|
| 65 | #endif
|
| 66 |
|
| 67 | if (count * sizeof(struct iovec) != transferred)
|
| 68 | return -EIO;
|
| 69 |
|
| 70 | memcpy(dst, src, transferred);
|
| 71 | return 0;
|
| 72 | }
|
| 73 |
|
| 74 |
|
| 75 | static int fuse_verify_ioctl_iov(struct fuse_conn *fc, struct iovec *iov,
|
| 76 | size_t count)
|
| 77 | {
|
| 78 | size_t n;
|
| 79 | u32 max = fc->max_pages << PAGE_SHIFT;
|
| 80 |
|
| 81 | for (n = 0; n < count; n++, iov++) {
|
| 82 | if (iov->iov_len > (size_t) max)
|
| 83 | return -ENOMEM;
|
| 84 | max -= iov->iov_len;
|
| 85 | }
|
| 86 | return 0;
|
| 87 | }
|
| 88 |
|
| 89 | static int fuse_copy_ioctl_iovec(struct fuse_conn *fc, struct iovec *dst,
|
| 90 | void *src, size_t transferred, unsigned count,
|
| 91 | bool is_compat)
|
| 92 | {
|
| 93 | unsigned i;
|
| 94 | struct fuse_ioctl_iovec *fiov = src;
|
| 95 |
|
| 96 | if (fc->minor < 16) {
|
| 97 | return fuse_copy_ioctl_iovec_old(dst, src, transferred,
|
| 98 | count, is_compat);
|
| 99 | }
|
| 100 |
|
| 101 | if (count * sizeof(struct fuse_ioctl_iovec) != transferred)
|
| 102 | return -EIO;
|
| 103 |
|
| 104 | for (i = 0; i < count; i++) {
|
| 105 |
|
| 106 | if (fiov[i].base != (unsigned long) fiov[i].base ||
|
| 107 | fiov[i].len != (unsigned long) fiov[i].len)
|
| 108 | return -EIO;
|
| 109 |
|
| 110 | dst[i].iov_base = (void __user *) (unsigned long) fiov[i].base;
|
| 111 | dst[i].iov_len = (size_t) fiov[i].len;
|
| 112 |
|
| 113 | #ifdef CONFIG_COMPAT
|
| 114 | if (is_compat &&
|
| 115 | (ptr_to_compat(dst[i].iov_base) != fiov[i].base ||
|
| 116 | (compat_size_t) dst[i].iov_len != fiov[i].len))
|
| 117 | return -EIO;
|
| 118 | #endif
|
| 119 | }
|
| 120 |
|
| 121 | return 0;
|
| 122 | }
|
| 123 |
|
| 124 |
|
| 125 | static int fuse_setup_measure_verity(unsigned long arg, struct iovec *iov)
|
| 126 | {
|
| 127 | __u16 digest_size;
|
| 128 | struct fsverity_digest __user *uarg = (void __user *)arg;
|
| 129 |
|
| 130 | if (copy_from_user(&digest_size, &uarg->digest_size, sizeof(digest_size)))
|
| 131 | return -EFAULT;
|
| 132 |
|
| 133 | if (digest_size > SIZE_MAX - sizeof(struct fsverity_digest))
|
| 134 | return -EINVAL;
|
| 135 |
|
| 136 | iov->iov_len = sizeof(struct fsverity_digest) + digest_size;
|
| 137 |
|
| 138 | return 0;
|
| 139 | }
|
| 140 |
|
| 141 | static int fuse_setup_enable_verity(unsigned long arg, struct iovec *iov,
|
| 142 | unsigned int *in_iovs)
|
| 143 | {
|
| 144 | struct fsverity_enable_arg enable;
|
| 145 | struct fsverity_enable_arg __user *uarg = (void __user *)arg;
|
| 146 | const __u32 max_buffer_len = FUSE_VERITY_ENABLE_ARG_MAX_PAGES * PAGE_SIZE;
|
| 147 |
|
| 148 | if (copy_from_user(&enable, uarg, sizeof(enable)))
|
| 149 | return -EFAULT;
|
| 150 |
|
| 151 | if (enable.salt_size > max_buffer_len || enable.sig_size > max_buffer_len)
|
| 152 | return -ENOMEM;
|
| 153 |
|
| 154 | if (enable.salt_size > 0) {
|
| 155 | iov++;
|
| 156 | (*in_iovs)++;
|
| 157 |
|
| 158 | iov->iov_base = u64_to_user_ptr(enable.salt_ptr);
|
| 159 | iov->iov_len = enable.salt_size;
|
| 160 | }
|
| 161 |
|
| 162 | if (enable.sig_size > 0) {
|
| 163 | iov++;
|
| 164 | (*in_iovs)++;
|
| 165 |
|
| 166 | iov->iov_base = u64_to_user_ptr(enable.sig_ptr);
|
| 167 | iov->iov_len = enable.sig_size;
|
| 168 | }
|
| 169 | return 0;
|
| 170 | }
|
| 171 |
|
| 172 | |
| 173 | * For ioctls, there is no generic way to determine how much memory
|
| 174 | * needs to be read and/or written. Furthermore, ioctls are allowed
|
| 175 | * to dereference the passed pointer, so the parameter requires deep
|
| 176 | * copying but FUSE has no idea whatsoever about what to copy in or
|
| 177 | * out.
|
| 178 | *
|
| 179 | * This is solved by allowing FUSE server to retry ioctl with
|
| 180 | * necessary in/out iovecs. Let's assume the ioctl implementation
|
| 181 | * needs to read in the following structure.
|
| 182 | *
|
| 183 | * struct a {
|
| 184 | * char *buf;
|
| 185 | * size_t buflen;
|
| 186 | * }
|
| 187 | *
|
| 188 | * On the first callout to FUSE server, inarg->in_size and
|
| 189 | * inarg->out_size will be NULL; then, the server completes the ioctl
|
| 190 | * with FUSE_IOCTL_RETRY set in out->flags, out->in_iovs set to 1 and
|
| 191 | * the actual iov array to
|
| 192 | *
|
| 193 | * { { .iov_base = inarg.arg, .iov_len = sizeof(struct a) } }
|
| 194 | *
|
| 195 | * which tells FUSE to copy in the requested area and retry the ioctl.
|
| 196 | * On the second round, the server has access to the structure and
|
| 197 | * from that it can tell what to look for next, so on the invocation,
|
| 198 | * it sets FUSE_IOCTL_RETRY, out->in_iovs to 2 and iov array to
|
| 199 | *
|
| 200 | * { { .iov_base = inarg.arg, .iov_len = sizeof(struct a) },
|
| 201 | * { .iov_base = a.buf, .iov_len = a.buflen } }
|
| 202 | *
|
| 203 | * FUSE will copy both struct a and the pointed buffer from the
|
| 204 | * process doing the ioctl and retry ioctl with both struct a and the
|
| 205 | * buffer.
|
| 206 | *
|
| 207 | * This time, FUSE server has everything it needs and completes ioctl
|
| 208 | * without FUSE_IOCTL_RETRY which finishes the ioctl call.
|
| 209 | *
|
| 210 | * Copying data out works the same way.
|
| 211 | *
|
| 212 | * Note that if FUSE_IOCTL_UNRESTRICTED is clear, the kernel
|
| 213 | * automatically initializes in and out iovs by decoding @cmd with
|
| 214 | * _IOC_* macros and the server is not allowed to request RETRY. This
|
| 215 | * limits ioctl data transfers to well-formed ioctls and is the forced
|
| 216 | * behavior for all FUSE servers.
|
| 217 |
|
| 218 | long fuse_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg,
|
| 219 | unsigned int flags)
|
| 220 | {
|
| 221 | struct fuse_file *ff = file->private_data;
|
| 222 | struct fuse_mount *fm = ff->fm;
|
| 223 | struct fuse_ioctl_in inarg = {
|
| 224 | .fh = ff->fh,
|
| 225 | .cmd = cmd,
|
| 226 | .arg = arg,
|
| 227 | .flags = flags
|
| 228 | };
|
| 229 | struct fuse_ioctl_out outarg;
|
| 230 | struct iovec *iov_page = NULL;
|
| 231 | struct iovec *in_iov = NULL, *out_iov = NULL;
|
| 232 | unsigned int in_iovs = 0, out_iovs = 0, max_pages;
|
| 233 | size_t in_size, out_size, c;
|
| 234 | ssize_t transferred;
|
| 235 | int err, i;
|
| 236 | struct iov_iter ii;
|
| 237 | struct fuse_args_pages ap = {};
|
| 238 |
|
| 239 | #if BITS_PER_LONG == 32
|
| 240 | inarg.flags |= FUSE_IOCTL_32BIT;
|
| 241 | #else
|
| 242 | if (flags & FUSE_IOCTL_COMPAT) {
|
| 243 | inarg.flags |= FUSE_IOCTL_32BIT;
|
| 244 | #ifdef CONFIG_X86_X32_ABI
|
| 245 | if (in_x32_syscall())
|
| 246 | inarg.flags |= FUSE_IOCTL_COMPAT_X32;
|
| 247 | #endif
|
| 248 | }
|
| 249 | #endif
|
| 250 |
|
| 251 |
|
| 252 | BUILD_BUG_ON(sizeof(struct fuse_ioctl_iovec) * FUSE_IOCTL_MAX_IOV > PAGE_SIZE);
|
| 253 |
|
| 254 | err = -ENOMEM;
|
| 255 | ap.folios = fuse_folios_alloc(fm->fc->max_pages, GFP_KERNEL, &ap.descs);
|
| 256 | iov_page = kmalloc(PAGE_SIZE, GFP_KERNEL);
|
| 257 | if (!ap.folios || !iov_page)
|
| 258 | goto out;
|
| 259 |
|
| 260 | fuse_folio_descs_length_init(ap.descs, 0, fm->fc->max_pages);
|
| 261 |
|
| 262 | |
| 263 | * If restricted, initialize IO parameters as encoded in @cmd.
|
| 264 | * RETRY from server is not allowed.
|
| 265 |
|
| 266 | if (!(flags & FUSE_IOCTL_UNRESTRICTED)) {
|
| 267 | struct iovec *iov = iov_page;
|
| 268 |
|
| 269 | iov->iov_base = (void __user *)arg;
|
| 270 | iov->iov_len = _IOC_SIZE(cmd);
|
| 271 |
|
| 272 | if (_IOC_DIR(cmd) & _IOC_WRITE) {
|
| 273 | in_iov = iov;
|
| 274 | in_iovs = 1;
|
| 275 | }
|
| 276 |
|
| 277 | if (_IOC_DIR(cmd) & _IOC_READ) {
|
| 278 | out_iov = iov;
|
| 279 | out_iovs = 1;
|
| 280 | }
|
| 281 |
|
| 282 | err = 0;
|
| 283 | switch (cmd) {
|
| 284 | case FS_IOC_MEASURE_VERITY:
|
| 285 | err = fuse_setup_measure_verity(arg, iov);
|
| 286 | break;
|
| 287 | case FS_IOC_ENABLE_VERITY:
|
| 288 | err = fuse_setup_enable_verity(arg, iov, &in_iovs);
|
| 289 | break;
|
| 290 | }
|
| 291 | if (err)
|
| 292 | goto out;
|
| 293 | }
|
| 294 |
|
| 295 | retry:
|
| 296 | inarg.in_size = in_size = iov_length(in_iov, in_iovs);
|
| 297 | inarg.out_size = out_size = iov_length(out_iov, out_iovs);
|
| 298 |
|
| 299 | |
| 300 | * Out data can be used either for actual out data or iovs,
|
| 301 | * make sure there always is at least one page.
|
| 302 |
|
| 303 | out_size = max_t(size_t, out_size, PAGE_SIZE);
|
| 304 | max_pages = DIV_ROUND_UP(max(in_size, out_size), PAGE_SIZE);
|
| 305 |
|
| 306 |
|
| 307 | err = -ENOMEM;
|
| 308 | if (max_pages > fm->fc->max_pages)
|
| 309 | goto out;
|
| 310 | while (ap.num_folios < max_pages) {
|
| 311 | ap.folios[ap.num_folios] = folio_alloc(GFP_KERNEL | __GFP_HIGHMEM, 0);
|
| 312 | if (!ap.folios[ap.num_folios])
|
| 313 | goto out;
|
| 314 | ap.num_folios++;
|
| 315 | }
|
| 316 |
|
| 317 |
|
| 318 | ap.args.opcode = FUSE_IOCTL;
|
| 319 | ap.args.nodeid = ff->nodeid;
|
| 320 | ap.args.in_numargs = 1;
|
| 321 | ap.args.in_args[0].size = sizeof(inarg);
|
| 322 | ap.args.in_args[0].value = &inarg;
|
| 323 | if (in_size) {
|
| 324 | ap.args.in_numargs++;
|
| 325 | ap.args.in_args[1].size = in_size;
|
| 326 | ap.args.in_pages = true;
|
| 327 |
|
| 328 | err = -EFAULT;
|
| 329 | iov_iter_init(&ii, ITER_SOURCE, in_iov, in_iovs, in_size);
|
| 330 | for (i = 0; iov_iter_count(&ii) && !WARN_ON(i >= ap.num_folios); i++) {
|
| 331 | c = copy_folio_from_iter(ap.folios[i], 0, PAGE_SIZE, &ii);
|
| 332 | if (c != PAGE_SIZE && iov_iter_count(&ii))
|
| 333 | goto out;
|
| 334 | }
|
| 335 | }
|
| 336 |
|
| 337 | ap.args.out_numargs = 2;
|
| 338 | ap.args.out_args[1].size = out_size;
|
| 339 | ap.args.out_pages = true;
|
| 340 | ap.args.out_argvar = true;
|
| 341 |
|
| 342 | transferred = fuse_send_ioctl(fm, &ap.args, &outarg);
|
| 343 | err = transferred;
|
| 344 | if (transferred < 0)
|
| 345 | goto out;
|
| 346 |
|
| 347 |
|
| 348 | if (outarg.flags & FUSE_IOCTL_RETRY) {
|
| 349 | void *vaddr;
|
| 350 |
|
| 351 |
|
| 352 | err = -EIO;
|
| 353 | if (!(flags & FUSE_IOCTL_UNRESTRICTED))
|
| 354 | goto out;
|
| 355 |
|
| 356 | in_iovs = outarg.in_iovs;
|
| 357 | out_iovs = outarg.out_iovs;
|
| 358 |
|
| 359 | |
| 360 | * Make sure things are in boundary, separate checks
|
| 361 | * are to protect against overflow.
|
| 362 |
|
| 363 | err = -ENOMEM;
|
| 364 | if (in_iovs > FUSE_IOCTL_MAX_IOV ||
|
| 365 | out_iovs > FUSE_IOCTL_MAX_IOV ||
|
| 366 | in_iovs + out_iovs > FUSE_IOCTL_MAX_IOV)
|
| 367 | goto out;
|
| 368 |
|
| 369 | vaddr = kmap_local_folio(ap.folios[0], 0);
|
| 370 | err = fuse_copy_ioctl_iovec(fm->fc, iov_page, vaddr,
|
| 371 | transferred, in_iovs + out_iovs,
|
| 372 | (flags & FUSE_IOCTL_COMPAT) != 0);
|
| 373 | kunmap_local(vaddr);
|
| 374 | if (err)
|
| 375 | goto out;
|
| 376 |
|
| 377 | in_iov = iov_page;
|
| 378 | out_iov = in_iov + in_iovs;
|
| 379 |
|
| 380 | err = fuse_verify_ioctl_iov(fm->fc, in_iov, in_iovs);
|
| 381 | if (err)
|
| 382 | goto out;
|
| 383 |
|
| 384 | err = fuse_verify_ioctl_iov(fm->fc, out_iov, out_iovs);
|
| 385 | if (err)
|
| 386 | goto out;
|
| 387 |
|
| 388 | goto retry;
|
| 389 | }
|
| 390 |
|
| 391 | err = -EIO;
|
| 392 | if (transferred > inarg.out_size)
|
| 393 | goto out;
|
| 394 |
|
| 395 | err = -EFAULT;
|
| 396 | iov_iter_init(&ii, ITER_DEST, out_iov, out_iovs, transferred);
|
| 397 | for (i = 0; iov_iter_count(&ii) && !WARN_ON(i >= ap.num_folios); i++) {
|
| 398 | c = copy_folio_to_iter(ap.folios[i], 0, PAGE_SIZE, &ii);
|
| 399 | if (c != PAGE_SIZE && iov_iter_count(&ii))
|
| 400 | goto out;
|
| 401 | }
|
| 402 | err = 0;
|
| 403 | out:
|
| 404 | kfree(iov_page);
|
| 405 | while (ap.num_folios)
|
| 406 | folio_put(ap.folios[--ap.num_folios]);
|
| 407 | kfree(ap.folios);
|
| 408 |
|
| 409 | return err ? err : outarg.result;
|
| 410 | }
|
| 411 | EXPORT_SYMBOL_GPL(fuse_do_ioctl);
|
| 412 |
|
| 413 | long fuse_ioctl_common(struct file *file, unsigned int cmd,
|
| 414 | unsigned long arg, unsigned int flags)
|
| 415 | {
|
| 416 | struct inode *inode = file_inode(file);
|
| 417 | struct fuse_conn *fc = get_fuse_conn(inode);
|
| 418 |
|
| 419 | if (!fuse_allow_current_process(fc))
|
| 420 | return -EACCES;
|
| 421 |
|
| 422 | if (fuse_is_bad(inode))
|
| 423 | return -EIO;
|
| 424 |
|
| 425 | return fuse_do_ioctl(file, cmd, arg, flags);
|
| 426 | }
|
| 427 |
|
| 428 | long fuse_file_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
| 429 | {
|
| 430 | return fuse_ioctl_common(file, cmd, arg, 0);
|
| 431 | }
|
| 432 |
|
| 433 | long fuse_file_compat_ioctl(struct file *file, unsigned int cmd,
|
| 434 | unsigned long arg)
|
| 435 | {
|
| 436 | return fuse_ioctl_common(file, cmd, arg, FUSE_IOCTL_COMPAT);
|
| 437 | }
|
| 438 |
|
| 439 | static int fuse_priv_ioctl(struct inode *inode, struct fuse_file *ff,
|
| 440 | unsigned int cmd, void *ptr, size_t size)
|
| 441 | {
|
| 442 | struct fuse_mount *fm = ff->fm;
|
| 443 | struct fuse_ioctl_in inarg;
|
| 444 | struct fuse_ioctl_out outarg;
|
| 445 | FUSE_ARGS(args);
|
| 446 | int err;
|
| 447 |
|
| 448 | memset(&inarg, 0, sizeof(inarg));
|
| 449 | inarg.fh = ff->fh;
|
| 450 | inarg.cmd = cmd;
|
| 451 |
|
| 452 | #if BITS_PER_LONG == 32
|
| 453 | inarg.flags |= FUSE_IOCTL_32BIT;
|
| 454 | #endif
|
| 455 | if (S_ISDIR(inode->i_mode))
|
| 456 | inarg.flags |= FUSE_IOCTL_DIR;
|
| 457 |
|
| 458 | if (_IOC_DIR(cmd) & _IOC_READ)
|
| 459 | inarg.out_size = size;
|
| 460 | if (_IOC_DIR(cmd) & _IOC_WRITE)
|
| 461 | inarg.in_size = size;
|
| 462 |
|
| 463 | args.opcode = FUSE_IOCTL;
|
| 464 | args.nodeid = ff->nodeid;
|
| 465 | args.in_numargs = 2;
|
| 466 | args.in_args[0].size = sizeof(inarg);
|
| 467 | args.in_args[0].value = &inarg;
|
| 468 | args.in_args[1].size = inarg.in_size;
|
| 469 | args.in_args[1].value = ptr;
|
| 470 | args.out_numargs = 2;
|
| 471 | args.out_args[1].size = inarg.out_size;
|
| 472 | args.out_args[1].value = ptr;
|
| 473 |
|
| 474 | err = fuse_send_ioctl(fm, &args, &outarg);
|
| 475 | if (!err) {
|
| 476 | if (outarg.result < 0)
|
| 477 | err = outarg.result;
|
| 478 | else if (outarg.flags & FUSE_IOCTL_RETRY)
|
| 479 | err = -EIO;
|
| 480 | }
|
| 481 | return err;
|
| 482 | }
|
| 483 |
|
| 484 | static struct fuse_file *fuse_priv_ioctl_prepare(struct inode *inode)
|
| 485 | {
|
| 486 | struct fuse_mount *fm = get_fuse_mount(inode);
|
| 487 | bool isdir = S_ISDIR(inode->i_mode);
|
| 488 |
|
| 489 | if (!fuse_allow_current_process(fm->fc))
|
| 490 | return ERR_PTR(-EACCES);
|
| 491 |
|
| 492 | if (fuse_is_bad(inode))
|
| 493 | return ERR_PTR(-EIO);
|
| 494 |
|
| 495 | if (!S_ISREG(inode->i_mode) && !isdir)
|
| 496 | return ERR_PTR(-ENOTTY);
|
| 497 |
|
| 498 | return fuse_file_open(fm, get_node_id(inode), O_RDONLY, isdir);
|
| 499 | }
|
| 500 |
|
| 501 | static void fuse_priv_ioctl_cleanup(struct inode *inode, struct fuse_file *ff)
|
| 502 | {
|
| 503 | fuse_file_release(inode, ff, O_RDONLY, NULL, S_ISDIR(inode->i_mode));
|
| 504 | }
|
| 505 |
|
| 506 | int fuse_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
|
| 507 | {
|
| 508 | struct inode *inode = d_inode(dentry);
|
| 509 | struct fuse_file *ff;
|
| 510 | unsigned int flags;
|
| 511 | struct fsxattr xfa;
|
| 512 | int err;
|
| 513 |
|
| 514 | ff = fuse_priv_ioctl_prepare(inode);
|
| 515 | if (IS_ERR(ff))
|
| 516 | return PTR_ERR(ff);
|
| 517 |
|
| 518 | if (fa->flags_valid) {
|
| 519 | err = fuse_priv_ioctl(inode, ff, FS_IOC_GETFLAGS,
|
| 520 | &flags, sizeof(flags));
|
| 521 | if (err)
|
| 522 | goto cleanup;
|
| 523 |
|
| 524 | fileattr_fill_flags(fa, flags);
|
| 525 | } else {
|
| 526 | err = fuse_priv_ioctl(inode, ff, FS_IOC_FSGETXATTR,
|
| 527 | &xfa, sizeof(xfa));
|
| 528 | if (err)
|
| 529 | goto cleanup;
|
| 530 |
|
| 531 | fileattr_fill_xflags(fa, xfa.fsx_xflags);
|
| 532 | fa->fsx_extsize = xfa.fsx_extsize;
|
| 533 | fa->fsx_nextents = xfa.fsx_nextents;
|
| 534 | fa->fsx_projid = xfa.fsx_projid;
|
| 535 | fa->fsx_cowextsize = xfa.fsx_cowextsize;
|
| 536 | }
|
| 537 | cleanup:
|
| 538 | fuse_priv_ioctl_cleanup(inode, ff);
|
| 539 |
|
| 540 | return err;
|
| 541 | }
|
| 542 |
|
| 543 | int fuse_fileattr_set(const struct mnt_idmap *idmap,
|
| 544 | struct dentry *dentry, struct file_kattr *fa)
|
| 545 | {
|
| 546 | struct inode *inode = d_inode(dentry);
|
| 547 | struct fuse_file *ff;
|
| 548 | unsigned int flags = fa->flags;
|
| 549 | struct fsxattr xfa;
|
| 550 | int err;
|
| 551 |
|
| 552 | ff = fuse_priv_ioctl_prepare(inode);
|
| 553 | if (IS_ERR(ff))
|
| 554 | return PTR_ERR(ff);
|
| 555 |
|
| 556 | if (fa->flags_valid) {
|
| 557 | err = fuse_priv_ioctl(inode, ff, FS_IOC_SETFLAGS,
|
| 558 | &flags, sizeof(flags));
|
| 559 | if (err)
|
| 560 | goto cleanup;
|
| 561 | } else {
|
| 562 | memset(&xfa, 0, sizeof(xfa));
|
| 563 | xfa.fsx_xflags = fa->fsx_xflags;
|
| 564 | xfa.fsx_extsize = fa->fsx_extsize;
|
| 565 | xfa.fsx_nextents = fa->fsx_nextents;
|
| 566 | xfa.fsx_projid = fa->fsx_projid;
|
| 567 | xfa.fsx_cowextsize = fa->fsx_cowextsize;
|
| 568 |
|
| 569 | err = fuse_priv_ioctl(inode, ff, FS_IOC_FSSETXATTR,
|
| 570 | &xfa, sizeof(xfa));
|
| 571 | }
|
| 572 |
|
| 573 | cleanup:
|
| 574 | fuse_priv_ioctl_cleanup(inode, ff);
|
| 575 |
|
| 576 | return err;
|
| 577 | }
|