1 /* 2 * 9p Posix callback 3 * 4 * Copyright IBM, Corp. 2010 5 * 6 * Authors: 7 * Anthony Liguori <aliguori@us.ibm.com> 8 * 9 * This work is licensed under the terms of the GNU GPL, version 2. See 10 * the COPYING file in the top-level directory. 11 * 12 */ 13 14 #include "qemu/osdep.h" 15 #include "9p.h" 16 #include "9p-local.h" 17 #include "9p-xattr.h" 18 #include "9p-util.h" 19 #include "fsdev/qemu-fsdev.h" /* local_ops */ 20 #include <arpa/inet.h> 21 #include <pwd.h> 22 #include <grp.h> 23 #include <sys/socket.h> 24 #include <sys/un.h> 25 #include "qemu/xattr.h" 26 #include "qemu/cutils.h" 27 #include "qemu/error-report.h" 28 #include <libgen.h> 29 #include <linux/fs.h> 30 #ifdef CONFIG_LINUX_MAGIC_H 31 #include <linux/magic.h> 32 #endif 33 #include <sys/ioctl.h> 34 35 #ifndef XFS_SUPER_MAGIC 36 #define XFS_SUPER_MAGIC 0x58465342 37 #endif 38 #ifndef EXT2_SUPER_MAGIC 39 #define EXT2_SUPER_MAGIC 0xEF53 40 #endif 41 #ifndef REISERFS_SUPER_MAGIC 42 #define REISERFS_SUPER_MAGIC 0x52654973 43 #endif 44 #ifndef BTRFS_SUPER_MAGIC 45 #define BTRFS_SUPER_MAGIC 0x9123683E 46 #endif 47 48 typedef struct { 49 int mountfd; 50 } LocalData; 51 52 int local_open_nofollow(FsContext *fs_ctx, const char *path, int flags, 53 mode_t mode) 54 { 55 LocalData *data = fs_ctx->private; 56 int fd = data->mountfd; 57 58 while (*path && fd != -1) { 59 const char *c; 60 int next_fd; 61 char *head; 62 63 /* Only relative paths without consecutive slashes */ 64 assert(*path != '/'); 65 66 head = g_strdup(path); 67 c = strchrnul(path, '/'); 68 if (*c) { 69 /* Intermediate path element */ 70 head[c - path] = 0; 71 path = c + 1; 72 next_fd = openat_dir(fd, head); 73 } else { 74 /* Rightmost path element */ 75 next_fd = openat_file(fd, head, flags, mode); 76 path = c; 77 } 78 g_free(head); 79 if (fd != data->mountfd) { 80 close_preserve_errno(fd); 81 } 82 fd = next_fd; 83 } 84 85 assert(fd != data->mountfd); 86 return fd; 87 } 88 89 int local_opendir_nofollow(FsContext *fs_ctx, const char *path) 90 { 91 return local_open_nofollow(fs_ctx, path, O_DIRECTORY | O_RDONLY, 0); 92 } 93 94 static void renameat_preserve_errno(int odirfd, const char *opath, int ndirfd, 95 const char *npath) 96 { 97 int serrno = errno; 98 renameat(odirfd, opath, ndirfd, npath); 99 errno = serrno; 100 } 101 102 static void unlinkat_preserve_errno(int dirfd, const char *path, int flags) 103 { 104 int serrno = errno; 105 unlinkat(dirfd, path, flags); 106 errno = serrno; 107 } 108 109 #define VIRTFS_META_DIR ".virtfs_metadata" 110 111 static FILE *local_fopenat(int dirfd, const char *name, const char *mode) 112 { 113 int fd, o_mode = 0; 114 FILE *fp; 115 int flags; 116 /* 117 * only supports two modes 118 */ 119 if (mode[0] == 'r') { 120 flags = O_RDONLY; 121 } else if (mode[0] == 'w') { 122 flags = O_WRONLY | O_TRUNC | O_CREAT; 123 o_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; 124 } else { 125 return NULL; 126 } 127 fd = openat_file(dirfd, name, flags, o_mode); 128 if (fd == -1) { 129 return NULL; 130 } 131 fp = fdopen(fd, mode); 132 if (!fp) { 133 close(fd); 134 } 135 return fp; 136 } 137 138 #define ATTR_MAX 100 139 static void local_mapped_file_attr(int dirfd, const char *name, 140 struct stat *stbuf) 141 { 142 FILE *fp; 143 char buf[ATTR_MAX]; 144 int map_dirfd; 145 146 map_dirfd = openat_dir(dirfd, VIRTFS_META_DIR); 147 if (map_dirfd == -1) { 148 return; 149 } 150 151 fp = local_fopenat(map_dirfd, name, "r"); 152 close_preserve_errno(map_dirfd); 153 if (!fp) { 154 return; 155 } 156 memset(buf, 0, ATTR_MAX); 157 while (fgets(buf, ATTR_MAX, fp)) { 158 if (!strncmp(buf, "virtfs.uid", 10)) { 159 stbuf->st_uid = atoi(buf+11); 160 } else if (!strncmp(buf, "virtfs.gid", 10)) { 161 stbuf->st_gid = atoi(buf+11); 162 } else if (!strncmp(buf, "virtfs.mode", 11)) { 163 stbuf->st_mode = atoi(buf+12); 164 } else if (!strncmp(buf, "virtfs.rdev", 11)) { 165 stbuf->st_rdev = atoi(buf+12); 166 } 167 memset(buf, 0, ATTR_MAX); 168 } 169 fclose(fp); 170 } 171 172 static int local_lstat(FsContext *fs_ctx, V9fsPath *fs_path, struct stat *stbuf) 173 { 174 int err = -1; 175 char *dirpath = g_path_get_dirname(fs_path->data); 176 char *name = g_path_get_basename(fs_path->data); 177 int dirfd; 178 179 dirfd = local_opendir_nofollow(fs_ctx, dirpath); 180 if (dirfd == -1) { 181 goto out; 182 } 183 184 err = fstatat(dirfd, name, stbuf, AT_SYMLINK_NOFOLLOW); 185 if (err) { 186 goto err_out; 187 } 188 if (fs_ctx->export_flags & V9FS_SM_MAPPED) { 189 /* Actual credentials are part of extended attrs */ 190 uid_t tmp_uid; 191 gid_t tmp_gid; 192 mode_t tmp_mode; 193 dev_t tmp_dev; 194 195 if (fgetxattrat_nofollow(dirfd, name, "user.virtfs.uid", &tmp_uid, 196 sizeof(uid_t)) > 0) { 197 stbuf->st_uid = le32_to_cpu(tmp_uid); 198 } 199 if (fgetxattrat_nofollow(dirfd, name, "user.virtfs.gid", &tmp_gid, 200 sizeof(gid_t)) > 0) { 201 stbuf->st_gid = le32_to_cpu(tmp_gid); 202 } 203 if (fgetxattrat_nofollow(dirfd, name, "user.virtfs.mode", &tmp_mode, 204 sizeof(mode_t)) > 0) { 205 stbuf->st_mode = le32_to_cpu(tmp_mode); 206 } 207 if (fgetxattrat_nofollow(dirfd, name, "user.virtfs.rdev", &tmp_dev, 208 sizeof(dev_t)) > 0) { 209 stbuf->st_rdev = le64_to_cpu(tmp_dev); 210 } 211 } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) { 212 local_mapped_file_attr(dirfd, name, stbuf); 213 } 214 215 err_out: 216 close_preserve_errno(dirfd); 217 out: 218 g_free(name); 219 g_free(dirpath); 220 return err; 221 } 222 223 static int local_set_mapped_file_attrat(int dirfd, const char *name, 224 FsCred *credp) 225 { 226 FILE *fp; 227 int ret; 228 char buf[ATTR_MAX]; 229 int uid = -1, gid = -1, mode = -1, rdev = -1; 230 int map_dirfd; 231 232 ret = mkdirat(dirfd, VIRTFS_META_DIR, 0700); 233 if (ret < 0 && errno != EEXIST) { 234 return -1; 235 } 236 237 map_dirfd = openat_dir(dirfd, VIRTFS_META_DIR); 238 if (map_dirfd == -1) { 239 return -1; 240 } 241 242 fp = local_fopenat(map_dirfd, name, "r"); 243 if (!fp) { 244 if (errno == ENOENT) { 245 goto update_map_file; 246 } else { 247 close_preserve_errno(map_dirfd); 248 return -1; 249 } 250 } 251 memset(buf, 0, ATTR_MAX); 252 while (fgets(buf, ATTR_MAX, fp)) { 253 if (!strncmp(buf, "virtfs.uid", 10)) { 254 uid = atoi(buf + 11); 255 } else if (!strncmp(buf, "virtfs.gid", 10)) { 256 gid = atoi(buf + 11); 257 } else if (!strncmp(buf, "virtfs.mode", 11)) { 258 mode = atoi(buf + 12); 259 } else if (!strncmp(buf, "virtfs.rdev", 11)) { 260 rdev = atoi(buf + 12); 261 } 262 memset(buf, 0, ATTR_MAX); 263 } 264 fclose(fp); 265 266 update_map_file: 267 fp = local_fopenat(map_dirfd, name, "w"); 268 close_preserve_errno(map_dirfd); 269 if (!fp) { 270 return -1; 271 } 272 273 if (credp->fc_uid != -1) { 274 uid = credp->fc_uid; 275 } 276 if (credp->fc_gid != -1) { 277 gid = credp->fc_gid; 278 } 279 if (credp->fc_mode != -1) { 280 mode = credp->fc_mode; 281 } 282 if (credp->fc_rdev != -1) { 283 rdev = credp->fc_rdev; 284 } 285 286 if (uid != -1) { 287 fprintf(fp, "virtfs.uid=%d\n", uid); 288 } 289 if (gid != -1) { 290 fprintf(fp, "virtfs.gid=%d\n", gid); 291 } 292 if (mode != -1) { 293 fprintf(fp, "virtfs.mode=%d\n", mode); 294 } 295 if (rdev != -1) { 296 fprintf(fp, "virtfs.rdev=%d\n", rdev); 297 } 298 fclose(fp); 299 300 return 0; 301 } 302 303 static int fchmodat_nofollow(int dirfd, const char *name, mode_t mode) 304 { 305 int fd, ret; 306 307 /* FIXME: this should be handled with fchmodat(AT_SYMLINK_NOFOLLOW). 308 * Unfortunately, the linux kernel doesn't implement it yet. As an 309 * alternative, let's open the file and use fchmod() instead. This 310 * may fail depending on the permissions of the file, but it is the 311 * best we can do to avoid TOCTTOU. We first try to open read-only 312 * in case name points to a directory. If that fails, we try write-only 313 * in case name doesn't point to a directory. 314 */ 315 fd = openat_file(dirfd, name, O_RDONLY, 0); 316 if (fd == -1) { 317 /* In case the file is writable-only and isn't a directory. */ 318 if (errno == EACCES) { 319 fd = openat_file(dirfd, name, O_WRONLY, 0); 320 } 321 if (fd == -1 && errno == EISDIR) { 322 errno = EACCES; 323 } 324 } 325 if (fd == -1) { 326 return -1; 327 } 328 ret = fchmod(fd, mode); 329 close_preserve_errno(fd); 330 return ret; 331 } 332 333 static int local_set_xattrat(int dirfd, const char *path, FsCred *credp) 334 { 335 int err; 336 337 if (credp->fc_uid != -1) { 338 uint32_t tmp_uid = cpu_to_le32(credp->fc_uid); 339 err = fsetxattrat_nofollow(dirfd, path, "user.virtfs.uid", &tmp_uid, 340 sizeof(uid_t), 0); 341 if (err) { 342 return err; 343 } 344 } 345 if (credp->fc_gid != -1) { 346 uint32_t tmp_gid = cpu_to_le32(credp->fc_gid); 347 err = fsetxattrat_nofollow(dirfd, path, "user.virtfs.gid", &tmp_gid, 348 sizeof(gid_t), 0); 349 if (err) { 350 return err; 351 } 352 } 353 if (credp->fc_mode != -1) { 354 uint32_t tmp_mode = cpu_to_le32(credp->fc_mode); 355 err = fsetxattrat_nofollow(dirfd, path, "user.virtfs.mode", &tmp_mode, 356 sizeof(mode_t), 0); 357 if (err) { 358 return err; 359 } 360 } 361 if (credp->fc_rdev != -1) { 362 uint64_t tmp_rdev = cpu_to_le64(credp->fc_rdev); 363 err = fsetxattrat_nofollow(dirfd, path, "user.virtfs.rdev", &tmp_rdev, 364 sizeof(dev_t), 0); 365 if (err) { 366 return err; 367 } 368 } 369 return 0; 370 } 371 372 static int local_set_cred_passthrough(FsContext *fs_ctx, int dirfd, 373 const char *name, FsCred *credp) 374 { 375 if (fchownat(dirfd, name, credp->fc_uid, credp->fc_gid, 376 AT_SYMLINK_NOFOLLOW) < 0) { 377 /* 378 * If we fail to change ownership and if we are 379 * using security model none. Ignore the error 380 */ 381 if ((fs_ctx->export_flags & V9FS_SEC_MASK) != V9FS_SM_NONE) { 382 return -1; 383 } 384 } 385 386 return fchmodat_nofollow(dirfd, name, credp->fc_mode & 07777); 387 } 388 389 static ssize_t local_readlink(FsContext *fs_ctx, V9fsPath *fs_path, 390 char *buf, size_t bufsz) 391 { 392 ssize_t tsize = -1; 393 394 if ((fs_ctx->export_flags & V9FS_SM_MAPPED) || 395 (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE)) { 396 int fd; 397 398 fd = local_open_nofollow(fs_ctx, fs_path->data, O_RDONLY, 0); 399 if (fd == -1) { 400 return -1; 401 } 402 do { 403 tsize = read(fd, (void *)buf, bufsz); 404 } while (tsize == -1 && errno == EINTR); 405 close_preserve_errno(fd); 406 } else if ((fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) || 407 (fs_ctx->export_flags & V9FS_SM_NONE)) { 408 char *dirpath = g_path_get_dirname(fs_path->data); 409 char *name = g_path_get_basename(fs_path->data); 410 int dirfd; 411 412 dirfd = local_opendir_nofollow(fs_ctx, dirpath); 413 if (dirfd == -1) { 414 goto out; 415 } 416 417 tsize = readlinkat(dirfd, name, buf, bufsz); 418 close_preserve_errno(dirfd); 419 out: 420 g_free(name); 421 g_free(dirpath); 422 } 423 return tsize; 424 } 425 426 static int local_close(FsContext *ctx, V9fsFidOpenState *fs) 427 { 428 return close(fs->fd); 429 } 430 431 static int local_closedir(FsContext *ctx, V9fsFidOpenState *fs) 432 { 433 return closedir(fs->dir.stream); 434 } 435 436 static int local_open(FsContext *ctx, V9fsPath *fs_path, 437 int flags, V9fsFidOpenState *fs) 438 { 439 int fd; 440 441 fd = local_open_nofollow(ctx, fs_path->data, flags, 0); 442 if (fd == -1) { 443 return -1; 444 } 445 fs->fd = fd; 446 return fs->fd; 447 } 448 449 static int local_opendir(FsContext *ctx, 450 V9fsPath *fs_path, V9fsFidOpenState *fs) 451 { 452 int dirfd; 453 DIR *stream; 454 455 dirfd = local_opendir_nofollow(ctx, fs_path->data); 456 if (dirfd == -1) { 457 return -1; 458 } 459 460 stream = fdopendir(dirfd); 461 if (!stream) { 462 close(dirfd); 463 return -1; 464 } 465 fs->dir.stream = stream; 466 return 0; 467 } 468 469 static void local_rewinddir(FsContext *ctx, V9fsFidOpenState *fs) 470 { 471 rewinddir(fs->dir.stream); 472 } 473 474 static off_t local_telldir(FsContext *ctx, V9fsFidOpenState *fs) 475 { 476 return telldir(fs->dir.stream); 477 } 478 479 static bool local_is_mapped_file_metadata(FsContext *fs_ctx, const char *name) 480 { 481 return !strcmp(name, VIRTFS_META_DIR); 482 } 483 484 static struct dirent *local_readdir(FsContext *ctx, V9fsFidOpenState *fs) 485 { 486 struct dirent *entry; 487 488 again: 489 entry = readdir(fs->dir.stream); 490 if (!entry) { 491 return NULL; 492 } 493 494 if (ctx->export_flags & V9FS_SM_MAPPED) { 495 entry->d_type = DT_UNKNOWN; 496 } else if (ctx->export_flags & V9FS_SM_MAPPED_FILE) { 497 if (local_is_mapped_file_metadata(ctx, entry->d_name)) { 498 /* skip the meta data directory */ 499 goto again; 500 } 501 entry->d_type = DT_UNKNOWN; 502 } 503 504 return entry; 505 } 506 507 static void local_seekdir(FsContext *ctx, V9fsFidOpenState *fs, off_t off) 508 { 509 seekdir(fs->dir.stream, off); 510 } 511 512 static ssize_t local_preadv(FsContext *ctx, V9fsFidOpenState *fs, 513 const struct iovec *iov, 514 int iovcnt, off_t offset) 515 { 516 #ifdef CONFIG_PREADV 517 return preadv(fs->fd, iov, iovcnt, offset); 518 #else 519 int err = lseek(fs->fd, offset, SEEK_SET); 520 if (err == -1) { 521 return err; 522 } else { 523 return readv(fs->fd, iov, iovcnt); 524 } 525 #endif 526 } 527 528 static ssize_t local_pwritev(FsContext *ctx, V9fsFidOpenState *fs, 529 const struct iovec *iov, 530 int iovcnt, off_t offset) 531 { 532 ssize_t ret; 533 #ifdef CONFIG_PREADV 534 ret = pwritev(fs->fd, iov, iovcnt, offset); 535 #else 536 int err = lseek(fs->fd, offset, SEEK_SET); 537 if (err == -1) { 538 return err; 539 } else { 540 ret = writev(fs->fd, iov, iovcnt); 541 } 542 #endif 543 #ifdef CONFIG_SYNC_FILE_RANGE 544 if (ret > 0 && ctx->export_flags & V9FS_IMMEDIATE_WRITEOUT) { 545 /* 546 * Initiate a writeback. This is not a data integrity sync. 547 * We want to ensure that we don't leave dirty pages in the cache 548 * after write when writeout=immediate is sepcified. 549 */ 550 sync_file_range(fs->fd, offset, ret, 551 SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE); 552 } 553 #endif 554 return ret; 555 } 556 557 static int local_chmod(FsContext *fs_ctx, V9fsPath *fs_path, FsCred *credp) 558 { 559 char *dirpath = g_path_get_dirname(fs_path->data); 560 char *name = g_path_get_basename(fs_path->data); 561 int ret = -1; 562 int dirfd; 563 564 dirfd = local_opendir_nofollow(fs_ctx, dirpath); 565 if (dirfd == -1) { 566 goto out; 567 } 568 569 if (fs_ctx->export_flags & V9FS_SM_MAPPED) { 570 ret = local_set_xattrat(dirfd, name, credp); 571 } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) { 572 ret = local_set_mapped_file_attrat(dirfd, name, credp); 573 } else if (fs_ctx->export_flags & V9FS_SM_PASSTHROUGH || 574 fs_ctx->export_flags & V9FS_SM_NONE) { 575 ret = fchmodat_nofollow(dirfd, name, credp->fc_mode); 576 } 577 close_preserve_errno(dirfd); 578 579 out: 580 g_free(dirpath); 581 g_free(name); 582 return ret; 583 } 584 585 static int local_mknod(FsContext *fs_ctx, V9fsPath *dir_path, 586 const char *name, FsCred *credp) 587 { 588 int err = -1; 589 int dirfd; 590 591 if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE && 592 local_is_mapped_file_metadata(fs_ctx, name)) { 593 errno = EINVAL; 594 return -1; 595 } 596 597 dirfd = local_opendir_nofollow(fs_ctx, dir_path->data); 598 if (dirfd == -1) { 599 return -1; 600 } 601 602 if (fs_ctx->export_flags & V9FS_SM_MAPPED || 603 fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) { 604 err = mknodat(dirfd, name, SM_LOCAL_MODE_BITS | S_IFREG, 0); 605 if (err == -1) { 606 goto out; 607 } 608 609 if (fs_ctx->export_flags & V9FS_SM_MAPPED) { 610 err = local_set_xattrat(dirfd, name, credp); 611 } else { 612 err = local_set_mapped_file_attrat(dirfd, name, credp); 613 } 614 if (err == -1) { 615 goto err_end; 616 } 617 } else if (fs_ctx->export_flags & V9FS_SM_PASSTHROUGH || 618 fs_ctx->export_flags & V9FS_SM_NONE) { 619 err = mknodat(dirfd, name, credp->fc_mode, credp->fc_rdev); 620 if (err == -1) { 621 goto out; 622 } 623 err = local_set_cred_passthrough(fs_ctx, dirfd, name, credp); 624 if (err == -1) { 625 goto err_end; 626 } 627 } 628 goto out; 629 630 err_end: 631 unlinkat_preserve_errno(dirfd, name, 0); 632 out: 633 close_preserve_errno(dirfd); 634 return err; 635 } 636 637 static int local_mkdir(FsContext *fs_ctx, V9fsPath *dir_path, 638 const char *name, FsCred *credp) 639 { 640 int err = -1; 641 int dirfd; 642 643 if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE && 644 local_is_mapped_file_metadata(fs_ctx, name)) { 645 errno = EINVAL; 646 return -1; 647 } 648 649 dirfd = local_opendir_nofollow(fs_ctx, dir_path->data); 650 if (dirfd == -1) { 651 return -1; 652 } 653 654 if (fs_ctx->export_flags & V9FS_SM_MAPPED || 655 fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) { 656 err = mkdirat(dirfd, name, SM_LOCAL_DIR_MODE_BITS); 657 if (err == -1) { 658 goto out; 659 } 660 credp->fc_mode = credp->fc_mode | S_IFDIR; 661 662 if (fs_ctx->export_flags & V9FS_SM_MAPPED) { 663 err = local_set_xattrat(dirfd, name, credp); 664 } else { 665 err = local_set_mapped_file_attrat(dirfd, name, credp); 666 } 667 if (err == -1) { 668 goto err_end; 669 } 670 } else if (fs_ctx->export_flags & V9FS_SM_PASSTHROUGH || 671 fs_ctx->export_flags & V9FS_SM_NONE) { 672 err = mkdirat(dirfd, name, credp->fc_mode); 673 if (err == -1) { 674 goto out; 675 } 676 err = local_set_cred_passthrough(fs_ctx, dirfd, name, credp); 677 if (err == -1) { 678 goto err_end; 679 } 680 } 681 goto out; 682 683 err_end: 684 unlinkat_preserve_errno(dirfd, name, AT_REMOVEDIR); 685 out: 686 close_preserve_errno(dirfd); 687 return err; 688 } 689 690 static int local_fstat(FsContext *fs_ctx, int fid_type, 691 V9fsFidOpenState *fs, struct stat *stbuf) 692 { 693 int err, fd; 694 695 if (fid_type == P9_FID_DIR) { 696 fd = dirfd(fs->dir.stream); 697 } else { 698 fd = fs->fd; 699 } 700 701 err = fstat(fd, stbuf); 702 if (err) { 703 return err; 704 } 705 if (fs_ctx->export_flags & V9FS_SM_MAPPED) { 706 /* Actual credentials are part of extended attrs */ 707 uid_t tmp_uid; 708 gid_t tmp_gid; 709 mode_t tmp_mode; 710 dev_t tmp_dev; 711 712 if (fgetxattr(fd, "user.virtfs.uid", &tmp_uid, sizeof(uid_t)) > 0) { 713 stbuf->st_uid = le32_to_cpu(tmp_uid); 714 } 715 if (fgetxattr(fd, "user.virtfs.gid", &tmp_gid, sizeof(gid_t)) > 0) { 716 stbuf->st_gid = le32_to_cpu(tmp_gid); 717 } 718 if (fgetxattr(fd, "user.virtfs.mode", &tmp_mode, sizeof(mode_t)) > 0) { 719 stbuf->st_mode = le32_to_cpu(tmp_mode); 720 } 721 if (fgetxattr(fd, "user.virtfs.rdev", &tmp_dev, sizeof(dev_t)) > 0) { 722 stbuf->st_rdev = le64_to_cpu(tmp_dev); 723 } 724 } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) { 725 errno = EOPNOTSUPP; 726 return -1; 727 } 728 return err; 729 } 730 731 static int local_open2(FsContext *fs_ctx, V9fsPath *dir_path, const char *name, 732 int flags, FsCred *credp, V9fsFidOpenState *fs) 733 { 734 int fd = -1; 735 int err = -1; 736 int dirfd; 737 738 if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE && 739 local_is_mapped_file_metadata(fs_ctx, name)) { 740 errno = EINVAL; 741 return -1; 742 } 743 744 /* 745 * Mark all the open to not follow symlinks 746 */ 747 flags |= O_NOFOLLOW; 748 749 dirfd = local_opendir_nofollow(fs_ctx, dir_path->data); 750 if (dirfd == -1) { 751 return -1; 752 } 753 754 /* Determine the security model */ 755 if (fs_ctx->export_flags & V9FS_SM_MAPPED || 756 fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) { 757 fd = openat_file(dirfd, name, flags, SM_LOCAL_MODE_BITS); 758 if (fd == -1) { 759 goto out; 760 } 761 credp->fc_mode = credp->fc_mode|S_IFREG; 762 if (fs_ctx->export_flags & V9FS_SM_MAPPED) { 763 /* Set cleint credentials in xattr */ 764 err = local_set_xattrat(dirfd, name, credp); 765 } else { 766 err = local_set_mapped_file_attrat(dirfd, name, credp); 767 } 768 if (err == -1) { 769 goto err_end; 770 } 771 } else if ((fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) || 772 (fs_ctx->export_flags & V9FS_SM_NONE)) { 773 fd = openat_file(dirfd, name, flags, credp->fc_mode); 774 if (fd == -1) { 775 goto out; 776 } 777 err = local_set_cred_passthrough(fs_ctx, dirfd, name, credp); 778 if (err == -1) { 779 goto err_end; 780 } 781 } 782 err = fd; 783 fs->fd = fd; 784 goto out; 785 786 err_end: 787 unlinkat_preserve_errno(dirfd, name, 788 flags & O_DIRECTORY ? AT_REMOVEDIR : 0); 789 close_preserve_errno(fd); 790 out: 791 close_preserve_errno(dirfd); 792 return err; 793 } 794 795 796 static int local_symlink(FsContext *fs_ctx, const char *oldpath, 797 V9fsPath *dir_path, const char *name, FsCred *credp) 798 { 799 int err = -1; 800 int dirfd; 801 802 if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE && 803 local_is_mapped_file_metadata(fs_ctx, name)) { 804 errno = EINVAL; 805 return -1; 806 } 807 808 dirfd = local_opendir_nofollow(fs_ctx, dir_path->data); 809 if (dirfd == -1) { 810 return -1; 811 } 812 813 /* Determine the security model */ 814 if (fs_ctx->export_flags & V9FS_SM_MAPPED || 815 fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) { 816 int fd; 817 ssize_t oldpath_size, write_size; 818 819 fd = openat_file(dirfd, name, O_CREAT | O_EXCL | O_RDWR, 820 SM_LOCAL_MODE_BITS); 821 if (fd == -1) { 822 goto out; 823 } 824 /* Write the oldpath (target) to the file. */ 825 oldpath_size = strlen(oldpath); 826 do { 827 write_size = write(fd, (void *)oldpath, oldpath_size); 828 } while (write_size == -1 && errno == EINTR); 829 close_preserve_errno(fd); 830 831 if (write_size != oldpath_size) { 832 goto err_end; 833 } 834 /* Set cleint credentials in symlink's xattr */ 835 credp->fc_mode = credp->fc_mode | S_IFLNK; 836 837 if (fs_ctx->export_flags & V9FS_SM_MAPPED) { 838 err = local_set_xattrat(dirfd, name, credp); 839 } else { 840 err = local_set_mapped_file_attrat(dirfd, name, credp); 841 } 842 if (err == -1) { 843 goto err_end; 844 } 845 } else if (fs_ctx->export_flags & V9FS_SM_PASSTHROUGH || 846 fs_ctx->export_flags & V9FS_SM_NONE) { 847 err = symlinkat(oldpath, dirfd, name); 848 if (err) { 849 goto out; 850 } 851 err = fchownat(dirfd, name, credp->fc_uid, credp->fc_gid, 852 AT_SYMLINK_NOFOLLOW); 853 if (err == -1) { 854 /* 855 * If we fail to change ownership and if we are 856 * using security model none. Ignore the error 857 */ 858 if ((fs_ctx->export_flags & V9FS_SEC_MASK) != V9FS_SM_NONE) { 859 goto err_end; 860 } else { 861 err = 0; 862 } 863 } 864 } 865 goto out; 866 867 err_end: 868 unlinkat_preserve_errno(dirfd, name, 0); 869 out: 870 close_preserve_errno(dirfd); 871 return err; 872 } 873 874 static int local_link(FsContext *ctx, V9fsPath *oldpath, 875 V9fsPath *dirpath, const char *name) 876 { 877 char *odirpath = g_path_get_dirname(oldpath->data); 878 char *oname = g_path_get_basename(oldpath->data); 879 int ret = -1; 880 int odirfd, ndirfd; 881 882 if (ctx->export_flags & V9FS_SM_MAPPED_FILE && 883 local_is_mapped_file_metadata(ctx, name)) { 884 errno = EINVAL; 885 return -1; 886 } 887 888 odirfd = local_opendir_nofollow(ctx, odirpath); 889 if (odirfd == -1) { 890 goto out; 891 } 892 893 ndirfd = local_opendir_nofollow(ctx, dirpath->data); 894 if (ndirfd == -1) { 895 close_preserve_errno(odirfd); 896 goto out; 897 } 898 899 ret = linkat(odirfd, oname, ndirfd, name, 0); 900 if (ret < 0) { 901 goto out_close; 902 } 903 904 /* now link the virtfs_metadata files */ 905 if (ctx->export_flags & V9FS_SM_MAPPED_FILE) { 906 int omap_dirfd, nmap_dirfd; 907 908 ret = mkdirat(ndirfd, VIRTFS_META_DIR, 0700); 909 if (ret < 0 && errno != EEXIST) { 910 goto err_undo_link; 911 } 912 913 omap_dirfd = openat_dir(odirfd, VIRTFS_META_DIR); 914 if (omap_dirfd == -1) { 915 goto err; 916 } 917 918 nmap_dirfd = openat_dir(ndirfd, VIRTFS_META_DIR); 919 if (nmap_dirfd == -1) { 920 close_preserve_errno(omap_dirfd); 921 goto err; 922 } 923 924 ret = linkat(omap_dirfd, oname, nmap_dirfd, name, 0); 925 close_preserve_errno(nmap_dirfd); 926 close_preserve_errno(omap_dirfd); 927 if (ret < 0 && errno != ENOENT) { 928 goto err_undo_link; 929 } 930 931 ret = 0; 932 } 933 goto out_close; 934 935 err: 936 ret = -1; 937 err_undo_link: 938 unlinkat_preserve_errno(ndirfd, name, 0); 939 out_close: 940 close_preserve_errno(ndirfd); 941 close_preserve_errno(odirfd); 942 out: 943 g_free(oname); 944 g_free(odirpath); 945 return ret; 946 } 947 948 static int local_truncate(FsContext *ctx, V9fsPath *fs_path, off_t size) 949 { 950 int fd, ret; 951 952 fd = local_open_nofollow(ctx, fs_path->data, O_WRONLY, 0); 953 if (fd == -1) { 954 return -1; 955 } 956 ret = ftruncate(fd, size); 957 close_preserve_errno(fd); 958 return ret; 959 } 960 961 static int local_chown(FsContext *fs_ctx, V9fsPath *fs_path, FsCred *credp) 962 { 963 char *dirpath = g_path_get_dirname(fs_path->data); 964 char *name = g_path_get_basename(fs_path->data); 965 int ret = -1; 966 int dirfd; 967 968 dirfd = local_opendir_nofollow(fs_ctx, dirpath); 969 if (dirfd == -1) { 970 goto out; 971 } 972 973 if ((credp->fc_uid == -1 && credp->fc_gid == -1) || 974 (fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) || 975 (fs_ctx->export_flags & V9FS_SM_NONE)) { 976 ret = fchownat(dirfd, name, credp->fc_uid, credp->fc_gid, 977 AT_SYMLINK_NOFOLLOW); 978 } else if (fs_ctx->export_flags & V9FS_SM_MAPPED) { 979 ret = local_set_xattrat(dirfd, name, credp); 980 } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) { 981 ret = local_set_mapped_file_attrat(dirfd, name, credp); 982 } 983 984 close_preserve_errno(dirfd); 985 out: 986 g_free(name); 987 g_free(dirpath); 988 return ret; 989 } 990 991 static int local_utimensat(FsContext *s, V9fsPath *fs_path, 992 const struct timespec *buf) 993 { 994 char *dirpath = g_path_get_dirname(fs_path->data); 995 char *name = g_path_get_basename(fs_path->data); 996 int dirfd, ret = -1; 997 998 dirfd = local_opendir_nofollow(s, dirpath); 999 if (dirfd == -1) { 1000 goto out; 1001 } 1002 1003 ret = utimensat(dirfd, name, buf, AT_SYMLINK_NOFOLLOW); 1004 close_preserve_errno(dirfd); 1005 out: 1006 g_free(dirpath); 1007 g_free(name); 1008 return ret; 1009 } 1010 1011 static int local_unlinkat_common(FsContext *ctx, int dirfd, const char *name, 1012 int flags) 1013 { 1014 int ret = -1; 1015 1016 if (ctx->export_flags & V9FS_SM_MAPPED_FILE) { 1017 int map_dirfd; 1018 1019 /* We need to remove the metadata as well: 1020 * - the metadata directory if we're removing a directory 1021 * - the metadata file in the parent's metadata directory 1022 * 1023 * If any of these are missing (ie, ENOENT) then we're probably 1024 * trying to remove something that wasn't created in mapped-file 1025 * mode. We just ignore the error. 1026 */ 1027 if (flags == AT_REMOVEDIR) { 1028 int fd; 1029 1030 fd = openat_dir(dirfd, name); 1031 if (fd == -1) { 1032 goto err_out; 1033 } 1034 ret = unlinkat(fd, VIRTFS_META_DIR, AT_REMOVEDIR); 1035 close_preserve_errno(fd); 1036 if (ret < 0 && errno != ENOENT) { 1037 goto err_out; 1038 } 1039 } 1040 map_dirfd = openat_dir(dirfd, VIRTFS_META_DIR); 1041 if (map_dirfd != -1) { 1042 ret = unlinkat(map_dirfd, name, 0); 1043 close_preserve_errno(map_dirfd); 1044 if (ret < 0 && errno != ENOENT) { 1045 goto err_out; 1046 } 1047 } else if (errno != ENOENT) { 1048 goto err_out; 1049 } 1050 } 1051 1052 ret = unlinkat(dirfd, name, flags); 1053 err_out: 1054 return ret; 1055 } 1056 1057 static int local_remove(FsContext *ctx, const char *path) 1058 { 1059 struct stat stbuf; 1060 char *dirpath = g_path_get_dirname(path); 1061 char *name = g_path_get_basename(path); 1062 int flags = 0; 1063 int dirfd; 1064 int err = -1; 1065 1066 dirfd = local_opendir_nofollow(ctx, dirpath); 1067 if (dirfd == -1) { 1068 goto out; 1069 } 1070 1071 if (fstatat(dirfd, path, &stbuf, AT_SYMLINK_NOFOLLOW) < 0) { 1072 goto err_out; 1073 } 1074 1075 if (S_ISDIR(stbuf.st_mode)) { 1076 flags |= AT_REMOVEDIR; 1077 } 1078 1079 err = local_unlinkat_common(ctx, dirfd, name, flags); 1080 err_out: 1081 close_preserve_errno(dirfd); 1082 out: 1083 g_free(name); 1084 g_free(dirpath); 1085 return err; 1086 } 1087 1088 static int local_fsync(FsContext *ctx, int fid_type, 1089 V9fsFidOpenState *fs, int datasync) 1090 { 1091 int fd; 1092 1093 if (fid_type == P9_FID_DIR) { 1094 fd = dirfd(fs->dir.stream); 1095 } else { 1096 fd = fs->fd; 1097 } 1098 1099 if (datasync) { 1100 return qemu_fdatasync(fd); 1101 } else { 1102 return fsync(fd); 1103 } 1104 } 1105 1106 static int local_statfs(FsContext *s, V9fsPath *fs_path, struct statfs *stbuf) 1107 { 1108 int fd, ret; 1109 1110 fd = local_open_nofollow(s, fs_path->data, O_RDONLY, 0); 1111 if (fd == -1) { 1112 return -1; 1113 } 1114 ret = fstatfs(fd, stbuf); 1115 close_preserve_errno(fd); 1116 return ret; 1117 } 1118 1119 static ssize_t local_lgetxattr(FsContext *ctx, V9fsPath *fs_path, 1120 const char *name, void *value, size_t size) 1121 { 1122 char *path = fs_path->data; 1123 1124 return v9fs_get_xattr(ctx, path, name, value, size); 1125 } 1126 1127 static ssize_t local_llistxattr(FsContext *ctx, V9fsPath *fs_path, 1128 void *value, size_t size) 1129 { 1130 char *path = fs_path->data; 1131 1132 return v9fs_list_xattr(ctx, path, value, size); 1133 } 1134 1135 static int local_lsetxattr(FsContext *ctx, V9fsPath *fs_path, const char *name, 1136 void *value, size_t size, int flags) 1137 { 1138 char *path = fs_path->data; 1139 1140 return v9fs_set_xattr(ctx, path, name, value, size, flags); 1141 } 1142 1143 static int local_lremovexattr(FsContext *ctx, V9fsPath *fs_path, 1144 const char *name) 1145 { 1146 char *path = fs_path->data; 1147 1148 return v9fs_remove_xattr(ctx, path, name); 1149 } 1150 1151 static int local_name_to_path(FsContext *ctx, V9fsPath *dir_path, 1152 const char *name, V9fsPath *target) 1153 { 1154 if (ctx->export_flags & V9FS_SM_MAPPED_FILE && 1155 local_is_mapped_file_metadata(ctx, name)) { 1156 errno = EINVAL; 1157 return -1; 1158 } 1159 1160 if (dir_path) { 1161 if (!strcmp(name, ".")) { 1162 /* "." relative to "foo/bar" is "foo/bar" */ 1163 v9fs_path_copy(target, dir_path); 1164 } else if (!strcmp(name, "..")) { 1165 if (!strcmp(dir_path->data, ".")) { 1166 /* ".." relative to the root is "." */ 1167 v9fs_path_sprintf(target, "."); 1168 } else { 1169 char *tmp = g_path_get_dirname(dir_path->data); 1170 /* Symbolic links are resolved by the client. We can assume 1171 * that ".." relative to "foo/bar" is equivalent to "foo" 1172 */ 1173 v9fs_path_sprintf(target, "%s", tmp); 1174 g_free(tmp); 1175 } 1176 } else { 1177 assert(!strchr(name, '/')); 1178 v9fs_path_sprintf(target, "%s/%s", dir_path->data, name); 1179 } 1180 } else if (!strcmp(name, "/") || !strcmp(name, ".") || 1181 !strcmp(name, "..")) { 1182 /* This is the root fid */ 1183 v9fs_path_sprintf(target, "."); 1184 } else { 1185 assert(!strchr(name, '/')); 1186 v9fs_path_sprintf(target, "./%s", name); 1187 } 1188 return 0; 1189 } 1190 1191 static int local_renameat(FsContext *ctx, V9fsPath *olddir, 1192 const char *old_name, V9fsPath *newdir, 1193 const char *new_name) 1194 { 1195 int ret; 1196 int odirfd, ndirfd; 1197 1198 if (ctx->export_flags & V9FS_SM_MAPPED_FILE && 1199 (local_is_mapped_file_metadata(ctx, old_name) || 1200 local_is_mapped_file_metadata(ctx, new_name))) { 1201 errno = EINVAL; 1202 return -1; 1203 } 1204 1205 odirfd = local_opendir_nofollow(ctx, olddir->data); 1206 if (odirfd == -1) { 1207 return -1; 1208 } 1209 1210 ndirfd = local_opendir_nofollow(ctx, newdir->data); 1211 if (ndirfd == -1) { 1212 close_preserve_errno(odirfd); 1213 return -1; 1214 } 1215 1216 ret = renameat(odirfd, old_name, ndirfd, new_name); 1217 if (ret < 0) { 1218 goto out; 1219 } 1220 1221 if (ctx->export_flags & V9FS_SM_MAPPED_FILE) { 1222 int omap_dirfd, nmap_dirfd; 1223 1224 ret = mkdirat(ndirfd, VIRTFS_META_DIR, 0700); 1225 if (ret < 0 && errno != EEXIST) { 1226 goto err_undo_rename; 1227 } 1228 1229 omap_dirfd = openat_dir(odirfd, VIRTFS_META_DIR); 1230 if (omap_dirfd == -1) { 1231 goto err; 1232 } 1233 1234 nmap_dirfd = openat_dir(ndirfd, VIRTFS_META_DIR); 1235 if (nmap_dirfd == -1) { 1236 close_preserve_errno(omap_dirfd); 1237 goto err; 1238 } 1239 1240 /* rename the .virtfs_metadata files */ 1241 ret = renameat(omap_dirfd, old_name, nmap_dirfd, new_name); 1242 close_preserve_errno(nmap_dirfd); 1243 close_preserve_errno(omap_dirfd); 1244 if (ret < 0 && errno != ENOENT) { 1245 goto err_undo_rename; 1246 } 1247 1248 ret = 0; 1249 } 1250 goto out; 1251 1252 err: 1253 ret = -1; 1254 err_undo_rename: 1255 renameat_preserve_errno(ndirfd, new_name, odirfd, old_name); 1256 out: 1257 close_preserve_errno(ndirfd); 1258 close_preserve_errno(odirfd); 1259 return ret; 1260 } 1261 1262 static void v9fs_path_init_dirname(V9fsPath *path, const char *str) 1263 { 1264 path->data = g_path_get_dirname(str); 1265 path->size = strlen(path->data) + 1; 1266 } 1267 1268 static int local_rename(FsContext *ctx, const char *oldpath, 1269 const char *newpath) 1270 { 1271 int err; 1272 char *oname = g_path_get_basename(oldpath); 1273 char *nname = g_path_get_basename(newpath); 1274 V9fsPath olddir, newdir; 1275 1276 v9fs_path_init_dirname(&olddir, oldpath); 1277 v9fs_path_init_dirname(&newdir, newpath); 1278 1279 err = local_renameat(ctx, &olddir, oname, &newdir, nname); 1280 1281 v9fs_path_free(&newdir); 1282 v9fs_path_free(&olddir); 1283 g_free(nname); 1284 g_free(oname); 1285 1286 return err; 1287 } 1288 1289 static int local_unlinkat(FsContext *ctx, V9fsPath *dir, 1290 const char *name, int flags) 1291 { 1292 int ret; 1293 int dirfd; 1294 1295 if (ctx->export_flags & V9FS_SM_MAPPED_FILE && 1296 local_is_mapped_file_metadata(ctx, name)) { 1297 errno = EINVAL; 1298 return -1; 1299 } 1300 1301 dirfd = local_opendir_nofollow(ctx, dir->data); 1302 if (dirfd == -1) { 1303 return -1; 1304 } 1305 1306 ret = local_unlinkat_common(ctx, dirfd, name, flags); 1307 close_preserve_errno(dirfd); 1308 return ret; 1309 } 1310 1311 static int local_ioc_getversion(FsContext *ctx, V9fsPath *path, 1312 mode_t st_mode, uint64_t *st_gen) 1313 { 1314 #ifdef FS_IOC_GETVERSION 1315 int err; 1316 V9fsFidOpenState fid_open; 1317 1318 /* 1319 * Do not try to open special files like device nodes, fifos etc 1320 * We can get fd for regular files and directories only 1321 */ 1322 if (!S_ISREG(st_mode) && !S_ISDIR(st_mode)) { 1323 errno = ENOTTY; 1324 return -1; 1325 } 1326 err = local_open(ctx, path, O_RDONLY, &fid_open); 1327 if (err < 0) { 1328 return err; 1329 } 1330 err = ioctl(fid_open.fd, FS_IOC_GETVERSION, st_gen); 1331 local_close(ctx, &fid_open); 1332 return err; 1333 #else 1334 errno = ENOTTY; 1335 return -1; 1336 #endif 1337 } 1338 1339 static int local_init(FsContext *ctx) 1340 { 1341 struct statfs stbuf; 1342 LocalData *data = g_malloc(sizeof(*data)); 1343 1344 data->mountfd = open(ctx->fs_root, O_DIRECTORY | O_RDONLY); 1345 if (data->mountfd == -1) { 1346 goto err; 1347 } 1348 1349 #ifdef FS_IOC_GETVERSION 1350 /* 1351 * use ioc_getversion only if the ioctl is definied 1352 */ 1353 if (fstatfs(data->mountfd, &stbuf) < 0) { 1354 close_preserve_errno(data->mountfd); 1355 goto err; 1356 } 1357 switch (stbuf.f_type) { 1358 case EXT2_SUPER_MAGIC: 1359 case BTRFS_SUPER_MAGIC: 1360 case REISERFS_SUPER_MAGIC: 1361 case XFS_SUPER_MAGIC: 1362 ctx->exops.get_st_gen = local_ioc_getversion; 1363 break; 1364 } 1365 #endif 1366 1367 if (ctx->export_flags & V9FS_SM_PASSTHROUGH) { 1368 ctx->xops = passthrough_xattr_ops; 1369 } else if (ctx->export_flags & V9FS_SM_MAPPED) { 1370 ctx->xops = mapped_xattr_ops; 1371 } else if (ctx->export_flags & V9FS_SM_NONE) { 1372 ctx->xops = none_xattr_ops; 1373 } else if (ctx->export_flags & V9FS_SM_MAPPED_FILE) { 1374 /* 1375 * xattr operation for mapped-file and passthrough 1376 * remain same. 1377 */ 1378 ctx->xops = passthrough_xattr_ops; 1379 } 1380 ctx->export_flags |= V9FS_PATHNAME_FSCONTEXT; 1381 1382 ctx->private = data; 1383 return 0; 1384 1385 err: 1386 g_free(data); 1387 return -1; 1388 } 1389 1390 static void local_cleanup(FsContext *ctx) 1391 { 1392 LocalData *data = ctx->private; 1393 1394 close(data->mountfd); 1395 g_free(data); 1396 } 1397 1398 static int local_parse_opts(QemuOpts *opts, struct FsDriverEntry *fse) 1399 { 1400 const char *sec_model = qemu_opt_get(opts, "security_model"); 1401 const char *path = qemu_opt_get(opts, "path"); 1402 Error *err = NULL; 1403 1404 if (!sec_model) { 1405 error_report("Security model not specified, local fs needs security model"); 1406 error_printf("valid options are:" 1407 "\tsecurity_model=[passthrough|mapped-xattr|mapped-file|none]\n"); 1408 return -1; 1409 } 1410 1411 if (!strcmp(sec_model, "passthrough")) { 1412 fse->export_flags |= V9FS_SM_PASSTHROUGH; 1413 } else if (!strcmp(sec_model, "mapped") || 1414 !strcmp(sec_model, "mapped-xattr")) { 1415 fse->export_flags |= V9FS_SM_MAPPED; 1416 } else if (!strcmp(sec_model, "none")) { 1417 fse->export_flags |= V9FS_SM_NONE; 1418 } else if (!strcmp(sec_model, "mapped-file")) { 1419 fse->export_flags |= V9FS_SM_MAPPED_FILE; 1420 } else { 1421 error_report("Invalid security model %s specified", sec_model); 1422 error_printf("valid options are:" 1423 "\t[passthrough|mapped-xattr|mapped-file|none]\n"); 1424 return -1; 1425 } 1426 1427 if (!path) { 1428 error_report("fsdev: No path specified"); 1429 return -1; 1430 } 1431 1432 fsdev_throttle_parse_opts(opts, &fse->fst, &err); 1433 if (err) { 1434 error_reportf_err(err, "Throttle configuration is not valid: "); 1435 return -1; 1436 } 1437 1438 fse->path = g_strdup(path); 1439 1440 return 0; 1441 } 1442 1443 FileOperations local_ops = { 1444 .parse_opts = local_parse_opts, 1445 .init = local_init, 1446 .cleanup = local_cleanup, 1447 .lstat = local_lstat, 1448 .readlink = local_readlink, 1449 .close = local_close, 1450 .closedir = local_closedir, 1451 .open = local_open, 1452 .opendir = local_opendir, 1453 .rewinddir = local_rewinddir, 1454 .telldir = local_telldir, 1455 .readdir = local_readdir, 1456 .seekdir = local_seekdir, 1457 .preadv = local_preadv, 1458 .pwritev = local_pwritev, 1459 .chmod = local_chmod, 1460 .mknod = local_mknod, 1461 .mkdir = local_mkdir, 1462 .fstat = local_fstat, 1463 .open2 = local_open2, 1464 .symlink = local_symlink, 1465 .link = local_link, 1466 .truncate = local_truncate, 1467 .rename = local_rename, 1468 .chown = local_chown, 1469 .utimensat = local_utimensat, 1470 .remove = local_remove, 1471 .fsync = local_fsync, 1472 .statfs = local_statfs, 1473 .lgetxattr = local_lgetxattr, 1474 .llistxattr = local_llistxattr, 1475 .lsetxattr = local_lsetxattr, 1476 .lremovexattr = local_lremovexattr, 1477 .name_to_path = local_name_to_path, 1478 .renameat = local_renameat, 1479 .unlinkat = local_unlinkat, 1480 }; 1481