|
26 | 26 |
|
27 | 27 | #include "dirent-util.h" |
28 | 28 | #include "fd-util.h" |
| 29 | +#include "fileio.h" |
29 | 30 | #include "fs-util.h" |
30 | 31 | #include "macro.h" |
| 32 | +#include "memfd-util.h" |
31 | 33 | #include "missing.h" |
32 | 34 | #include "parse-util.h" |
33 | 35 | #include "path-util.h" |
@@ -421,3 +423,158 @@ int move_fd(int from, int to, int cloexec) { |
421 | 423 |
|
422 | 424 | return to; |
423 | 425 | } |
| 426 | + |
| 427 | +int acquire_data_fd(const void *data, size_t size, unsigned flags) { |
| 428 | + |
| 429 | + char procfs_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int)]; |
| 430 | + _cleanup_close_pair_ int pipefds[2] = { -1, -1 }; |
| 431 | + char pattern[] = "/dev/shm/data-fd-XXXXXX"; |
| 432 | + _cleanup_close_ int fd = -1; |
| 433 | + int isz = 0, r; |
| 434 | + ssize_t n; |
| 435 | + off_t f; |
| 436 | + |
| 437 | + assert(data || size == 0); |
| 438 | + |
| 439 | + /* Acquire a read-only file descriptor that when read from returns the specified data. This is much more |
| 440 | + * complex than I wish it was. But here's why: |
| 441 | + * |
| 442 | + * a) First we try to use memfds. They are the best option, as we can seal them nicely to make them |
| 443 | + * read-only. Unfortunately they require kernel 3.17, and – at the time of writing – we still support 3.14. |
| 444 | + * |
| 445 | + * b) Then, we try classic pipes. They are the second best options, as we can close the writing side, retaining |
| 446 | + * a nicely read-only fd in the reading side. However, they are by default quite small, and unprivileged |
| 447 | + * clients can only bump their size to a system-wide limit, which might be quite low. |
| 448 | + * |
| 449 | + * c) Then, we try an O_TMPFILE file in /dev/shm (that dir is the only suitable one known to exist from |
| 450 | + * earliest boot on). To make it read-only we open the fd a second time with O_RDONLY via |
| 451 | + * /proc/self/<fd>. Unfortunately O_TMPFILE is not available on older kernels on tmpfs. |
| 452 | + * |
| 453 | + * d) Finally, we try creating a regular file in /dev/shm, which we then delete. |
| 454 | + * |
| 455 | + * It sucks a bit that depending on the situation we return very different objects here, but that's Linux I |
| 456 | + * figure. */ |
| 457 | + |
| 458 | + if (size == 0 && ((flags & ACQUIRE_NO_DEV_NULL) == 0)) { |
| 459 | + /* As a special case, return /dev/null if we have been called for an empty data block */ |
| 460 | + r = open("/dev/null", O_RDONLY|O_CLOEXEC|O_NOCTTY); |
| 461 | + if (r < 0) |
| 462 | + return -errno; |
| 463 | + |
| 464 | + return r; |
| 465 | + } |
| 466 | + |
| 467 | + if ((flags & ACQUIRE_NO_MEMFD) == 0) { |
| 468 | + fd = memfd_new("data-fd"); |
| 469 | + if (fd < 0) |
| 470 | + goto try_pipe; |
| 471 | + |
| 472 | + n = write(fd, data, size); |
| 473 | + if (n < 0) |
| 474 | + return -errno; |
| 475 | + if ((size_t) n != size) |
| 476 | + return -EIO; |
| 477 | + |
| 478 | + f = lseek(fd, 0, SEEK_SET); |
| 479 | + if (f != 0) |
| 480 | + return -errno; |
| 481 | + |
| 482 | + r = memfd_set_sealed(fd); |
| 483 | + if (r < 0) |
| 484 | + return r; |
| 485 | + |
| 486 | + r = fd; |
| 487 | + fd = -1; |
| 488 | + |
| 489 | + return r; |
| 490 | + } |
| 491 | + |
| 492 | +try_pipe: |
| 493 | + if ((flags & ACQUIRE_NO_PIPE) == 0) { |
| 494 | + if (pipe2(pipefds, O_CLOEXEC|O_NONBLOCK) < 0) |
| 495 | + return -errno; |
| 496 | + |
| 497 | + isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0); |
| 498 | + if (isz < 0) |
| 499 | + return -errno; |
| 500 | + |
| 501 | + if ((size_t) isz < size) { |
| 502 | + isz = (int) size; |
| 503 | + if (isz < 0 || (size_t) isz != size) |
| 504 | + return -E2BIG; |
| 505 | + |
| 506 | + /* Try to bump the pipe size */ |
| 507 | + (void) fcntl(pipefds[1], F_SETPIPE_SZ, isz); |
| 508 | + |
| 509 | + /* See if that worked */ |
| 510 | + isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0); |
| 511 | + if (isz < 0) |
| 512 | + return -errno; |
| 513 | + |
| 514 | + if ((size_t) isz < size) |
| 515 | + goto try_dev_shm; |
| 516 | + } |
| 517 | + |
| 518 | + n = write(pipefds[1], data, size); |
| 519 | + if (n < 0) |
| 520 | + return -errno; |
| 521 | + if ((size_t) n != size) |
| 522 | + return -EIO; |
| 523 | + |
| 524 | + (void) fd_nonblock(pipefds[0], false); |
| 525 | + |
| 526 | + r = pipefds[0]; |
| 527 | + pipefds[0] = -1; |
| 528 | + |
| 529 | + return r; |
| 530 | + } |
| 531 | + |
| 532 | +try_dev_shm: |
| 533 | + if ((flags & ACQUIRE_NO_TMPFILE) == 0) { |
| 534 | + fd = open("/dev/shm", O_RDWR|O_TMPFILE|O_CLOEXEC, 0500); |
| 535 | + if (fd < 0) |
| 536 | + goto try_dev_shm_without_o_tmpfile; |
| 537 | + |
| 538 | + n = write(fd, data, size); |
| 539 | + if (n < 0) |
| 540 | + return -errno; |
| 541 | + if ((size_t) n != size) |
| 542 | + return -EIO; |
| 543 | + |
| 544 | + /* Let's reopen the thing, in order to get an O_RDONLY fd for the original O_RDWR one */ |
| 545 | + xsprintf(procfs_path, "/proc/self/fd/%i", fd); |
| 546 | + r = open(procfs_path, O_RDONLY|O_CLOEXEC); |
| 547 | + if (r < 0) |
| 548 | + return -errno; |
| 549 | + |
| 550 | + return r; |
| 551 | + } |
| 552 | + |
| 553 | +try_dev_shm_without_o_tmpfile: |
| 554 | + if ((flags & ACQUIRE_NO_REGULAR) == 0) { |
| 555 | + fd = mkostemp_safe(pattern); |
| 556 | + if (fd < 0) |
| 557 | + return fd; |
| 558 | + |
| 559 | + n = write(fd, data, size); |
| 560 | + if (n < 0) { |
| 561 | + r = -errno; |
| 562 | + goto unlink_and_return; |
| 563 | + } |
| 564 | + if ((size_t) n != size) { |
| 565 | + r = -EIO; |
| 566 | + goto unlink_and_return; |
| 567 | + } |
| 568 | + |
| 569 | + /* Let's reopen the thing, in order to get an O_RDONLY fd for the original O_RDWR one */ |
| 570 | + r = open(pattern, O_RDONLY|O_CLOEXEC); |
| 571 | + if (r < 0) |
| 572 | + r = -errno; |
| 573 | + |
| 574 | + unlink_and_return: |
| 575 | + (void) unlink(pattern); |
| 576 | + return r; |
| 577 | + } |
| 578 | + |
| 579 | + return -EOPNOTSUPP; |
| 580 | +} |
0 commit comments