| // |
| // Copyright (C) 2012 The Android Open Source Project |
| // |
| // 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 "update_engine/common/utils.h" |
| |
| #include <stdint.h> |
| |
| #include <dirent.h> |
| #include <elf.h> |
| #include <endian.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/mount.h> |
| #include <sys/resource.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <algorithm> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/callback.h> |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/files/scoped_file.h> |
| #include <base/format_macros.h> |
| #include <base/location.h> |
| #include <base/logging.h> |
| #include <base/posix/eintr_wrapper.h> |
| #include <base/rand_util.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_split.h> |
| #include <base/strings/string_util.h> |
| #include <base/strings/stringprintf.h> |
| #include <brillo/data_encoding.h> |
| |
| #include "update_engine/common/clock_interface.h" |
| #include "update_engine/common/constants.h" |
| #include "update_engine/common/platform_constants.h" |
| #include "update_engine/common/prefs_interface.h" |
| #include "update_engine/common/subprocess.h" |
| #include "update_engine/payload_consumer/file_descriptor.h" |
| #include "update_engine/payload_consumer/payload_constants.h" |
| |
| using base::Time; |
| using base::TimeDelta; |
| using std::min; |
| using std::pair; |
| using std::string; |
| using std::vector; |
| |
| namespace chromeos_update_engine { |
| |
| namespace { |
| |
| // The following constants control how UnmountFilesystem should retry if |
| // umount() fails with an errno EBUSY, i.e. retry 5 times over the course of |
| // one second. |
| const int kUnmountMaxNumOfRetries = 5; |
| const int kUnmountRetryIntervalInMicroseconds = 200 * 1000; // 200 ms |
| |
| // Number of bytes to read from a file to attempt to detect its contents. Used |
| // in GetFileFormat. |
| const int kGetFileFormatMaxHeaderSize = 32; |
| |
| // The path to the kernel's boot_id. |
| const char kBootIdPath[] = "/proc/sys/kernel/random/boot_id"; |
| |
| // A pointer to a null-terminated string containing the root directory where all |
| // the temporary files should be created. If null, the system default is used |
| // instead. |
| const char* root_temp_dir = nullptr; |
| |
| // Return true if |disk_name| is an MTD or a UBI device. Note that this test is |
| // simply based on the name of the device. |
| bool IsMtdDeviceName(const string& disk_name) { |
| return base::StartsWith(disk_name, "/dev/ubi", |
| base::CompareCase::SENSITIVE) || |
| base::StartsWith(disk_name, "/dev/mtd", base::CompareCase::SENSITIVE); |
| } |
| |
| // Return the device name for the corresponding partition on a NAND device. |
| // WARNING: This function returns device names that are not mountable. |
| string MakeNandPartitionName(int partition_num) { |
| switch (partition_num) { |
| case 2: |
| case 4: |
| case 6: { |
| return base::StringPrintf("/dev/mtd%d", partition_num); |
| } |
| default: { |
| return base::StringPrintf("/dev/ubi%d_0", partition_num); |
| } |
| } |
| } |
| |
| // Return the device name for the corresponding partition on a NAND device that |
| // may be mountable (but may not be writable). |
| string MakeNandPartitionNameForMount(int partition_num) { |
| switch (partition_num) { |
| case 2: |
| case 4: |
| case 6: { |
| return base::StringPrintf("/dev/mtd%d", partition_num); |
| } |
| case 3: |
| case 5: |
| case 7: { |
| return base::StringPrintf("/dev/ubiblock%d_0", partition_num); |
| } |
| default: { |
| return base::StringPrintf("/dev/ubi%d_0", partition_num); |
| } |
| } |
| } |
| |
| // If |path| is absolute, or explicit relative to the current working directory, |
| // leaves it as is. Otherwise, uses the system's temp directory, as defined by |
| // base::GetTempDir() and prepends it to |path|. On success stores the full |
| // temporary path in |template_path| and returns true. |
| bool GetTempName(const string& path, base::FilePath* template_path) { |
| if (path[0] == '/' || |
| base::StartsWith(path, "./", base::CompareCase::SENSITIVE) || |
| base::StartsWith(path, "../", base::CompareCase::SENSITIVE)) { |
| *template_path = base::FilePath(path); |
| return true; |
| } |
| |
| base::FilePath temp_dir; |
| if (root_temp_dir) { |
| temp_dir = base::FilePath(root_temp_dir); |
| } else { |
| #ifdef __ANDROID__ |
| temp_dir = base::FilePath(constants::kNonVolatileDirectory).Append("tmp"); |
| #else |
| TEST_AND_RETURN_FALSE(base::GetTempDir(&temp_dir)); |
| #endif // __ANDROID__ |
| } |
| if (!base::PathExists(temp_dir)) |
| TEST_AND_RETURN_FALSE(base::CreateDirectory(temp_dir)); |
| *template_path = temp_dir.Append(path); |
| return true; |
| } |
| |
| } // namespace |
| |
| namespace utils { |
| |
| void SetRootTempDir(const char* new_root_temp_dir) { |
| root_temp_dir = new_root_temp_dir; |
| } |
| |
| string ParseECVersion(string input_line) { |
| base::TrimWhitespaceASCII(input_line, base::TRIM_ALL, &input_line); |
| |
| // At this point we want to convert the format key=value pair from mosys to |
| // a vector of key value pairs. |
| vector<pair<string, string>> kv_pairs; |
| if (base::SplitStringIntoKeyValuePairs(input_line, '=', ' ', &kv_pairs)) { |
| for (const pair<string, string>& kv_pair : kv_pairs) { |
| // Finally match against the fw_verion which may have quotes. |
| if (kv_pair.first == "fw_version") { |
| string output; |
| // Trim any quotes. |
| base::TrimString(kv_pair.second, "\"", &output); |
| return output; |
| } |
| } |
| } |
| LOG(ERROR) << "Unable to parse fwid from ec info."; |
| return ""; |
| } |
| |
| bool WriteFile(const char* path, const void* data, size_t data_len) { |
| int fd = HANDLE_EINTR(open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600)); |
| TEST_AND_RETURN_FALSE_ERRNO(fd >= 0); |
| ScopedFdCloser fd_closer(&fd); |
| return WriteAll(fd, data, data_len); |
| } |
| |
| bool ReadAll( |
| int fd, void* buf, size_t count, size_t* out_bytes_read, bool* eof) { |
| char* c_buf = static_cast<char*>(buf); |
| size_t bytes_read = 0; |
| *eof = false; |
| while (bytes_read < count) { |
| ssize_t rc = HANDLE_EINTR(read(fd, c_buf + bytes_read, count - bytes_read)); |
| if (rc < 0) { |
| // EAGAIN and EWOULDBLOCK are normal return values when there's no more |
| // input and we are in non-blocking mode. |
| if (errno != EWOULDBLOCK && errno != EAGAIN) { |
| PLOG(ERROR) << "Error reading fd " << fd; |
| *out_bytes_read = bytes_read; |
| return false; |
| } |
| break; |
| } else if (rc == 0) { |
| // A value of 0 means that we reached EOF and there is nothing else to |
| // read from this fd. |
| *eof = true; |
| break; |
| } else { |
| bytes_read += rc; |
| } |
| } |
| *out_bytes_read = bytes_read; |
| return true; |
| } |
| |
| bool WriteAll(int fd, const void* buf, size_t count) { |
| const char* c_buf = static_cast<const char*>(buf); |
| ssize_t bytes_written = 0; |
| while (bytes_written < static_cast<ssize_t>(count)) { |
| ssize_t rc = write(fd, c_buf + bytes_written, count - bytes_written); |
| TEST_AND_RETURN_FALSE_ERRNO(rc >= 0); |
| bytes_written += rc; |
| } |
| return true; |
| } |
| |
| bool PWriteAll(int fd, const void* buf, size_t count, off_t offset) { |
| const char* c_buf = static_cast<const char*>(buf); |
| size_t bytes_written = 0; |
| int num_attempts = 0; |
| while (bytes_written < count) { |
| num_attempts++; |
| ssize_t rc = pwrite(fd, c_buf + bytes_written, count - bytes_written, |
| offset + bytes_written); |
| // TODO(garnold) for debugging failure in chromium-os:31077; to be removed. |
| if (rc < 0) { |
| PLOG(ERROR) << "pwrite error; num_attempts=" << num_attempts |
| << " bytes_written=" << bytes_written |
| << " count=" << count << " offset=" << offset; |
| } |
| TEST_AND_RETURN_FALSE_ERRNO(rc >= 0); |
| bytes_written += rc; |
| } |
| return true; |
| } |
| |
| bool WriteAll(const FileDescriptorPtr& fd, const void* buf, size_t count) { |
| const char* c_buf = static_cast<const char*>(buf); |
| ssize_t bytes_written = 0; |
| while (bytes_written < static_cast<ssize_t>(count)) { |
| ssize_t rc = fd->Write(c_buf + bytes_written, count - bytes_written); |
| TEST_AND_RETURN_FALSE_ERRNO(rc >= 0); |
| bytes_written += rc; |
| } |
| return true; |
| } |
| |
| bool PWriteAll(const FileDescriptorPtr& fd, |
| const void* buf, |
| size_t count, |
| off_t offset) { |
| TEST_AND_RETURN_FALSE_ERRNO(fd->Seek(offset, SEEK_SET) != |
| static_cast<off_t>(-1)); |
| return WriteAll(fd, buf, count); |
| } |
| |
| bool PReadAll(int fd, void* buf, size_t count, off_t offset, |
| ssize_t* out_bytes_read) { |
| char* c_buf = static_cast<char*>(buf); |
| ssize_t bytes_read = 0; |
| while (bytes_read < static_cast<ssize_t>(count)) { |
| ssize_t rc = pread(fd, c_buf + bytes_read, count - bytes_read, |
| offset + bytes_read); |
| TEST_AND_RETURN_FALSE_ERRNO(rc >= 0); |
| if (rc == 0) { |
| break; |
| } |
| bytes_read += rc; |
| } |
| *out_bytes_read = bytes_read; |
| return true; |
| } |
| |
| bool PReadAll(const FileDescriptorPtr& fd, void* buf, size_t count, off_t offset, |
| ssize_t* out_bytes_read) { |
| TEST_AND_RETURN_FALSE_ERRNO(fd->Seek(offset, SEEK_SET) != |
| static_cast<off_t>(-1)); |
| char* c_buf = static_cast<char*>(buf); |
| ssize_t bytes_read = 0; |
| while (bytes_read < static_cast<ssize_t>(count)) { |
| ssize_t rc = fd->Read(c_buf + bytes_read, count - bytes_read); |
| TEST_AND_RETURN_FALSE_ERRNO(rc >= 0); |
| if (rc == 0) { |
| break; |
| } |
| bytes_read += rc; |
| } |
| *out_bytes_read = bytes_read; |
| return true; |
| } |
| |
| // Append |nbytes| of content from |buf| to the vector pointed to by either |
| // |vec_p| or |str_p|. |
| static void AppendBytes(const uint8_t* buf, size_t nbytes, |
| brillo::Blob* vec_p) { |
| CHECK(buf); |
| CHECK(vec_p); |
| vec_p->insert(vec_p->end(), buf, buf + nbytes); |
| } |
| static void AppendBytes(const uint8_t* buf, size_t nbytes, |
| string* str_p) { |
| CHECK(buf); |
| CHECK(str_p); |
| str_p->append(buf, buf + nbytes); |
| } |
| |
| // Reads from an open file |fp|, appending the read content to the container |
| // pointer to by |out_p|. Returns true upon successful reading all of the |
| // file's content, false otherwise. If |size| is not -1, reads up to |size| |
| // bytes. |
| template <class T> |
| static bool Read(FILE* fp, off_t size, T* out_p) { |
| CHECK(fp); |
| CHECK(size == -1 || size >= 0); |
| uint8_t buf[1024]; |
| while (size == -1 || size > 0) { |
| off_t bytes_to_read = sizeof(buf); |
| if (size > 0 && bytes_to_read > size) { |
| bytes_to_read = size; |
| } |
| size_t nbytes = fread(buf, 1, bytes_to_read, fp); |
| if (!nbytes) { |
| break; |
| } |
| AppendBytes(buf, nbytes, out_p); |
| if (size != -1) { |
| CHECK(size >= static_cast<off_t>(nbytes)); |
| size -= nbytes; |
| } |
| } |
| if (ferror(fp)) { |
| return false; |
| } |
| return size == 0 || feof(fp); |
| } |
| |
| // Opens a file |path| for reading and appends its the contents to a container |
| // |out_p|. Starts reading the file from |offset|. If |offset| is beyond the end |
| // of the file, returns success. If |size| is not -1, reads up to |size| bytes. |
| template <class T> |
| static bool ReadFileChunkAndAppend(const string& path, |
| off_t offset, |
| off_t size, |
| T* out_p) { |
| CHECK_GE(offset, 0); |
| CHECK(size == -1 || size >= 0); |
| base::ScopedFILE fp(fopen(path.c_str(), "r")); |
| if (!fp.get()) |
| return false; |
| if (offset) { |
| // Return success without appending any data if a chunk beyond the end of |
| // the file is requested. |
| if (offset >= FileSize(path)) { |
| return true; |
| } |
| TEST_AND_RETURN_FALSE_ERRNO(fseek(fp.get(), offset, SEEK_SET) == 0); |
| } |
| return Read(fp.get(), size, out_p); |
| } |
| |
| // TODO(deymo): This is only used in unittest, but requires the private |
| // Read<string>() defined here. Expose Read<string>() or move to base/ version. |
| bool ReadPipe(const string& cmd, string* out_p) { |
| FILE* fp = popen(cmd.c_str(), "r"); |
| if (!fp) |
| return false; |
| bool success = Read(fp, -1, out_p); |
| return (success && pclose(fp) >= 0); |
| } |
| |
| bool ReadFile(const string& path, brillo::Blob* out_p) { |
| return ReadFileChunkAndAppend(path, 0, -1, out_p); |
| } |
| |
| bool ReadFile(const string& path, string* out_p) { |
| return ReadFileChunkAndAppend(path, 0, -1, out_p); |
| } |
| |
| bool ReadFileChunk(const string& path, off_t offset, off_t size, |
| brillo::Blob* out_p) { |
| return ReadFileChunkAndAppend(path, offset, size, out_p); |
| } |
| |
| off_t BlockDevSize(int fd) { |
| uint64_t dev_size; |
| int rc = ioctl(fd, BLKGETSIZE64, &dev_size); |
| if (rc == -1) { |
| dev_size = -1; |
| PLOG(ERROR) << "Error running ioctl(BLKGETSIZE64) on " << fd; |
| } |
| return dev_size; |
| } |
| |
| off_t FileSize(int fd) { |
| struct stat stbuf; |
| int rc = fstat(fd, &stbuf); |
| CHECK_EQ(rc, 0); |
| if (rc < 0) { |
| PLOG(ERROR) << "Error stat-ing " << fd; |
| return rc; |
| } |
| if (S_ISREG(stbuf.st_mode)) |
| return stbuf.st_size; |
| if (S_ISBLK(stbuf.st_mode)) |
| return BlockDevSize(fd); |
| LOG(ERROR) << "Couldn't determine the type of " << fd; |
| return -1; |
| } |
| |
| off_t FileSize(const string& path) { |
| int fd = open(path.c_str(), O_RDONLY | O_CLOEXEC); |
| if (fd == -1) { |
| PLOG(ERROR) << "Error opening " << path; |
| return fd; |
| } |
| off_t size = FileSize(fd); |
| if (size == -1) |
| PLOG(ERROR) << "Error getting file size of " << path; |
| close(fd); |
| return size; |
| } |
| |
| void HexDumpArray(const uint8_t* const arr, const size_t length) { |
| LOG(INFO) << "Logging array of length: " << length; |
| const unsigned int bytes_per_line = 16; |
| for (uint32_t i = 0; i < length; i += bytes_per_line) { |
| const unsigned int bytes_remaining = length - i; |
| const unsigned int bytes_per_this_line = min(bytes_per_line, |
| bytes_remaining); |
| char header[100]; |
| int r = snprintf(header, sizeof(header), "0x%08x : ", i); |
| TEST_AND_RETURN(r == 13); |
| string line = header; |
| for (unsigned int j = 0; j < bytes_per_this_line; j++) { |
| char buf[20]; |
| uint8_t c = arr[i + j]; |
| r = snprintf(buf, sizeof(buf), "%02x ", static_cast<unsigned int>(c)); |
| TEST_AND_RETURN(r == 3); |
| line += buf; |
| } |
| LOG(INFO) << line; |
| } |
| } |
| |
| bool SplitPartitionName(const string& partition_name, |
| string* out_disk_name, |
| int* out_partition_num) { |
| if (!base::StartsWith(partition_name, "/dev/", |
| base::CompareCase::SENSITIVE)) { |
| LOG(ERROR) << "Invalid partition device name: " << partition_name; |
| return false; |
| } |
| |
| size_t last_nondigit_pos = partition_name.find_last_not_of("0123456789"); |
| if (last_nondigit_pos == string::npos || |
| (last_nondigit_pos + 1) == partition_name.size()) { |
| LOG(ERROR) << "Unable to parse partition device name: " << partition_name; |
| return false; |
| } |
| |
| size_t partition_name_len = string::npos; |
| if (partition_name[last_nondigit_pos] == '_') { |
| // NAND block devices have weird naming which could be something |
| // like "/dev/ubiblock2_0". We discard "_0" in such a case. |
| size_t prev_nondigit_pos = |
| partition_name.find_last_not_of("0123456789", last_nondigit_pos - 1); |
| if (prev_nondigit_pos == string::npos || |
| (prev_nondigit_pos + 1) == last_nondigit_pos) { |
| LOG(ERROR) << "Unable to parse partition device name: " << partition_name; |
| return false; |
| } |
| |
| partition_name_len = last_nondigit_pos - prev_nondigit_pos; |
| last_nondigit_pos = prev_nondigit_pos; |
| } |
| |
| if (out_disk_name) { |
| // Special case for MMC devices which have the following naming scheme: |
| // mmcblk0p2 |
| size_t disk_name_len = last_nondigit_pos; |
| if (partition_name[last_nondigit_pos] != 'p' || |
| last_nondigit_pos == 0 || |
| !isdigit(partition_name[last_nondigit_pos - 1])) { |
| disk_name_len++; |
| } |
| *out_disk_name = partition_name.substr(0, disk_name_len); |
| } |
| |
| if (out_partition_num) { |
| string partition_str = partition_name.substr(last_nondigit_pos + 1, |
| partition_name_len); |
| *out_partition_num = atoi(partition_str.c_str()); |
| } |
| return true; |
| } |
| |
| string MakePartitionName(const string& disk_name, int partition_num) { |
| if (partition_num < 1) { |
| LOG(ERROR) << "Invalid partition number: " << partition_num; |
| return string(); |
| } |
| |
| if (!base::StartsWith(disk_name, "/dev/", base::CompareCase::SENSITIVE)) { |
| LOG(ERROR) << "Invalid disk name: " << disk_name; |
| return string(); |
| } |
| |
| if (IsMtdDeviceName(disk_name)) { |
| // Special case for UBI block devices. |
| // 1. ubiblock is not writable, we need to use plain "ubi". |
| // 2. There is a "_0" suffix. |
| return MakeNandPartitionName(partition_num); |
| } |
| |
| string partition_name = disk_name; |
| if (isdigit(partition_name.back())) { |
| // Special case for devices with names ending with a digit. |
| // Add "p" to separate the disk name from partition number, |
| // e.g. "/dev/loop0p2" |
| partition_name += 'p'; |
| } |
| |
| partition_name += std::to_string(partition_num); |
| |
| return partition_name; |
| } |
| |
| string MakePartitionNameForMount(const string& part_name) { |
| if (IsMtdDeviceName(part_name)) { |
| int partition_num; |
| if (!SplitPartitionName(part_name, nullptr, &partition_num)) { |
| return ""; |
| } |
| return MakeNandPartitionNameForMount(partition_num); |
| } |
| return part_name; |
| } |
| |
| string ErrnoNumberAsString(int err) { |
| char buf[100]; |
| buf[0] = '\0'; |
| return strerror_r(err, buf, sizeof(buf)); |
| } |
| |
| bool FileExists(const char* path) { |
| struct stat stbuf; |
| return 0 == lstat(path, &stbuf); |
| } |
| |
| bool IsSymlink(const char* path) { |
| struct stat stbuf; |
| return lstat(path, &stbuf) == 0 && S_ISLNK(stbuf.st_mode) != 0; |
| } |
| |
| bool TryAttachingUbiVolume(int volume_num, int timeout) { |
| const string volume_path = base::StringPrintf("/dev/ubi%d_0", volume_num); |
| if (FileExists(volume_path.c_str())) { |
| return true; |
| } |
| |
| int exit_code; |
| vector<string> cmd = { |
| "ubiattach", |
| "-m", |
| base::StringPrintf("%d", volume_num), |
| "-d", |
| base::StringPrintf("%d", volume_num) |
| }; |
| TEST_AND_RETURN_FALSE(Subprocess::SynchronousExec(cmd, &exit_code, nullptr)); |
| TEST_AND_RETURN_FALSE(exit_code == 0); |
| |
| cmd = { |
| "ubiblock", |
| "--create", |
| volume_path |
| }; |
| TEST_AND_RETURN_FALSE(Subprocess::SynchronousExec(cmd, &exit_code, nullptr)); |
| TEST_AND_RETURN_FALSE(exit_code == 0); |
| |
| while (timeout > 0 && !FileExists(volume_path.c_str())) { |
| sleep(1); |
| timeout--; |
| } |
| |
| return FileExists(volume_path.c_str()); |
| } |
| |
| bool MakeTempFile(const string& base_filename_template, |
| string* filename, |
| int* fd) { |
| base::FilePath filename_template; |
| TEST_AND_RETURN_FALSE( |
| GetTempName(base_filename_template, &filename_template)); |
| DCHECK(filename || fd); |
| vector<char> buf(filename_template.value().size() + 1); |
| memcpy(buf.data(), filename_template.value().data(), |
| filename_template.value().size()); |
| buf[filename_template.value().size()] = '\0'; |
| |
| int mkstemp_fd = mkstemp(buf.data()); |
| TEST_AND_RETURN_FALSE_ERRNO(mkstemp_fd >= 0); |
| if (filename) { |
| *filename = buf.data(); |
| } |
| if (fd) { |
| *fd = mkstemp_fd; |
| } else { |
| close(mkstemp_fd); |
| } |
| return true; |
| } |
| |
| bool SetBlockDeviceReadOnly(const string& device, bool read_only) { |
| int fd = HANDLE_EINTR(open(device.c_str(), O_RDONLY | O_CLOEXEC)); |
| if (fd < 0) { |
| PLOG(ERROR) << "Opening block device " << device; |
| return false; |
| } |
| ScopedFdCloser fd_closer(&fd); |
| // We take no action if not needed. |
| int read_only_flag; |
| int expected_flag = read_only ? 1 : 0; |
| int rc = ioctl(fd, BLKROGET, &read_only_flag); |
| // In case of failure reading the setting we will try to set it anyway. |
| if (rc == 0 && read_only_flag == expected_flag) |
| return true; |
| |
| rc = ioctl(fd, BLKROSET, &expected_flag); |
| if (rc != 0) { |
| PLOG(ERROR) << "Marking block device " << device << " as read_only=" |
| << expected_flag; |
| return false; |
| } |
| return true; |
| } |
| |
| bool MountFilesystem(const string& device, |
| const string& mountpoint, |
| unsigned long mountflags, // NOLINT(runtime/int) |
| const string& type, |
| const string& fs_mount_options) { |
| vector<const char*> fstypes; |
| if (type.empty()) { |
| fstypes = {"ext2", "ext3", "ext4", "squashfs"}; |
| } else { |
| fstypes = {type.c_str()}; |
| } |
| for (const char* fstype : fstypes) { |
| int rc = mount(device.c_str(), mountpoint.c_str(), fstype, mountflags, |
| fs_mount_options.c_str()); |
| if (rc == 0) |
| return true; |
| |
| PLOG(WARNING) << "Unable to mount destination device " << device |
| << " on " << mountpoint << " as " << fstype; |
| } |
| if (!type.empty()) { |
| LOG(ERROR) << "Unable to mount " << device << " with any supported type"; |
| } |
| return false; |
| } |
| |
| bool UnmountFilesystem(const string& mountpoint) { |
| int num_retries = 1; |
| for (;; ++num_retries) { |
| if (umount(mountpoint.c_str()) == 0) |
| return true; |
| if (errno != EBUSY || num_retries >= kUnmountMaxNumOfRetries) |
| break; |
| usleep(kUnmountRetryIntervalInMicroseconds); |
| } |
| if (errno == EINVAL) { |
| LOG(INFO) << "Not a mountpoint: " << mountpoint; |
| return false; |
| } |
| PLOG(WARNING) << "Error unmounting " << mountpoint << " after " << num_retries |
| << " attempts. Lazy unmounting instead, error was"; |
| if (umount2(mountpoint.c_str(), MNT_DETACH) != 0) { |
| PLOG(ERROR) << "Lazy unmount failed"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool IsMountpoint(const std::string& mountpoint) { |
| struct stat stdir, stparent; |
| |
| // Check whether the passed mountpoint is a directory and the /.. is in the |
| // same device or not. If mountpoint/.. is in a different device it means that |
| // there is a filesystem mounted there. If it is not, but they both point to |
| // the same inode it basically is the special case of /.. pointing to /. This |
| // test doesn't play well with bind mount but that's out of the scope of what |
| // we want to detect here. |
| if (lstat(mountpoint.c_str(), &stdir) != 0) { |
| PLOG(ERROR) << "Error stat'ing " << mountpoint; |
| return false; |
| } |
| if (!S_ISDIR(stdir.st_mode)) |
| return false; |
| |
| base::FilePath parent(mountpoint); |
| parent = parent.Append(".."); |
| if (lstat(parent.value().c_str(), &stparent) != 0) { |
| PLOG(ERROR) << "Error stat'ing " << parent.value(); |
| return false; |
| } |
| return S_ISDIR(stparent.st_mode) && |
| (stparent.st_dev != stdir.st_dev || stparent.st_ino == stdir.st_ino); |
| } |
| |
| // Tries to parse the header of an ELF file to obtain a human-readable |
| // description of it on the |output| string. |
| static bool GetFileFormatELF(const uint8_t* buffer, size_t size, |
| string* output) { |
| // 0x00: EI_MAG - ELF magic header, 4 bytes. |
| if (size < SELFMAG || memcmp(buffer, ELFMAG, SELFMAG) != 0) |
| return false; |
| *output = "ELF"; |
| |
| // 0x04: EI_CLASS, 1 byte. |
| if (size < EI_CLASS + 1) |
| return true; |
| switch (buffer[EI_CLASS]) { |
| case ELFCLASS32: |
| *output += " 32-bit"; |
| break; |
| case ELFCLASS64: |
| *output += " 64-bit"; |
| break; |
| default: |
| *output += " ?-bit"; |
| } |
| |
| // 0x05: EI_DATA, endianness, 1 byte. |
| if (size < EI_DATA + 1) |
| return true; |
| uint8_t ei_data = buffer[EI_DATA]; |
| switch (ei_data) { |
| case ELFDATA2LSB: |
| *output += " little-endian"; |
| break; |
| case ELFDATA2MSB: |
| *output += " big-endian"; |
| break; |
| default: |
| *output += " ?-endian"; |
| // Don't parse anything after the 0x10 offset if endianness is unknown. |
| return true; |
| } |
| |
| const Elf32_Ehdr* hdr = reinterpret_cast<const Elf32_Ehdr*>(buffer); |
| // 0x12: e_machine, 2 byte endianness based on ei_data. The position (0x12) |
| // and size is the same for both 32 and 64 bits. |
| if (size < offsetof(Elf32_Ehdr, e_machine) + sizeof(hdr->e_machine)) |
| return true; |
| uint16_t e_machine; |
| // Fix endianess regardless of the host endianess. |
| if (ei_data == ELFDATA2LSB) |
| e_machine = le16toh(hdr->e_machine); |
| else |
| e_machine = be16toh(hdr->e_machine); |
| |
| switch (e_machine) { |
| case EM_386: |
| *output += " x86"; |
| break; |
| case EM_MIPS: |
| *output += " mips"; |
| break; |
| case EM_ARM: |
| *output += " arm"; |
| break; |
| case EM_X86_64: |
| *output += " x86-64"; |
| break; |
| default: |
| *output += " unknown-arch"; |
| } |
| return true; |
| } |
| |
| string GetFileFormat(const string& path) { |
| brillo::Blob buffer; |
| if (!ReadFileChunkAndAppend(path, 0, kGetFileFormatMaxHeaderSize, &buffer)) |
| return "File not found."; |
| |
| string result; |
| if (GetFileFormatELF(buffer.data(), buffer.size(), &result)) |
| return result; |
| |
| return "data"; |
| } |
| |
| int FuzzInt(int value, unsigned int range) { |
| int min = value - range / 2; |
| int max = value + range - range / 2; |
| return base::RandInt(min, max); |
| } |
| |
| string FormatSecs(unsigned secs) { |
| return FormatTimeDelta(TimeDelta::FromSeconds(secs)); |
| } |
| |
| string FormatTimeDelta(TimeDelta delta) { |
| string str; |
| |
| // Handle negative durations by prefixing with a minus. |
| if (delta.ToInternalValue() < 0) { |
| delta *= -1; |
| str = "-"; |
| } |
| |
| // Canonicalize into days, hours, minutes, seconds and microseconds. |
| unsigned days = delta.InDays(); |
| delta -= TimeDelta::FromDays(days); |
| unsigned hours = delta.InHours(); |
| delta -= TimeDelta::FromHours(hours); |
| unsigned mins = delta.InMinutes(); |
| delta -= TimeDelta::FromMinutes(mins); |
| unsigned secs = delta.InSeconds(); |
| delta -= TimeDelta::FromSeconds(secs); |
| unsigned usecs = delta.InMicroseconds(); |
| |
| if (days) |
| base::StringAppendF(&str, "%ud", days); |
| if (days || hours) |
| base::StringAppendF(&str, "%uh", hours); |
| if (days || hours || mins) |
| base::StringAppendF(&str, "%um", mins); |
| base::StringAppendF(&str, "%u", secs); |
| if (usecs) { |
| int width = 6; |
| while ((usecs / 10) * 10 == usecs) { |
| usecs /= 10; |
| width--; |
| } |
| base::StringAppendF(&str, ".%0*u", width, usecs); |
| } |
| base::StringAppendF(&str, "s"); |
| return str; |
| } |
| |
| string ToString(const Time utc_time) { |
| Time::Exploded exp_time; |
| utc_time.UTCExplode(&exp_time); |
| return base::StringPrintf("%d/%d/%d %d:%02d:%02d GMT", |
| exp_time.month, |
| exp_time.day_of_month, |
| exp_time.year, |
| exp_time.hour, |
| exp_time.minute, |
| exp_time.second); |
| } |
| |
| string ToString(bool b) { |
| return (b ? "true" : "false"); |
| } |
| |
| string ToString(DownloadSource source) { |
| switch (source) { |
| case kDownloadSourceHttpsServer: return "HttpsServer"; |
| case kDownloadSourceHttpServer: return "HttpServer"; |
| case kDownloadSourceHttpPeer: return "HttpPeer"; |
| case kNumDownloadSources: return "Unknown"; |
| // Don't add a default case to let the compiler warn about newly added |
| // download sources which should be added here. |
| } |
| |
| return "Unknown"; |
| } |
| |
| string ToString(PayloadType payload_type) { |
| switch (payload_type) { |
| case kPayloadTypeDelta: return "Delta"; |
| case kPayloadTypeFull: return "Full"; |
| case kPayloadTypeForcedFull: return "ForcedFull"; |
| case kNumPayloadTypes: return "Unknown"; |
| // Don't add a default case to let the compiler warn about newly added |
| // payload types which should be added here. |
| } |
| |
| return "Unknown"; |
| } |
| |
| ErrorCode GetBaseErrorCode(ErrorCode code) { |
| // Ignore the higher order bits in the code by applying the mask as |
| // we want the enumerations to be in the small contiguous range |
| // with values less than ErrorCode::kUmaReportedMax. |
| ErrorCode base_code = static_cast<ErrorCode>( |
| static_cast<int>(code) & ~static_cast<int>(ErrorCode::kSpecialFlags)); |
| |
| // Make additional adjustments required for UMA and error classification. |
| // TODO(jaysri): Move this logic to UeErrorCode.cc when we fix |
| // chromium-os:34369. |
| if (base_code >= ErrorCode::kOmahaRequestHTTPResponseBase) { |
| // Since we want to keep the enums to a small value, aggregate all HTTP |
| // errors into this one bucket for UMA and error classification purposes. |
| LOG(INFO) << "Converting error code " << base_code |
| << " to ErrorCode::kOmahaErrorInHTTPResponse"; |
| base_code = ErrorCode::kOmahaErrorInHTTPResponse; |
| } |
| |
| return base_code; |
| } |
| |
| Time TimeFromStructTimespec(struct timespec *ts) { |
| int64_t us = static_cast<int64_t>(ts->tv_sec) * Time::kMicrosecondsPerSecond + |
| static_cast<int64_t>(ts->tv_nsec) / Time::kNanosecondsPerMicrosecond; |
| return Time::UnixEpoch() + TimeDelta::FromMicroseconds(us); |
| } |
| |
| string StringVectorToString(const vector<string> &vec_str) { |
| string str = "["; |
| for (vector<string>::const_iterator i = vec_str.begin(); |
| i != vec_str.end(); ++i) { |
| if (i != vec_str.begin()) |
| str += ", "; |
| str += '"'; |
| str += *i; |
| str += '"'; |
| } |
| str += "]"; |
| return str; |
| } |
| |
| // The P2P file id should be the same for devices running new version and old |
| // version so that they can share it with each other. The hash in the response |
| // was base64 encoded, but now that we switched to use "hash_sha256" field which |
| // is hex encoded, we have to convert them back to base64 for P2P. However, the |
| // base64 encoded hash was base64 encoded here again historically for some |
| // reason, so we keep the same behavior here. |
| string CalculateP2PFileId(const brillo::Blob& payload_hash, |
| size_t payload_size) { |
| string encoded_hash = brillo::data_encoding::Base64Encode( |
| brillo::data_encoding::Base64Encode(payload_hash)); |
| return base::StringPrintf("cros_update_size_%" PRIuS "_hash_%s", |
| payload_size, |
| encoded_hash.c_str()); |
| } |
| |
| bool DecodeAndStoreBase64String(const string& base64_encoded, |
| base::FilePath *out_path) { |
| brillo::Blob contents; |
| |
| out_path->clear(); |
| |
| if (base64_encoded.size() == 0) { |
| LOG(ERROR) << "Can't decode empty string."; |
| return false; |
| } |
| |
| if (!brillo::data_encoding::Base64Decode(base64_encoded, &contents) || |
| contents.size() == 0) { |
| LOG(ERROR) << "Error decoding base64."; |
| return false; |
| } |
| |
| FILE *file = base::CreateAndOpenTemporaryFile(out_path); |
| if (file == nullptr) { |
| LOG(ERROR) << "Error creating temporary file."; |
| return false; |
| } |
| |
| if (fwrite(contents.data(), 1, contents.size(), file) != contents.size()) { |
| PLOG(ERROR) << "Error writing to temporary file."; |
| if (fclose(file) != 0) |
| PLOG(ERROR) << "Error closing temporary file."; |
| if (unlink(out_path->value().c_str()) != 0) |
| PLOG(ERROR) << "Error unlinking temporary file."; |
| out_path->clear(); |
| return false; |
| } |
| |
| if (fclose(file) != 0) { |
| PLOG(ERROR) << "Error closing temporary file."; |
| out_path->clear(); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool ConvertToOmahaInstallDate(Time time, int *out_num_days) { |
| time_t unix_time = time.ToTimeT(); |
| // Output of: date +"%s" --date="Jan 1, 2007 0:00 PST". |
| const time_t kOmahaEpoch = 1167638400; |
| const int64_t kNumSecondsPerWeek = 7*24*3600; |
| const int64_t kNumDaysPerWeek = 7; |
| |
| time_t omaha_time = unix_time - kOmahaEpoch; |
| |
| if (omaha_time < 0) |
| return false; |
| |
| // Note, as per the comment in utils.h we are deliberately not |
| // handling DST correctly. |
| |
| int64_t num_weeks_since_omaha_epoch = omaha_time / kNumSecondsPerWeek; |
| *out_num_days = num_weeks_since_omaha_epoch * kNumDaysPerWeek; |
| |
| return true; |
| } |
| |
| bool GetMinorVersion(const brillo::KeyValueStore& store, |
| uint32_t* minor_version) { |
| string result; |
| if (store.GetString("PAYLOAD_MINOR_VERSION", &result)) { |
| if (!base::StringToUint(result, minor_version)) { |
| LOG(ERROR) << "StringToUint failed when parsing delta minor version."; |
| return false; |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| bool IsZlibCompatible(const string& fingerprint) { |
| if (fingerprint.size() != sizeof(kCompatibleZlibFingerprint[0]) - 1) { |
| LOG(ERROR) << "Invalid fingerprint: " << fingerprint; |
| return false; |
| } |
| for (auto& f : kCompatibleZlibFingerprint) { |
| if (base::CompareCaseInsensitiveASCII(fingerprint, f) == 0) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool ReadExtents(const string& path, const vector<Extent>& extents, |
| brillo::Blob* out_data, ssize_t out_data_size, |
| size_t block_size) { |
| brillo::Blob data(out_data_size); |
| ssize_t bytes_read = 0; |
| int fd = open(path.c_str(), O_RDONLY); |
| TEST_AND_RETURN_FALSE_ERRNO(fd >= 0); |
| ScopedFdCloser fd_closer(&fd); |
| |
| for (const Extent& extent : extents) { |
| ssize_t bytes_read_this_iteration = 0; |
| ssize_t bytes = extent.num_blocks() * block_size; |
| TEST_AND_RETURN_FALSE(bytes_read + bytes <= out_data_size); |
| TEST_AND_RETURN_FALSE(utils::PReadAll(fd, |
| &data[bytes_read], |
| bytes, |
| extent.start_block() * block_size, |
| &bytes_read_this_iteration)); |
| TEST_AND_RETURN_FALSE(bytes_read_this_iteration == bytes); |
| bytes_read += bytes_read_this_iteration; |
| } |
| TEST_AND_RETURN_FALSE(out_data_size == bytes_read); |
| *out_data = data; |
| return true; |
| } |
| |
| bool GetBootId(string* boot_id) { |
| TEST_AND_RETURN_FALSE( |
| base::ReadFileToString(base::FilePath(kBootIdPath), boot_id)); |
| base::TrimWhitespaceASCII(*boot_id, base::TRIM_TRAILING, boot_id); |
| return true; |
| } |
| |
| } // namespace utils |
| |
| } // namespace chromeos_update_engine |