blob: 68455638c020198f2d6fcd93477a0ed4325d2836 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
* Copyright (C) 2010-2021 Tuxera Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <fcntl.h>
#include <sys/mount.h>
#define LOG_TAG "Vold::Exfat"
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <logwrap/logwrap.h>
#include "Exfat.h"
#include "Utils.h"
using android::base::StringPrintf;
namespace android {
namespace vold {
namespace exfat {
static const char* kMkfsPath = "/system/bin/mkexfat";
static const char* kFsckPath = "/system/bin/exfatck";
status_t Detect(const std::string& source, bool *outResult) {
const char *const fsPath = source.c_str();
int retval = -1;
int fd = open(fsPath, O_RDONLY | O_CLOEXEC);
if (fd != -1) {
loff_t seek_res = lseek64(fd, 0, SEEK_SET);
if (seek_res == 0) {
char sb_region[4096];
ssize_t read_res = read(fd, sb_region, 4096);
if (read_res >= 512) {
if (!memcmp(&sb_region[3], "EXFAT ", 8)) {
LOG(INFO) << "exFAT filesystem detected.";
*outResult = true;
}
else if (!memcmp(&sb_region[0], "RRaAXFAT ", 11)) {
LOG(INFO) << "Corrupted exFAT filesystem detected. Fixing.";
*outResult = true;
}
else {
LOG(VERBOSE) << "Filesystem detection failed (not an exFAT "
<< "filesystem).";
*outResult = false;
}
retval = 0;
}
else if (read_res != -1) {
int err = errno ? errno : EIO;
LOG(ERROR) << "Error while reading 4096 bytes from device "
<< "offset 0: " << strerror(errno) << " (" << errno
<< ")";
errno = err;
}
}
else if (seek_res != -1) {
int err = errno ? errno : EIO;
LOG(ERROR) << "Error while seeking to device offset 0: "
<< strerror(errno) << " (" << errno << ")";
errno = err;
}
close(fd);
}
return retval;
}
bool IsSupported() {
return access(kMkfsPath, X_OK) == 0 && access(kFsckPath, X_OK) == 0 &&
IsFilesystemSupported("texfat");
}
status_t Check(const std::string& source) {
std::vector<std::string> cmd;
cmd.push_back(kFsckPath);
cmd.push_back("-r");
cmd.push_back(source);
int rc = ForkExecvp(cmd, nullptr, sFsckUntrustedContext);
if (rc == 0) {
LOG(INFO) << "Check OK";
return 0;
} else {
LOG(ERROR) << "Check failed (code " << rc << ")";
/* Ignore return/errno value. We know it's exFAT, so any file system
* check/repair results should be considered a bonus. The driver should
* be able to mount the volume in any case and deal with inconsistencies
* appropriately. */
errno = 0;
return 0;
}
}
status_t Mount(const std::string& source, const std::string& target, bool ro,
bool remount, bool executable, int ownerUid, int ownerGid, int permMask)
{
int rc;
unsigned long flags;
auto mountData = android::base::StringPrintf("utf8,uid=%d,gid=%d,fmask=%o,dmask=%o", ownerUid,
ownerGid, permMask, permMask);
flags = MS_NODEV | MS_NOSUID | MS_DIRSYNC | MS_NOATIME;
flags |= (executable ? 0 : MS_NOEXEC);
flags |= (ro ? MS_RDONLY : 0);
flags |= (remount ? MS_REMOUNT : 0);
rc = mount(source.c_str(), target.c_str(), "texfat", flags, mountData.c_str());
if (rc && errno == EROFS) {
LOG(ERROR) << source << "appears to be a read only filesystem - retrying mount RO";
flags |= MS_RDONLY;
rc = mount(source.c_str(), target.c_str(), "texfat", flags, mountData.c_str());
}
return rc;
}
static status_t DoFormat(const std::string& source, unsigned long numSectors,
bool wholeDevice, bool partitionOnly, bool wipe) {
const char *const fsPath = source.c_str();
if (!wholeDevice && partitionOnly) {
LOG(ERROR) << "Cannot partition non-whole device.";
errno = EIO;
return -1;
}
LOG(INFO) << (partitionOnly ? "Partitioning" : "Formatting")
<< " SDXC " << (partitionOnly ? "card" : "exFAT file system")
<< " at \"" << fsPath << "\" as "
<< (wholeDevice ? "whole device" : "single partition") << "...";
std::vector<std::string> cmd;
cmd.push_back(kMkfsPath);
if (wholeDevice)
cmd.push_back("--sda-whole");
cmd.push_back("--sda-strict");
if (partitionOnly) {
cmd.push_back("--sda-partitioning-only");
}
if (wipe) {
cmd.push_back("--discard");
}
if (numSectors) {
char tmp[32];
snprintf(tmp, sizeof(tmp), "%lu", numSectors);
cmd.push_back("--sector-count");
cmd.push_back(tmp);
}
cmd.push_back(source);
int rc = ForkExecvp(cmd);
if (rc == 0) {
LOG(INFO) << "Format OK";
return 0;
} else {
LOG(ERROR) << "Format failed (code " << rc << ")";
errno = EIO;
return -1;
}
return 0;
}
status_t Format(const std::string& source, unsigned long numSectors) {
return DoFormat(source, numSectors, false, false, false);
}
static int getDeviceSize(const char *const devicePath, uint64_t *const outSize)
{
int err = 0;
int fd = open(devicePath, O_RDONLY | O_CLOEXEC);
if (fd == -1)
err = errno ? errno : EIO;
else {
uint64_t size;
if (ioctl(fd, BLKGETSIZE64, &size) == -1)
err = errno ? errno : EIO;
else
*outSize = size;
close(fd);
}
return err;
}
int CheckSize(const std::string& source, bool isPartition) {
const char *const wholeDevicePath = source.c_str();
const unsigned long long limit_min =
isPartition ? 34342961152ULL : 34359738368ULL;
const unsigned long long limit_max =
isPartition ? 2198956146688ULL : 2199023255552ULL;
int res;
uint64_t size = 0;
int err = getDeviceSize(wholeDevicePath, &size);
if (!err) {
if (size < limit_min || size > limit_max)
res = 0;
else
res = 1;
}
else {
res = -1;
errno = err;
}
return res;
}
status_t PartitionDisk(const std::string& source) {
return DoFormat(source, 0, true, true, false);
}
#ifndef VFAT_IOCTL_GET_VOLUME_ID
#define VFAT_IOCTL_GET_VOLUME_ID _IOR('r', 0x12, __u32)
#endif
extern "C" {
typedef struct _TUXERA_IOCTL_GET_VOLUME_LABEL_ARGS {
__u32 size; /* Size in bytes of volume label buffer. */
__u8 label[]; /* Buffer to return the volume label into of size at least @size
bytes. */
} __attribute__ ((packed)) TUXERA_VOLUME_LABEL;
}
#ifndef TUXERA_GET_VOLUME_LABEL
#define TUXERA_GET_VOLUME_LABEL _IOWR('X', 2, TUXERA_VOLUME_LABEL)
#endif
int ExtractMetadata(const char *mountPoint, char **outVolumeLabel,
char **outVolumeUuid)
{
int err = 0;
__u32 returnedLabelSize;
__u32 volumeLabelSize;
TUXERA_VOLUME_LABEL *volumeLabel = NULL;
__u32 volumeLabelBufferSize = 0;
char *volumeLabelBuffer = NULL;
__u32 volumeUuidBufferSize = 0;
char *volumeUuidBuffer = NULL;
int fd = -1;
int volumeId;
fd = open(mountPoint, O_RDONLY | O_CLOEXEC);
if(fd == -1) {
err = errno ? errno : ENOENT;
LOG(ERROR) << "Error while opening mount point: " << strerror(errno)
<< " (" << errno << ")";
goto out;
}
volumeLabelSize = 4096;
while(1) {
TUXERA_VOLUME_LABEL *newVolumeLabel;
int res;
newVolumeLabel = (TUXERA_VOLUME_LABEL*) realloc(volumeLabel,
sizeof(TUXERA_VOLUME_LABEL) + volumeLabelSize);
if(!newVolumeLabel) {
err = errno ? errno : ENOMEM;
LOG(ERROR) << "Error while reallocating memory (" << volumeLabelSize
<< " bytes) for TUXERA_VOLUME_LABEL: " << strerror(errno)
<< " (" << errno << ")";
goto out;
}
volumeLabel = newVolumeLabel;
memset(volumeLabel, 0, sizeof(TUXERA_VOLUME_LABEL) + volumeLabelSize);
volumeLabel->size = volumeLabelSize;
if((res = ioctl(fd, TUXERA_GET_VOLUME_LABEL, volumeLabel)) < 0) {
err = errno ? errno : EINVAL;
LOG(ERROR) << "Error while issuing IOCTL to get volume label: "
<< strerror(errno) << " (" << errno << ")";
goto out;
}
returnedLabelSize = (__u32) res;
if(volumeLabelSize >= returnedLabelSize + 1) {
break;
}
volumeLabelSize = returnedLabelSize + 1;
}
volumeLabelBufferSize = volumeLabel->size + 1;
volumeLabelBuffer = (char*) malloc(volumeLabelBufferSize);
if(!volumeLabelBuffer) {
err = errno ? errno : ENOMEM;
LOG(ERROR) << "Error while allocating volume label buffer: "
<< strerror(errno) << " (" << errno << ")";
goto out;
}
memcpy(volumeLabelBuffer, volumeLabel->label, volumeLabel->size);
volumeLabelBuffer[volumeLabel->size] = '\0';
volumeUuidBufferSize = 10;
volumeUuidBuffer = (char*) malloc(volumeUuidBufferSize);
if(!volumeUuidBuffer) {
err = errno ? errno : ENOMEM;
LOG(ERROR) << "Error while allocating volume UUID buffer: "
<< strerror(errno) << " (" << errno << ")";
goto out;
}
volumeId = ioctl(fd, VFAT_IOCTL_GET_VOLUME_ID, NULL);
if(volumeId == -1) {
err = errno ? errno : EINVAL;
LOG(ERROR) << "Error while issuing IOCTL to get volume ID: "
<< strerror(errno) << " (" << errno << ")";
goto out;
}
if(snprintf(volumeUuidBuffer, volumeUuidBufferSize, "%04X-%04X",
(volumeId >> 16) & 0xFFFF, volumeId & 0xFFFF) != 9)
{
err = errno ? errno : EINVAL;
LOG(ERROR) << "Error while constructing volume UUID string: "
<< strerror(errno) << " (" << errno << ")";
goto out;
}
out:
if(err) {
if(volumeUuidBuffer) {
free(volumeUuidBuffer);
}
if(volumeLabelBuffer) {
free(volumeLabelBuffer);
}
}
else {
*outVolumeLabel = volumeLabelBuffer;
*outVolumeUuid = volumeUuidBuffer;
}
if(volumeLabel) {
free(volumeLabel);
}
if(fd != -1) {
close(fd);
}
return err;
}
} // namespace exfat
} // namespace vold
} // namespace android