mtd_file_descriptor.cc revision 39910dcd1d68987ccee7c3031dc269233a8490bb
1//
2// Copyright (C) 2014 The Android Open Source Project
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//      http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15//
16
17#include "update_engine/payload_consumer/mtd_file_descriptor.h"
18
19#include <fcntl.h>
20#include <mtd/ubi-user.h>
21#include <string>
22#include <sys/ioctl.h>
23#include <sys/stat.h>
24#include <sys/types.h>
25#include <vector>
26
27#include <base/files/file_path.h>
28#include <base/strings/string_number_conversions.h>
29#include <base/strings/string_util.h>
30#include <base/strings/stringprintf.h>
31#include <update_engine/subprocess.h>
32
33#include "update_engine/common/utils.h"
34
35using std::string;
36using std::vector;
37
38namespace {
39
40static const char kSysfsClassUbi[] = "/sys/class/ubi/";
41static const char kUsableEbSize[] = "/usable_eb_size";
42static const char kReservedEbs[] = "/reserved_ebs";
43
44using chromeos_update_engine::UbiVolumeInfo;
45using chromeos_update_engine::utils::ReadFile;
46
47// Return a UbiVolumeInfo pointer if |path| is a UBI volume. Otherwise, return
48// a null unique pointer.
49std::unique_ptr<UbiVolumeInfo> GetUbiVolumeInfo(const string& path) {
50  base::FilePath device_node(path);
51  base::FilePath ubi_name(device_node.BaseName());
52
53  string sysfs_node(kSysfsClassUbi);
54  sysfs_node.append(ubi_name.MaybeAsASCII());
55
56  std::unique_ptr<UbiVolumeInfo> ret;
57
58  // Obtain volume info from sysfs.
59  string s_reserved_ebs;
60  if (!ReadFile(sysfs_node + kReservedEbs, &s_reserved_ebs)) {
61    LOG(ERROR) << "Cannot read " << sysfs_node + kReservedEbs;
62    return ret;
63  }
64  string s_eb_size;
65  if (!ReadFile(sysfs_node + kUsableEbSize, &s_eb_size)) {
66    LOG(ERROR) << "Cannot read " << sysfs_node + kUsableEbSize;
67    return ret;
68  }
69
70  base::TrimWhitespaceASCII(s_reserved_ebs,
71                            base::TRIM_TRAILING,
72                            &s_reserved_ebs);
73  base::TrimWhitespaceASCII(s_eb_size, base::TRIM_TRAILING, &s_eb_size);
74
75  uint64_t reserved_ebs, eb_size;
76  if (!base::StringToUint64(s_reserved_ebs, &reserved_ebs)) {
77    LOG(ERROR) << "Cannot parse reserved_ebs: " << s_reserved_ebs;
78    return ret;
79  }
80  if (!base::StringToUint64(s_eb_size, &eb_size)) {
81    LOG(ERROR) << "Cannot parse usable_eb_size: " << s_eb_size;
82    return ret;
83  }
84
85  ret.reset(new UbiVolumeInfo);
86  ret->reserved_ebs = reserved_ebs;
87  ret->eraseblock_size = eb_size;
88  return ret;
89}
90
91}  // namespace
92
93namespace chromeos_update_engine {
94
95MtdFileDescriptor::MtdFileDescriptor()
96    : read_ctx_(nullptr, &mtd_read_close),
97      write_ctx_(nullptr, &mtd_write_close) {}
98
99bool MtdFileDescriptor::IsMtd(const char* path) {
100  uint64_t size;
101  return mtd_node_info(path, &size, nullptr, nullptr) == 0;
102}
103
104bool MtdFileDescriptor::Open(const char* path, int flags, mode_t mode) {
105  // This File Descriptor does not support read and write.
106  TEST_AND_RETURN_FALSE((flags & O_ACCMODE) != O_RDWR);
107  // But we need to open the underlying file descriptor in O_RDWR mode because
108  // during write, we need to read back to verify the write actually sticks or
109  // we have to skip the block. That job is done by mtdutils library.
110  if ((flags & O_ACCMODE) == O_WRONLY) {
111    flags &= ~O_ACCMODE;
112    flags |= O_RDWR;
113  }
114  TEST_AND_RETURN_FALSE(
115      EintrSafeFileDescriptor::Open(path, flags | O_CLOEXEC, mode));
116
117  if ((flags & O_ACCMODE) == O_RDWR) {
118    write_ctx_.reset(mtd_write_descriptor(fd_, path));
119    nr_written_ = 0;
120  } else {
121    read_ctx_.reset(mtd_read_descriptor(fd_, path));
122  }
123
124  if (!read_ctx_ && !write_ctx_) {
125    Close();
126    return false;
127  }
128
129  return true;
130}
131
132bool MtdFileDescriptor::Open(const char* path, int flags) {
133  mode_t cur = umask(022);
134  umask(cur);
135  return Open(path, flags, 0777 & ~cur);
136}
137
138ssize_t MtdFileDescriptor::Read(void* buf, size_t count) {
139  CHECK(read_ctx_);
140  return mtd_read_data(read_ctx_.get(), static_cast<char*>(buf), count);
141}
142
143ssize_t MtdFileDescriptor::Write(const void* buf, size_t count) {
144  CHECK(write_ctx_);
145  ssize_t result = mtd_write_data(write_ctx_.get(),
146                                  static_cast<const char*>(buf),
147                                  count);
148  if (result > 0) {
149    nr_written_ += result;
150  }
151  return result;
152}
153
154off64_t MtdFileDescriptor::Seek(off64_t offset, int whence) {
155  if (write_ctx_) {
156    // Ignore seek in write mode.
157    return nr_written_;
158  }
159  return EintrSafeFileDescriptor::Seek(offset, whence);
160}
161
162bool MtdFileDescriptor::Close() {
163  read_ctx_.reset();
164  write_ctx_.reset();
165  return EintrSafeFileDescriptor::Close();
166}
167
168bool UbiFileDescriptor::IsUbi(const char* path) {
169  base::FilePath device_node(path);
170  base::FilePath ubi_name(device_node.BaseName());
171  TEST_AND_RETURN_FALSE(
172      base::StartsWithASCII(ubi_name.MaybeAsASCII(), "ubi", true));
173
174  return static_cast<bool>(GetUbiVolumeInfo(path));
175}
176
177bool UbiFileDescriptor::Open(const char* path, int flags, mode_t mode) {
178  std::unique_ptr<UbiVolumeInfo> info = GetUbiVolumeInfo(path);
179  if (!info) {
180    return false;
181  }
182
183  // This File Descriptor does not support read and write.
184  TEST_AND_RETURN_FALSE((flags & O_ACCMODE) != O_RDWR);
185  TEST_AND_RETURN_FALSE(
186      EintrSafeFileDescriptor::Open(path, flags | O_CLOEXEC, mode));
187
188  usable_eb_blocks_ = info->reserved_ebs;
189  eraseblock_size_ = info->eraseblock_size;
190  volume_size_ = usable_eb_blocks_ * eraseblock_size_;
191
192  if ((flags & O_ACCMODE) == O_WRONLY) {
193    // It's best to use volume update ioctl so that UBI layer will mark the
194    // volume as being updated, and only clear that mark if the update is
195    // successful. We will need to pad to the whole volume size at close.
196    uint64_t vsize = volume_size_;
197    if (ioctl(fd_, UBI_IOCVOLUP, &vsize) != 0) {
198      PLOG(ERROR) << "Cannot issue volume update ioctl";
199      EintrSafeFileDescriptor::Close();
200      return false;
201    }
202    mode_ = kWriteOnly;
203    nr_written_ = 0;
204  } else {
205    mode_ = kReadOnly;
206  }
207
208  return true;
209}
210
211bool UbiFileDescriptor::Open(const char* path, int flags) {
212  mode_t cur = umask(022);
213  umask(cur);
214  return Open(path, flags, 0777 & ~cur);
215}
216
217ssize_t UbiFileDescriptor::Read(void* buf, size_t count) {
218  CHECK(mode_ == kReadOnly);
219  return EintrSafeFileDescriptor::Read(buf, count);
220}
221
222ssize_t UbiFileDescriptor::Write(const void* buf, size_t count) {
223  CHECK(mode_ == kWriteOnly);
224  ssize_t nr_chunk = EintrSafeFileDescriptor::Write(buf, count);
225  if (nr_chunk >= 0) {
226    nr_written_ += nr_chunk;
227  }
228  return nr_chunk;
229}
230
231off64_t UbiFileDescriptor::Seek(off64_t offset, int whence) {
232  if (mode_ == kWriteOnly) {
233    // Ignore seek in write mode.
234    return nr_written_;
235  }
236  return EintrSafeFileDescriptor::Seek(offset, whence);
237}
238
239bool UbiFileDescriptor::Close() {
240  bool pad_ok = true;
241  if (IsOpen() && mode_ == kWriteOnly) {
242    char buf[1024];
243    memset(buf, 0xFF, sizeof(buf));
244    while (nr_written_ < volume_size_) {
245      // We have written less than the whole volume. In order for us to clear
246      // the update marker, we need to fill the rest. It is recommended to fill
247      // UBI writes with 0xFF.
248      uint64_t to_write = volume_size_ - nr_written_;
249      if (to_write > sizeof(buf)) {
250        to_write = sizeof(buf);
251      }
252      ssize_t nr_chunk = EintrSafeFileDescriptor::Write(buf, to_write);
253      if (nr_chunk < 0) {
254        LOG(ERROR) << "Cannot 0xFF-pad before closing.";
255        // There is an error, but we can't really do any meaningful thing here.
256        pad_ok = false;
257        break;
258      }
259      nr_written_ += nr_chunk;
260    }
261  }
262  return EintrSafeFileDescriptor::Close() && pad_ok;
263}
264
265}  // namespace chromeos_update_engine
266