blob: c7e5c277afa1f0b26539009046827c5234378d8c [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fs-management/mount.h>
#include <fs/client.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <zircon/compiler.h>
#include <zircon/device/vfs.h>
#include <zircon/processargs.h>
#include <zircon/syscalls.h>
#include <fdio/limits.h>
#include <fdio/util.h>
#include <fdio/vfs.h>
#define HEADER_SIZE 4096
static const uint8_t minfs_magic[16] = {
0x21, 0x4d, 0x69, 0x6e, 0x46, 0x53, 0x21, 0x00,
0x04, 0xd3, 0xd3, 0xd3, 0xd3, 0x00, 0x50, 0x38,
};
static const uint8_t blobstore_magic[16] = {
0x21, 0x4d, 0x69, 0x9e, 0x47, 0x53, 0x21, 0xac,
0x14, 0xd3, 0xd3, 0xd4, 0xd4, 0x00, 0x50, 0x98,
};
static const uint8_t gpt_magic[16] = {
0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54,
0x00, 0x00, 0x01, 0x00, 0x5c, 0x00, 0x00, 0x00,
};
static const uint8_t fvm_magic[8] = {
0x46, 0x56, 0x4d, 0x20, 0x50, 0x41, 0x52, 0x54,
};
disk_format_t detect_disk_format(int fd) {
uint8_t data[HEADER_SIZE];
if (read(fd, data, sizeof(data)) != sizeof(data)) {
fprintf(stderr, "Error reading block device\n");
return -1;
}
if (!memcmp(data, fvm_magic, sizeof(fvm_magic))) {
return DISK_FORMAT_FVM;
} else if (!memcmp(data + 0x200, gpt_magic, sizeof(gpt_magic))) {
return DISK_FORMAT_GPT;
} else if (!memcmp(data, minfs_magic, sizeof(minfs_magic))) {
return DISK_FORMAT_MINFS;
} else if (!memcmp(data, blobstore_magic, sizeof(blobstore_magic))) {
return DISK_FORMAT_BLOBFS;
} else if ((data[510] == 0x55 && data[511] == 0xAA)) {
if ((data[38] == 0x29 || data[66] == 0x29)) {
// 0x55AA are always placed at offset 510 and 511 for FAT filesystems.
// 0x29 is the Boot Signature, but it is placed at either offset 38 or
// 66 (depending on FAT type).
return DISK_FORMAT_FAT;
} else {
return DISK_FORMAT_MBR;
}
}
return DISK_FORMAT_UNKNOWN;
}
// Initializes 'hnd' and 'ids' with the root handle and block device handle.
// Consumes devicefd.
static zx_status_t mount_prepare_handles(int devicefd, zx_handle_t* mount_handle_out,
zx_handle_t* hnd, uint32_t* ids, size_t* n) {
zx_status_t status;
zx_handle_t mountee_handle;
if ((status = zx_channel_create(0, &mountee_handle, mount_handle_out)) != ZX_OK) {
close(devicefd);
return status;
}
hnd[*n] = mountee_handle;
ids[*n] = PA_USER0;
*n = *n + 1;
if ((status = fdio_transfer_fd(devicefd, FS_FD_BLOCKDEVICE, hnd + *n, ids + *n)) <= 0) {
fprintf(stderr, "Failed to access device handle\n");
zx_handle_close(mountee_handle);
zx_handle_close(*mount_handle_out);
close(devicefd);
return status != 0 ? status : ZX_ERR_BAD_STATE;
}
*n = *n + status;
return ZX_OK;
}
// Describes the mountpoint of the to-be-mounted root,
// either by fd or by path (but never both).
typedef struct mountpoint {
union {
const char* path;
int fd;
};
uint32_t flags;
} mountpoint_t;
// Calls the 'launch callback' and mounts the remote handle to the target vnode, if successful.
static zx_status_t launch_and_mount(LaunchCallback cb, const mount_options_t* options,
const char** argv, int argc, zx_handle_t* hnd,
uint32_t* ids, size_t n, mountpoint_t* mp, zx_handle_t root) {
zx_status_t status;
if ((status = cb(argc, argv, hnd, ids, n)) != ZX_OK) {
goto fail;
}
if (options->wait_until_ready) {
// Wait until the filesystem is ready to take incoming requests
zx_signals_t observed;
status = zx_object_wait_one(root, ZX_USER_SIGNAL_0 | ZX_CHANNEL_PEER_CLOSED,
ZX_TIME_INFINITE, &observed);
if ((status != ZX_OK) || (observed & ZX_CHANNEL_PEER_CLOSED)) {
status = (status != ZX_OK) ? status : ZX_ERR_BAD_STATE;
goto fail;
}
}
// Install remote handle.
if (options->create_mountpoint) {
int fd = open("/", O_RDONLY | O_DIRECTORY | O_ADMIN);
if (fd < 0) {
goto fail;
}
size_t config_size = sizeof(mount_mkdir_config_t) + strlen(mp->path) + 1;
mount_mkdir_config_t* config = malloc(config_size);
if (config == NULL) {
close(fd);
goto fail;
}
config->fs_root = root;
config->flags = mp->flags;
strcpy(config->name, mp->path);
// Ioctl will close root for us if an error occurs
status = ioctl_vfs_mount_mkdir_fs(fd, config, config_size);
close(fd);
free(config);
return status;
}
// Ioctl will close root for us if an error occurs
return ioctl_vfs_mount_fs(mp->fd, &root);
fail:
// We've entered a failure case where the filesystem process (which may or may not be alive)
// had a *chance* to be spawned, but cannot be attached to a vnode (for whatever reason).
// Rather than abandoning the filesystem process (maybe causing dirty bits to be set), give it a
// chance to shutdown properly.
//
// The unmount process is a little atypical, since we're just sending a signal over a handle,
// rather than detaching the mounted filesytem from the "parent" filesystem.
vfs_unmount_handle(root, options->wait_until_ready ? ZX_TIME_INFINITE : 0);
return status;
}
static zx_status_t mount_mxfs(const char* binary, int devicefd, mountpoint_t* mp,
const mount_options_t* options, LaunchCallback cb) {
zx_handle_t hnd[FDIO_MAX_HANDLES * 2];
uint32_t ids[FDIO_MAX_HANDLES * 2];
size_t n = 0;
zx_handle_t root;
zx_status_t status;
if ((status = mount_prepare_handles(devicefd, &root, hnd, ids, &n)) != ZX_OK) {
return status;
}
if (options->verbose_mount) {
printf("fs_mount: Launching %s\n", binary);
}
const char* argv[] = {binary, "mount"};
return launch_and_mount(cb, options, argv, countof(argv), hnd, ids, n, mp, root);
}
static zx_status_t mount_fat(int devicefd, mountpoint_t* mp, const mount_options_t* options,
LaunchCallback cb) {
zx_handle_t hnd[FDIO_MAX_HANDLES * 2];
uint32_t ids[FDIO_MAX_HANDLES * 2];
size_t n = 0;
zx_handle_t root;
zx_status_t status;
if ((status = mount_prepare_handles(devicefd, &root, hnd, ids, &n)) != ZX_OK) {
return status;
}
char readonly_arg[64];
snprintf(readonly_arg, sizeof(readonly_arg), "-readonly=%s",
options->readonly ? "true" : "false");
char blockfd_arg[64];
snprintf(blockfd_arg, sizeof(blockfd_arg), "-blockFD=%d", FS_FD_BLOCKDEVICE);
if (options->verbose_mount) {
printf("fs_mount: Launching ThinFS\n");
}
const char* argv[] = {
"/system/bin/thinfs",
readonly_arg,
blockfd_arg,
"mount",
};
return launch_and_mount(cb, options, argv, countof(argv), hnd, ids, n, mp, root);
}
zx_status_t fmount_common(int devicefd, mountpoint_t* mp, disk_format_t df,
const mount_options_t* options, LaunchCallback cb) {
switch (df) {
case DISK_FORMAT_MINFS:
return mount_mxfs("/boot/bin/minfs", devicefd, mp, options, cb);
case DISK_FORMAT_BLOBFS:
return mount_mxfs("/boot/bin/blobstore", devicefd, mp, options, cb);
case DISK_FORMAT_FAT:
return mount_fat(devicefd, mp, options, cb);
default:
close(devicefd);
return ZX_ERR_NOT_SUPPORTED;
}
}
zx_status_t fmount(int devicefd, int mountfd, disk_format_t df,
const mount_options_t* options, LaunchCallback cb) {
mountpoint_t mp;
mp.fd = mountfd;
mp.flags = 0;
return fmount_common(devicefd, &mp, df, options, cb);
}
zx_status_t mount(int devicefd, const char* mountpath, disk_format_t df,
const mount_options_t* options, LaunchCallback cb) {
mountpoint_t mp;
mp.flags = 0;
if (options->create_mountpoint) {
// Using 'path' for mountpoint
mp.path = mountpath;
} else {
// Open mountpoint; use it directly
if ((mp.fd = open(mountpath, O_RDONLY | O_DIRECTORY | O_ADMIN)) < 0) {
return ZX_ERR_BAD_STATE;
}
}
zx_status_t status = fmount_common(devicefd, &mp, df, options, cb);
if (!options->create_mountpoint) {
close(mp.fd);
}
return status;
}
zx_status_t fumount(int mountfd) {
zx_handle_t h;
zx_status_t status = ioctl_vfs_unmount_node(mountfd, &h);
if (status < 0) {
fprintf(stderr, "Could not unmount filesystem: %d\n", status);
} else {
status = vfs_unmount_handle(h, ZX_TIME_INFINITE);
}
return status;
}
zx_status_t umount(const char* mountpath) {
int fd = open(mountpath, O_DIRECTORY | O_NOREMOTE | O_ADMIN);
if (fd < 0) {
fprintf(stderr, "Could not open directory: %s\n", strerror(errno));
return ZX_ERR_BAD_STATE;
}
zx_status_t status = fumount(fd);
close(fd);
return status;
}