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 
32 #include "update_engine/common/subprocess.h"
33 #include "update_engine/common/utils.h"
34 
35 using std::string;
36 using std::vector;
37 
38 namespace {
39 
40 static const char kSysfsClassUbi[] = "/sys/class/ubi/";
41 static const char kUsableEbSize[] = "/usable_eb_size";
42 static const char kReservedEbs[] = "/reserved_ebs";
43 
44 using chromeos_update_engine::UbiVolumeInfo;
45 using chromeos_update_engine::utils::ReadFile;
46 
47 // Return a UbiVolumeInfo pointer if |path| is a UBI volume. Otherwise, return
48 // a null unique pointer.
GetUbiVolumeInfo(const string & path)49 std::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 
93 namespace chromeos_update_engine {
94 
MtdFileDescriptor()95 MtdFileDescriptor::MtdFileDescriptor()
96     : read_ctx_(nullptr, &mtd_read_close),
97       write_ctx_(nullptr, &mtd_write_close) {}
98 
IsMtd(const char * path)99 bool MtdFileDescriptor::IsMtd(const char* path) {
100   uint64_t size;
101   return mtd_node_info(path, &size, nullptr, nullptr) == 0;
102 }
103 
Open(const char * path,int flags,mode_t mode)104 bool 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 
Open(const char * path,int flags)132 bool 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 
Read(void * buf,size_t count)138 ssize_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 
Write(const void * buf,size_t count)143 ssize_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 
Seek(off64_t offset,int whence)154 off64_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 
Close()162 bool MtdFileDescriptor::Close() {
163   read_ctx_.reset();
164   write_ctx_.reset();
165   return EintrSafeFileDescriptor::Close();
166 }
167 
IsUbi(const char * path)168 bool UbiFileDescriptor::IsUbi(const char* path) {
169   base::FilePath device_node(path);
170   base::FilePath ubi_name(device_node.BaseName());
171   TEST_AND_RETURN_FALSE(base::StartsWith(ubi_name.MaybeAsASCII(), "ubi",
172                                          base::CompareCase::SENSITIVE));
173 
174   return static_cast<bool>(GetUbiVolumeInfo(path));
175 }
176 
Open(const char * path,int flags,mode_t mode)177 bool 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 
Open(const char * path,int flags)211 bool 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 
Read(void * buf,size_t count)217 ssize_t UbiFileDescriptor::Read(void* buf, size_t count) {
218   CHECK(mode_ == kReadOnly);
219   return EintrSafeFileDescriptor::Read(buf, count);
220 }
221 
Write(const void * buf,size_t count)222 ssize_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 
Seek(off64_t offset,int whence)231 off64_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 
Close()239 bool 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