| // |
| // 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/sendfile.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #include <algorithm> |
| #include <utility> |
| #include <vector> |
| |
| #include <android-base/strings.h> |
| #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/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" |
| |
| using base::Time; |
| using base::TimeDelta; |
| using std::min; |
| using std::numeric_limits; |
| 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"; |
| |
| // 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; |
| #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 { |
| |
| 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(FileDescriptor* 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 WriteAll(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 ReadAll(FileDescriptor* 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; |
| } |
| |
| bool PReadAll(FileDescriptor* fd, |
| void* buf, |
| size_t count, |
| off_t offset, |
| ssize_t* out_bytes_read) { |
| auto old_off = fd->Seek(0, SEEK_CUR); |
| TEST_AND_RETURN_FALSE_ERRNO(old_off >= 0); |
| |
| auto success = ReadAll(fd, buf, count, offset, out_bytes_read); |
| TEST_AND_RETURN_FALSE_ERRNO(fd->Seek(old_off, SEEK_SET) == old_off); |
| return success; |
| } |
| |
| bool PWriteAll(const FileDescriptorPtr& fd, |
| const void* buf, |
| size_t count, |
| off_t offset) { |
| auto old_off = fd->Seek(0, SEEK_CUR); |
| TEST_AND_RETURN_FALSE_ERRNO(old_off >= 0); |
| |
| auto success = WriteAll(fd, buf, count, offset); |
| TEST_AND_RETURN_FALSE_ERRNO(fd->Seek(old_off, SEEK_SET) == old_off); |
| return success; |
| } |
| |
| // 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; |
| } |
| |
| bool SendFile(int out_fd, int in_fd, size_t count) { |
| off64_t offset = lseek(in_fd, 0, SEEK_CUR); |
| TEST_AND_RETURN_FALSE_ERRNO(offset >= 0); |
| constexpr size_t BUFFER_SIZE = 4096; |
| while (count > 0) { |
| const auto bytes_written = |
| sendfile(out_fd, in_fd, &offset, std::min(count, BUFFER_SIZE)); |
| TEST_AND_RETURN_FALSE_ERRNO(bytes_written > 0); |
| count -= bytes_written; |
| } |
| return true; |
| } |
| |
| 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; |
| } |
| |
| 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); |
| *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(); |
| } |
| |
| 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; |
| } |
| |
| 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 IsRegFile(const char* path) { |
| struct stat stbuf; |
| return lstat(path, &stbuf) == 0 && S_ISREG(stbuf.st_mode) != 0; |
| } |
| |
| 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", "erofs"}; |
| } 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 endianness regardless of the host endianness. |
| 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; |
| } |
| |
| 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 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 ReadExtents(const std::string& path, |
| const google::protobuf::RepeatedPtrField<Extent>& extents, |
| brillo::Blob* out_data, |
| size_t block_size) { |
| return ReadExtents(path, |
| {extents.begin(), extents.end()}, |
| out_data, |
| utils::BlocksInExtents(extents) * block_size, |
| block_size); |
| } |
| |
| bool WriteExtents(const std::string& path, |
| const google::protobuf::RepeatedPtrField<Extent>& extents, |
| const brillo::Blob& data, |
| size_t block_size) { |
| EintrSafeFileDescriptor fd; |
| TEST_AND_RETURN_FALSE(fd.Open(path.c_str(), O_RDWR)); |
| size_t bytes_written = 0; |
| for (const auto& ext : extents) { |
| TEST_AND_RETURN_FALSE_ERRNO( |
| fd.Seek(ext.start_block() * block_size, SEEK_SET)); |
| TEST_AND_RETURN_FALSE_ERRNO( |
| fd.Write(data.data() + bytes_written, ext.num_blocks() * block_size)); |
| bytes_written += ext.num_blocks() * block_size; |
| } |
| return true; |
| } |
| bool ReadExtents(const std::string& path, |
| const vector<Extent>& extents, |
| brillo::Blob* out_data, |
| ssize_t out_data_size, |
| size_t block_size) { |
| FileDescriptorPtr fd = std::make_shared<EintrSafeFileDescriptor>(); |
| fd->Open(path.c_str(), O_RDONLY); |
| return ReadExtents(fd, extents, out_data, out_data_size, block_size); |
| } |
| |
| bool ReadExtents(FileDescriptorPtr fd, |
| const google::protobuf::RepeatedPtrField<Extent>& extents, |
| brillo::Blob* out_data, |
| size_t block_size) { |
| return ReadExtents(fd, |
| {extents.begin(), extents.end()}, |
| out_data, |
| utils::BlocksInExtents(extents) * block_size, |
| block_size); |
| } |
| |
| bool ReadExtents(FileDescriptorPtr fd, |
| 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; |
| |
| for (const Extent& extent : extents) { |
| ssize_t bytes_read_this_iteration = 0; |
| ssize_t bytes = extent.num_blocks() * block_size; |
| TEST_LE(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 GetVpdValue(string key, string* result) { |
| int exit_code = 0; |
| string value, error; |
| vector<string> cmd = {"vpd_get_value", key}; |
| if (!chromeos_update_engine::Subprocess::SynchronousExec( |
| cmd, &exit_code, &value, &error) || |
| exit_code) { |
| LOG(ERROR) << "Failed to get vpd key for " << value |
| << " with exit code: " << exit_code << " and error: " << error; |
| return false; |
| } else if (!error.empty()) { |
| LOG(INFO) << "vpd_get_value succeeded but with following errors: " << error; |
| } |
| |
| base::TrimWhitespaceASCII(value, base::TRIM_ALL, &value); |
| *result = value; |
| 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; |
| } |
| |
| int VersionPrefix(const std::string& version) { |
| if (version.empty()) { |
| return 0; |
| } |
| vector<string> tokens = base::SplitString( |
| version, ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); |
| int value; |
| if (tokens.empty() || !base::StringToInt(tokens[0], &value)) |
| return -1; // Target version is invalid. |
| return value; |
| } |
| |
| void ParseRollbackKeyVersion(const string& raw_version, |
| uint16_t* high_version, |
| uint16_t* low_version) { |
| DCHECK(high_version); |
| DCHECK(low_version); |
| *high_version = numeric_limits<uint16_t>::max(); |
| *low_version = numeric_limits<uint16_t>::max(); |
| |
| vector<string> parts = base::SplitString( |
| raw_version, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
| if (parts.size() != 2) { |
| // The version string must have exactly one period. |
| return; |
| } |
| |
| int high; |
| int low; |
| if (!(base::StringToInt(parts[0], &high) && |
| base::StringToInt(parts[1], &low))) { |
| // Both parts of the version could not be parsed correctly. |
| return; |
| } |
| |
| if (high >= 0 && high < numeric_limits<uint16_t>::max() && low >= 0 && |
| low < numeric_limits<uint16_t>::max()) { |
| *high_version = static_cast<uint16_t>(high); |
| *low_version = static_cast<uint16_t>(low); |
| } |
| } |
| |
| string GetFilePath(int fd) { |
| base::FilePath proc("/proc/self/fd/" + std::to_string(fd)); |
| base::FilePath file_name; |
| |
| if (!base::ReadSymbolicLink(proc, &file_name)) { |
| return "not found"; |
| } |
| return file_name.value(); |
| } |
| |
| string GetTimeAsString(time_t utime) { |
| struct tm tm; |
| CHECK_EQ(localtime_r(&utime, &tm), &tm); |
| char str[16]; |
| CHECK_EQ(strftime(str, sizeof(str), "%Y%m%d-%H%M%S", &tm), 15u); |
| return str; |
| } |
| |
| string GetExclusionName(const string& str_to_convert) { |
| return base::NumberToString(base::StringPieceHash()(str_to_convert)); |
| } |
| |
| static bool ParseTimestamp(std::string_view str, int64_t* out) { |
| if (!base::StringToInt64(base::StringPiece(str.data(), str.size()), out)) { |
| LOG(WARNING) << "Invalid timestamp: " << str; |
| return false; |
| } |
| return true; |
| } |
| |
| ErrorCode IsTimestampNewer(const std::string_view old_version, |
| const std::string_view new_version) { |
| if (old_version.empty() || new_version.empty()) { |
| LOG(WARNING) |
| << "One of old/new timestamp is empty, permit update anyway. Old: " |
| << old_version << " New: " << new_version; |
| return ErrorCode::kSuccess; |
| } |
| int64_t old_ver = 0; |
| if (!ParseTimestamp(old_version, &old_ver)) { |
| return ErrorCode::kError; |
| } |
| int64_t new_ver = 0; |
| if (!ParseTimestamp(new_version, &new_ver)) { |
| return ErrorCode::kDownloadManifestParseError; |
| } |
| if (old_ver > new_ver) { |
| return ErrorCode::kPayloadTimestampError; |
| } |
| return ErrorCode::kSuccess; |
| } |
| |
| std::unique_ptr<android::base::MappedFile> GetReadonlyZeroBlock(size_t size) { |
| android::base::unique_fd fd{HANDLE_EINTR(open("/dev/zero", O_RDONLY))}; |
| return android::base::MappedFile::FromFd(fd, 0, size, PROT_READ); |
| } |
| |
| std::string_view GetReadonlyZeroString(size_t size) { |
| // Reserve 512MB of Virtual Address Space. No actual memory will be used. |
| static auto zero_block = GetReadonlyZeroBlock(1024 * 1024 * 512); |
| if (size > zero_block->size()) { |
| auto larger_block = GetReadonlyZeroBlock(size); |
| zero_block = std::move(larger_block); |
| } |
| return {zero_block->data(), size}; |
| } |
| |
| } // namespace utils |
| |
| std::string HexEncode(const brillo::Blob& blob) noexcept { |
| return base::HexEncode(blob.data(), blob.size()); |
| } |
| |
| std::string HexEncode(const std::string_view blob) noexcept { |
| return base::HexEncode(blob.data(), blob.size()); |
| } |
| |
| [[nodiscard]] std::string_view ToStringView( |
| const std::vector<unsigned char>& blob) noexcept { |
| return std::string_view{reinterpret_cast<const char*>(blob.data()), |
| blob.size()}; |
| } |
| |
| [[nodiscard]] std::string_view ToStringView(const void* data, |
| size_t size) noexcept { |
| return std::string_view(reinterpret_cast<const char*>(data), size); |
| } |
| |
| } // namespace chromeos_update_engine |