| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * Copyright (C) 2010-2022 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 = ForkExecvpTimeout(cmd, kUntrustedFsckSleepTime, 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 |