• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2014 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "components/metrics/serialization/serialization_utils.h"
6 
7 #include <errno.h>
8 #include <stdint.h>
9 #include <sys/file.h>
10 #include <unistd.h>
11 
12 #include <utility>
13 
14 #include "base/containers/span.h"
15 #include "base/files/file_path.h"
16 #include "base/files/file_util.h"
17 #include "base/files/scoped_file.h"
18 #include "base/logging.h"
19 #include "base/metrics/histogram_functions.h"
20 #include "base/numerics/safe_math.h"
21 #include "base/strings/string_split.h"
22 #include "base/strings/string_util.h"
23 #include "components/metrics/serialization/metric_sample.h"
24 
25 #define READ_WRITE_ALL_FILE_FLAGS \
26   (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
27 
28 namespace metrics {
29 namespace {
30 // Reads the next message from |file_descriptor| into |message|.
31 //
32 // |message| will be set to the empty string if no message could be read (EOF)
33 // or the message was badly constructed.
34 //
35 // Returns false if no message can be read from this file anymore (EOF or
36 // unrecoverable error).
ReadMessage(int fd,std::string * message)37 bool ReadMessage(int fd, std::string* message) {
38   CHECK(message);
39 
40   int result;
41   uint32_t encoded_size;
42   constexpr size_t message_header_size = sizeof(uint32_t);
43   // The file containing the metrics does not leave the device so the writer and
44   // the reader will always have the same endianness.
45   result = HANDLE_EINTR(read(fd, &encoded_size, message_header_size));
46   if (result < 0) {
47     DPLOG(ERROR) << "reading metrics message header";
48     return false;
49   }
50   if (result == 0) {
51     // This indicates a normal EOF.
52     return false;
53   }
54   if (base::checked_cast<size_t>(result) < message_header_size) {
55     DLOG(ERROR) << "bad read size " << result << ", expecting "
56                 << message_header_size;
57     return false;
58   }
59 
60   // kMessageMaxLength applies to the entire message: the 4-byte
61   // length field and the content.
62   size_t message_size = base::checked_cast<size_t>(encoded_size);
63   if (message_size > SerializationUtils::kMessageMaxLength) {
64     DLOG(ERROR) << "message too long : " << message_size;
65     if (HANDLE_EINTR(lseek(fd, message_size - message_header_size, SEEK_CUR)) ==
66         -1) {
67       DLOG(ERROR) << "error while skipping message. abort";
68       return false;
69     }
70     // Badly formatted message was skipped. Treat the badly formatted sample as
71     // an empty sample.
72     message->clear();
73     return true;
74   }
75 
76   if (message_size < message_header_size) {
77     DLOG(ERROR) << "message too short : " << message_size;
78     return false;
79   }
80 
81   message_size -= message_header_size;  // The message size includes itself.
82   char buffer[SerializationUtils::kMessageMaxLength];
83   if (!base::ReadFromFD(fd, base::span(buffer).first(message_size))) {
84     DPLOG(ERROR) << "reading metrics message body";
85     return false;
86   }
87   *message = std::string(buffer, message_size);
88   return true;
89 }
90 
91 // Reads all samples from a file and when done:
92 //  1) deletes the file if |delete_file| is true.
93 //  2) truncates the file if |delete_file| is false.
94 //
95 // This method is the implementation of ReadAndTruncateMetricsFromFile() and
96 // ReadAndDeleteMetricsFromFile().
ReadAndTruncateOrDeleteMetricsFromFile(const std::string & filename,bool delete_file,std::vector<std::unique_ptr<MetricSample>> * metrics)97 void ReadAndTruncateOrDeleteMetricsFromFile(
98     const std::string& filename,
99     bool delete_file,
100     std::vector<std::unique_ptr<MetricSample>>* metrics) {
101   struct stat stat_buf;
102   int result;
103 
104   result = stat(filename.c_str(), &stat_buf);
105   if (result < 0) {
106     if (errno == ENOENT) {
107       // File doesn't exist, nothing to collect. This isn't an error, it just
108       // means nothing on the ChromeOS side has written to the file yet.
109     } else {
110       DPLOG(ERROR) << "bad metrics file stat: " << filename;
111     }
112     return;
113   }
114   if (stat_buf.st_size == 0) {
115     // Also nothing to collect.
116     return;
117   }
118   // Only need to read/write if we're truncating.
119   int flag = delete_file ? O_RDONLY : O_RDWR;
120   base::ScopedFD fd(open(filename.c_str(), flag));
121   if (fd.get() < 0) {
122     DPLOG(ERROR) << "cannot open: " << filename;
123     return;
124   }
125   result = flock(fd.get(), LOCK_EX);
126   if (result < 0) {
127     DPLOG(ERROR) << "cannot lock: " << filename;
128     return;
129   }
130 
131   // This processes all messages in the log. When all messages are
132   // read and processed, or an error occurs, or we've read so many that the
133   // buffer is at risk of overflowing, delete the file or truncate the file to
134   // zero size according to |delete_file|. If we hit kMaxMessagesPerRead, don't
135   // add them to the vector to avoid memory overflow.
136   while (metrics->size() <
137          static_cast<size_t>(SerializationUtils::kMaxMessagesPerRead)) {
138     std::string message;
139 
140     if (!ReadMessage(fd.get(), &message)) {
141       break;
142     }
143 
144     std::unique_ptr<MetricSample> sample =
145         SerializationUtils::ParseSample(message);
146     if (sample) {
147       metrics->push_back(std::move(sample));
148     }
149   }
150 
151   base::UmaHistogramCustomCounts(
152       "Platform.ExternalMetrics.SamplesRead", metrics->size(), 1,
153       SerializationUtils::kMaxMessagesPerRead - 1, 50);
154 
155   if (delete_file) {
156     result = unlink(filename.c_str());
157     if (result < 0) {
158       DPLOG(ERROR) << "error deleting metrics log: " << filename;
159     }
160   } else {
161     result = ftruncate(fd.get(), 0);
162     if (result < 0) {
163       DPLOG(ERROR) << "error truncating metrics log: " << filename;
164     }
165   }
166 
167   result = flock(fd.get(), LOCK_UN);
168   if (result < 0) {
169     DPLOG(ERROR) << "error unlocking metrics log: " << filename;
170   }
171 }
172 
173 }  // namespace
174 
ParseSample(const std::string & sample)175 std::unique_ptr<MetricSample> SerializationUtils::ParseSample(
176     const std::string& sample) {
177   if (sample.empty()) {
178     return nullptr;
179   }
180 
181   std::vector<std::string> parts =
182       base::SplitString(sample, std::string(1, '\0'), base::TRIM_WHITESPACE,
183                         base::SPLIT_WANT_ALL);
184   // We should have two null terminated strings so split should produce
185   // three chunks.
186   if (parts.size() != 3) {
187     DLOG(ERROR) << "splitting message on \\0 produced " << parts.size()
188                 << " parts (expected 3)";
189     return nullptr;
190   }
191   const std::string& name = parts[0];
192   const std::string& value = parts[1];
193 
194   if (base::EqualsCaseInsensitiveASCII(name, "crash")) {
195     return MetricSample::ParseCrash(value);
196   }
197   if (base::EqualsCaseInsensitiveASCII(name, "histogram")) {
198     return MetricSample::ParseHistogram(value);
199   }
200   if (base::EqualsCaseInsensitiveASCII(name, "linearhistogram")) {
201     return MetricSample::ParseLinearHistogram(value);
202   }
203   if (base::EqualsCaseInsensitiveASCII(name, "sparsehistogram")) {
204     return MetricSample::ParseSparseHistogram(value);
205   }
206   if (base::EqualsCaseInsensitiveASCII(name, "useraction")) {
207     return MetricSample::ParseUserAction(value);
208   }
209   DLOG(ERROR) << "invalid event type: " << name << ", value: " << value;
210   return nullptr;
211 }
212 
ReadAndTruncateMetricsFromFile(const std::string & filename,std::vector<std::unique_ptr<MetricSample>> * metrics)213 void SerializationUtils::ReadAndTruncateMetricsFromFile(
214     const std::string& filename,
215     std::vector<std::unique_ptr<MetricSample>>* metrics) {
216   ReadAndTruncateOrDeleteMetricsFromFile(filename, /*delete_file=*/false,
217                                          metrics);
218 }
219 
ReadAndDeleteMetricsFromFile(const std::string & filename,std::vector<std::unique_ptr<MetricSample>> * metrics)220 void SerializationUtils::ReadAndDeleteMetricsFromFile(
221     const std::string& filename,
222     std::vector<std::unique_ptr<MetricSample>>* metrics) {
223   ReadAndTruncateOrDeleteMetricsFromFile(filename, /*delete_file=*/true,
224                                          metrics);
225 }
226 
WriteMetricToFile(const MetricSample & sample,const std::string & filename)227 bool SerializationUtils::WriteMetricToFile(const MetricSample& sample,
228                                            const std::string& filename) {
229   if (!sample.IsValid()) {
230     return false;
231   }
232 
233   base::ScopedFD file_descriptor(open(filename.c_str(),
234                                       O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC,
235                                       READ_WRITE_ALL_FILE_FLAGS));
236 
237   if (file_descriptor.get() < 0) {
238     DPLOG(ERROR) << "error opening the file: " << filename;
239     return false;
240   }
241 
242   fchmod(file_descriptor.get(), READ_WRITE_ALL_FILE_FLAGS);
243   // Grab a lock to avoid chrome truncating the file underneath us. Keep the
244   // file locked as briefly as possible. Freeing file_descriptor will close the
245   // file and remove the lock IFF the process was not forked in the meantime,
246   // which will leave the flock hanging and deadlock the reporting until the
247   // forked process is killed otherwise. Thus we have to explicitly unlock the
248   // file below.
249   if (HANDLE_EINTR(flock(file_descriptor.get(), LOCK_EX)) < 0) {
250     DPLOG(ERROR) << "error locking: " << filename;
251     return false;
252   }
253 
254   std::string msg = sample.ToString();
255   size_t size = 0;
256   if (!base::CheckAdd(msg.length(), sizeof(uint32_t)).AssignIfValid(&size) ||
257       size > kMessageMaxLength) {
258     DPLOG(ERROR) << "cannot write message: too long: " << filename;
259     std::ignore = flock(file_descriptor.get(), LOCK_UN);
260     return false;
261   }
262 
263   // The file containing the metrics samples will only be read by programs on
264   // the same device so we do not check endianness.
265   uint32_t encoded_size = base::checked_cast<uint32_t>(size);
266   if (!base::WriteFileDescriptor(file_descriptor.get(),
267                                  base::byte_span_from_ref(encoded_size))) {
268     DPLOG(ERROR) << "error writing message length: " << filename;
269     std::ignore = flock(file_descriptor.get(), LOCK_UN);
270     return false;
271   }
272 
273   if (!base::WriteFileDescriptor(file_descriptor.get(), msg)) {
274     DPLOG(ERROR) << "error writing message: " << filename;
275     std::ignore = flock(file_descriptor.get(), LOCK_UN);
276     return false;
277   }
278 
279   return true;
280 }
281 
282 }  // namespace metrics
283