Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(360)

Side by Side Diff: services/files/file_impl.cc

Issue 1375313006: For c++, Generate enum classes instead of enum from mojom. (Closed) Base URL: https://github.com/domokit/mojo.git@master
Patch Set: Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « services/files/directory_impl_unittest.cc ('k') | services/files/file_impl_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "services/files/file_impl.h" 5 #include "services/files/file_impl.h"
6 6
7 #include <errno.h> 7 #include <errno.h>
8 #include <fcntl.h> 8 #include <fcntl.h>
9 #include <sys/stat.h> 9 #include <sys/stat.h>
10 #include <sys/types.h> 10 #include <sys/types.h>
(...skipping 18 matching lines...) Expand all
29 FileImpl::FileImpl(InterfaceRequest<File> request, base::ScopedFD file_fd) 29 FileImpl::FileImpl(InterfaceRequest<File> request, base::ScopedFD file_fd)
30 : binding_(this, request.Pass()), file_fd_(file_fd.Pass()) { 30 : binding_(this, request.Pass()), file_fd_(file_fd.Pass()) {
31 DCHECK(file_fd_.is_valid()); 31 DCHECK(file_fd_.is_valid());
32 } 32 }
33 33
34 FileImpl::~FileImpl() { 34 FileImpl::~FileImpl() {
35 } 35 }
36 36
37 void FileImpl::Close(const CloseCallback& callback) { 37 void FileImpl::Close(const CloseCallback& callback) {
38 if (!file_fd_.is_valid()) { 38 if (!file_fd_.is_valid()) {
39 callback.Run(ERROR_CLOSED); 39 callback.Run(Error::CLOSED);
40 return; 40 return;
41 } 41 }
42 int fd_to_try_to_close = file_fd_.release(); 42 int fd_to_try_to_close = file_fd_.release();
43 // POSIX.1 (2013) leaves the validity of the FD undefined on EINTR and EIO. On 43 // POSIX.1 (2013) leaves the validity of the FD undefined on EINTR and EIO. On
44 // Linux, the FD is always invalidated, so we'll pretend that the close 44 // Linux, the FD is always invalidated, so we'll pretend that the close
45 // succeeded. (On other Unixes, the situation may be different and possibly 45 // succeeded. (On other Unixes, the situation may be different and possibly
46 // totally broken; see crbug.com/269623.) 46 // totally broken; see crbug.com/269623.)
47 if (IGNORE_EINTR(close(fd_to_try_to_close)) != 0) { 47 if (IGNORE_EINTR(close(fd_to_try_to_close)) != 0) {
48 // Save errno, since we do a few things and we don't want it trampled. 48 // Save errno, since we do a few things and we don't want it trampled.
49 int error = errno; 49 int error = errno;
50 CHECK_NE(error, EBADF); // This should never happen. 50 CHECK_NE(error, EBADF); // This should never happen.
51 DCHECK_NE(error, EINTR); // We already ignored EINTR. 51 DCHECK_NE(error, EINTR); // We already ignored EINTR.
52 // I don't know what Linux does on EIO (or any other errors) -- POSIX leaves 52 // I don't know what Linux does on EIO (or any other errors) -- POSIX leaves
53 // it undefined -- so report the error and hope that the FD was invalidated. 53 // it undefined -- so report the error and hope that the FD was invalidated.
54 callback.Run(ErrnoToError(error)); 54 callback.Run(ErrnoToError(error));
55 return; 55 return;
56 } 56 }
57 57
58 callback.Run(ERROR_OK); 58 callback.Run(Error::OK);
59 } 59 }
60 60
61 // TODO(vtl): Move the implementation to a thread pool. 61 // TODO(vtl): Move the implementation to a thread pool.
62 void FileImpl::Read(uint32_t num_bytes_to_read, 62 void FileImpl::Read(uint32_t num_bytes_to_read,
63 int64_t offset, 63 int64_t offset,
64 Whence whence, 64 Whence whence,
65 const ReadCallback& callback) { 65 const ReadCallback& callback) {
66 if (!file_fd_.is_valid()) { 66 if (!file_fd_.is_valid()) {
67 callback.Run(ERROR_CLOSED, Array<uint8_t>()); 67 callback.Run(Error::CLOSED, Array<uint8_t>());
68 return; 68 return;
69 } 69 }
70 if (num_bytes_to_read > kMaxReadSize) { 70 if (num_bytes_to_read > kMaxReadSize) {
71 callback.Run(ERROR_OUT_OF_RANGE, Array<uint8_t>()); 71 callback.Run(Error::OUT_OF_RANGE, Array<uint8_t>());
72 return; 72 return;
73 } 73 }
74 74
75 Error error = IsOffsetValid(offset); 75 Error error = IsOffsetValid(offset);
76 if (error != ERROR_OK) { 76 if (error != Error::OK) {
77 callback.Run(error, Array<uint8_t>()); 77 callback.Run(error, Array<uint8_t>());
78 return; 78 return;
79 } 79 }
80 80
81 error = IsWhenceValid(whence); 81 error = IsWhenceValid(whence);
82 if (error != ERROR_OK) { 82 if (error != Error::OK) {
83 callback.Run(error, Array<uint8_t>()); 83 callback.Run(error, Array<uint8_t>());
84 return; 84 return;
85 } 85 }
86 86
87 if (offset != 0 || whence != WHENCE_FROM_CURRENT) { 87 if (offset != 0 || whence != Whence::FROM_CURRENT) {
88 // TODO(vtl): Use |pread()| below in the |WHENCE_FROM_START| case. This 88 // TODO(vtl): Use |pread()| below in the |Whence::FROM_START| case. This
89 // implementation is obviously not atomic. (If someone seeks simultaneously, 89 // implementation is obviously not atomic. (If someone seeks simultaneously,
90 // we'll end up writing somewhere else. Or, well, we would if we were 90 // we'll end up writing somewhere else. Or, well, we would if we were
91 // multithreaded.) Maybe we should do an |ftell()| and always use |pread()|. 91 // multithreaded.) Maybe we should do an |ftell()| and always use |pread()|.
92 // TODO(vtl): Possibly, at least sometimes we should not change the file 92 // TODO(vtl): Possibly, at least sometimes we should not change the file
93 // position. See TODO in file.mojom. 93 // position. See TODO in file.mojom.
94 if (lseek(file_fd_.get(), static_cast<off_t>(offset), 94 if (lseek(file_fd_.get(), static_cast<off_t>(offset),
95 WhenceToStandardWhence(whence)) < 0) { 95 WhenceToStandardWhence(whence)) < 0) {
96 callback.Run(ErrnoToError(errno), Array<uint8_t>()); 96 callback.Run(ErrnoToError(errno), Array<uint8_t>());
97 return; 97 return;
98 } 98 }
99 } 99 }
100 100
101 Array<uint8_t> bytes_read(num_bytes_to_read); 101 Array<uint8_t> bytes_read(num_bytes_to_read);
102 ssize_t num_bytes_read = HANDLE_EINTR( 102 ssize_t num_bytes_read = HANDLE_EINTR(
103 read(file_fd_.get(), &bytes_read.front(), num_bytes_to_read)); 103 read(file_fd_.get(), &bytes_read.front(), num_bytes_to_read));
104 if (num_bytes_read < 0) { 104 if (num_bytes_read < 0) {
105 callback.Run(ErrnoToError(errno), Array<uint8_t>()); 105 callback.Run(ErrnoToError(errno), Array<uint8_t>());
106 return; 106 return;
107 } 107 }
108 108
109 DCHECK_LE(static_cast<size_t>(num_bytes_read), num_bytes_to_read); 109 DCHECK_LE(static_cast<size_t>(num_bytes_read), num_bytes_to_read);
110 bytes_read.resize(static_cast<size_t>(num_bytes_read)); 110 bytes_read.resize(static_cast<size_t>(num_bytes_read));
111 callback.Run(ERROR_OK, bytes_read.Pass()); 111 callback.Run(Error::OK, bytes_read.Pass());
112 } 112 }
113 113
114 // TODO(vtl): Move the implementation to a thread pool. 114 // TODO(vtl): Move the implementation to a thread pool.
115 void FileImpl::Write(Array<uint8_t> bytes_to_write, 115 void FileImpl::Write(Array<uint8_t> bytes_to_write,
116 int64_t offset, 116 int64_t offset,
117 Whence whence, 117 Whence whence,
118 const WriteCallback& callback) { 118 const WriteCallback& callback) {
119 DCHECK(!bytes_to_write.is_null()); 119 DCHECK(!bytes_to_write.is_null());
120 120
121 if (!file_fd_.is_valid()) { 121 if (!file_fd_.is_valid()) {
122 callback.Run(ERROR_CLOSED, 0); 122 callback.Run(Error::CLOSED, 0);
123 return; 123 return;
124 } 124 }
125 // Who knows what |write()| would return if the size is that big (and it 125 // Who knows what |write()| would return if the size is that big (and it
126 // actually wrote that much). 126 // actually wrote that much).
127 if (bytes_to_write.size() > 127 if (bytes_to_write.size() >
128 static_cast<size_t>(std::numeric_limits<ssize_t>::max())) { 128 static_cast<size_t>(std::numeric_limits<ssize_t>::max())) {
129 callback.Run(ERROR_OUT_OF_RANGE, 0); 129 callback.Run(Error::OUT_OF_RANGE, 0);
130 return; 130 return;
131 } 131 }
132 132
133 Error error = IsOffsetValid(offset); 133 Error error = IsOffsetValid(offset);
134 if (error != ERROR_OK) { 134 if (error != Error::OK) {
135 callback.Run(error, 0); 135 callback.Run(error, 0);
136 return; 136 return;
137 } 137 }
138 138
139 error = IsWhenceValid(whence); 139 error = IsWhenceValid(whence);
140 if (error != ERROR_OK) { 140 if (error != Error::OK) {
141 callback.Run(error, 0); 141 callback.Run(error, 0);
142 return; 142 return;
143 } 143 }
144 144
145 if (offset != 0 || whence != WHENCE_FROM_CURRENT) { 145 if (offset != 0 || whence != Whence::FROM_CURRENT) {
146 // TODO(vtl): Use |pwrite()| below in the |WHENCE_FROM_START| case. This 146 // TODO(vtl): Use |pwrite()| below in the |Whence::FROM_START| case. This
147 // implementation is obviously not atomic. (If someone seeks simultaneously, 147 // implementation is obviously not atomic. (If someone seeks simultaneously,
148 // we'll end up writing somewhere else. Or, well, we would if we were 148 // we'll end up writing somewhere else. Or, well, we would if we were
149 // multithreaded.) Maybe we should do an |ftell()| and always use 149 // multithreaded.) Maybe we should do an |ftell()| and always use
150 // |pwrite()|. 150 // |pwrite()|.
151 // TODO(vtl): Possibly, at least sometimes we should not change the file 151 // TODO(vtl): Possibly, at least sometimes we should not change the file
152 // position. See TODO in file.mojom. 152 // position. See TODO in file.mojom.
153 if (lseek(file_fd_.get(), static_cast<off_t>(offset), 153 if (lseek(file_fd_.get(), static_cast<off_t>(offset),
154 WhenceToStandardWhence(whence)) < 0) { 154 WhenceToStandardWhence(whence)) < 0) {
155 callback.Run(ErrnoToError(errno), 0); 155 callback.Run(ErrnoToError(errno), 0);
156 return; 156 return;
157 } 157 }
158 } 158 }
159 159
160 const void* buf = 160 const void* buf =
161 (bytes_to_write.size() > 0) ? &bytes_to_write.front() : nullptr; 161 (bytes_to_write.size() > 0) ? &bytes_to_write.front() : nullptr;
162 ssize_t num_bytes_written = 162 ssize_t num_bytes_written =
163 HANDLE_EINTR(write(file_fd_.get(), buf, bytes_to_write.size())); 163 HANDLE_EINTR(write(file_fd_.get(), buf, bytes_to_write.size()));
164 if (num_bytes_written < 0) { 164 if (num_bytes_written < 0) {
165 callback.Run(ErrnoToError(errno), 0); 165 callback.Run(ErrnoToError(errno), 0);
166 return; 166 return;
167 } 167 }
168 168
169 DCHECK_LE(static_cast<size_t>(num_bytes_written), 169 DCHECK_LE(static_cast<size_t>(num_bytes_written),
170 std::numeric_limits<uint32_t>::max()); 170 std::numeric_limits<uint32_t>::max());
171 callback.Run(ERROR_OK, static_cast<uint32_t>(num_bytes_written)); 171 callback.Run(Error::OK, static_cast<uint32_t>(num_bytes_written));
172 } 172 }
173 173
174 void FileImpl::ReadToStream(ScopedDataPipeProducerHandle source, 174 void FileImpl::ReadToStream(ScopedDataPipeProducerHandle source,
175 int64_t offset, 175 int64_t offset,
176 Whence whence, 176 Whence whence,
177 int64_t num_bytes_to_read, 177 int64_t num_bytes_to_read,
178 const ReadToStreamCallback& callback) { 178 const ReadToStreamCallback& callback) {
179 if (!file_fd_.is_valid()) { 179 if (!file_fd_.is_valid()) {
180 callback.Run(ERROR_CLOSED); 180 callback.Run(Error::CLOSED);
181 return; 181 return;
182 } 182 }
183 183
184 Error error = IsOffsetValid(offset); 184 Error error = IsOffsetValid(offset);
185 if (error != ERROR_OK) { 185 if (error != Error::OK) {
186 callback.Run(error); 186 callback.Run(error);
187 return; 187 return;
188 } 188 }
189 189
190 error = IsWhenceValid(whence); 190 error = IsWhenceValid(whence);
191 if (error != ERROR_OK) { 191 if (error != Error::OK) {
192 callback.Run(error); 192 callback.Run(error);
193 return; 193 return;
194 } 194 }
195 195
196 // TODO(vtl): FIXME soon 196 // TODO(vtl): FIXME soon
197 NOTIMPLEMENTED(); 197 NOTIMPLEMENTED();
198 callback.Run(ERROR_UNIMPLEMENTED); 198 callback.Run(Error::UNIMPLEMENTED);
199 } 199 }
200 200
201 void FileImpl::WriteFromStream(ScopedDataPipeConsumerHandle sink, 201 void FileImpl::WriteFromStream(ScopedDataPipeConsumerHandle sink,
202 int64_t offset, 202 int64_t offset,
203 Whence whence, 203 Whence whence,
204 const WriteFromStreamCallback& callback) { 204 const WriteFromStreamCallback& callback) {
205 if (!file_fd_.is_valid()) { 205 if (!file_fd_.is_valid()) {
206 callback.Run(ERROR_CLOSED); 206 callback.Run(Error::CLOSED);
207 return; 207 return;
208 } 208 }
209 209
210 Error error = IsOffsetValid(offset); 210 Error error = IsOffsetValid(offset);
211 if (error != ERROR_OK) { 211 if (error != Error::OK) {
212 callback.Run(error); 212 callback.Run(error);
213 return; 213 return;
214 } 214 }
215 215
216 error = IsWhenceValid(whence); 216 error = IsWhenceValid(whence);
217 if (error != ERROR_OK) { 217 if (error != Error::OK) {
218 callback.Run(error); 218 callback.Run(error);
219 return; 219 return;
220 } 220 }
221 221
222 // TODO(vtl): FIXME soon 222 // TODO(vtl): FIXME soon
223 NOTIMPLEMENTED(); 223 NOTIMPLEMENTED();
224 callback.Run(ERROR_UNIMPLEMENTED); 224 callback.Run(Error::UNIMPLEMENTED);
225 } 225 }
226 226
227 void FileImpl::Tell(const TellCallback& callback) { 227 void FileImpl::Tell(const TellCallback& callback) {
228 Seek(0, WHENCE_FROM_CURRENT, callback); 228 Seek(0, Whence::FROM_CURRENT, callback);
229 } 229 }
230 230
231 void FileImpl::Seek(int64_t offset, 231 void FileImpl::Seek(int64_t offset,
232 Whence whence, 232 Whence whence,
233 const SeekCallback& callback) { 233 const SeekCallback& callback) {
234 if (!file_fd_.is_valid()) { 234 if (!file_fd_.is_valid()) {
235 callback.Run(ERROR_CLOSED, 0); 235 callback.Run(Error::CLOSED, 0);
236 return; 236 return;
237 } 237 }
238 238
239 Error error = IsOffsetValid(offset); 239 Error error = IsOffsetValid(offset);
240 if (error != ERROR_OK) { 240 if (error != Error::OK) {
241 callback.Run(error, 0); 241 callback.Run(error, 0);
242 return; 242 return;
243 } 243 }
244 244
245 error = IsWhenceValid(whence); 245 error = IsWhenceValid(whence);
246 if (error != ERROR_OK) { 246 if (error != Error::OK) {
247 callback.Run(error, 0); 247 callback.Run(error, 0);
248 return; 248 return;
249 } 249 }
250 250
251 off_t position = lseek(file_fd_.get(), static_cast<off_t>(offset), 251 off_t position = lseek(file_fd_.get(), static_cast<off_t>(offset),
252 WhenceToStandardWhence(whence)); 252 WhenceToStandardWhence(whence));
253 if (position < 0) { 253 if (position < 0) {
254 callback.Run(ErrnoToError(errno), 0); 254 callback.Run(ErrnoToError(errno), 0);
255 return; 255 return;
256 } 256 }
257 257
258 callback.Run(ERROR_OK, static_cast<int64>(position)); 258 callback.Run(Error::OK, static_cast<int64>(position));
259 } 259 }
260 260
261 void FileImpl::Stat(const StatCallback& callback) { 261 void FileImpl::Stat(const StatCallback& callback) {
262 if (!file_fd_.is_valid()) { 262 if (!file_fd_.is_valid()) {
263 callback.Run(ERROR_CLOSED, nullptr); 263 callback.Run(Error::CLOSED, nullptr);
264 return; 264 return;
265 } 265 }
266 StatFD(file_fd_.get(), FILE_TYPE_REGULAR_FILE, callback); 266 StatFD(file_fd_.get(), FileType::REGULAR_FILE, callback);
267 } 267 }
268 268
269 void FileImpl::Truncate(int64_t size, const TruncateCallback& callback) { 269 void FileImpl::Truncate(int64_t size, const TruncateCallback& callback) {
270 if (!file_fd_.is_valid()) { 270 if (!file_fd_.is_valid()) {
271 callback.Run(ERROR_CLOSED); 271 callback.Run(Error::CLOSED);
272 return; 272 return;
273 } 273 }
274 if (size < 0) { 274 if (size < 0) {
275 callback.Run(ERROR_INVALID_ARGUMENT); 275 callback.Run(Error::INVALID_ARGUMENT);
276 return; 276 return;
277 } 277 }
278 278
279 Error error = IsOffsetValid(size); 279 Error error = IsOffsetValid(size);
280 if (error != ERROR_OK) { 280 if (error != Error::OK) {
281 callback.Run(error); 281 callback.Run(error);
282 return; 282 return;
283 } 283 }
284 284
285 if (ftruncate(file_fd_.get(), static_cast<off_t>(size)) != 0) { 285 if (ftruncate(file_fd_.get(), static_cast<off_t>(size)) != 0) {
286 callback.Run(ErrnoToError(errno)); 286 callback.Run(ErrnoToError(errno));
287 return; 287 return;
288 } 288 }
289 289
290 callback.Run(ERROR_OK); 290 callback.Run(Error::OK);
291 } 291 }
292 292
293 void FileImpl::Touch(TimespecOrNowPtr atime, 293 void FileImpl::Touch(TimespecOrNowPtr atime,
294 TimespecOrNowPtr mtime, 294 TimespecOrNowPtr mtime,
295 const TouchCallback& callback) { 295 const TouchCallback& callback) {
296 if (!file_fd_.is_valid()) { 296 if (!file_fd_.is_valid()) {
297 callback.Run(ERROR_CLOSED); 297 callback.Run(Error::CLOSED);
298 return; 298 return;
299 } 299 }
300 TouchFD(file_fd_.get(), atime.Pass(), mtime.Pass(), callback); 300 TouchFD(file_fd_.get(), atime.Pass(), mtime.Pass(), callback);
301 } 301 }
302 302
303 void FileImpl::Dup(InterfaceRequest<File> file, const DupCallback& callback) { 303 void FileImpl::Dup(InterfaceRequest<File> file, const DupCallback& callback) {
304 if (!file_fd_.is_valid()) { 304 if (!file_fd_.is_valid()) {
305 callback.Run(ERROR_CLOSED); 305 callback.Run(Error::CLOSED);
306 return; 306 return;
307 } 307 }
308 308
309 base::ScopedFD file_fd(dup(file_fd_.get())); 309 base::ScopedFD file_fd(dup(file_fd_.get()));
310 if (!file_fd.is_valid()) { 310 if (!file_fd.is_valid()) {
311 callback.Run(ErrnoToError(errno)); 311 callback.Run(ErrnoToError(errno));
312 return; 312 return;
313 } 313 }
314 314
315 new FileImpl(file.Pass(), file_fd.Pass()); 315 new FileImpl(file.Pass(), file_fd.Pass());
316 callback.Run(ERROR_OK); 316 callback.Run(Error::OK);
317 } 317 }
318 318
319 void FileImpl::Reopen(InterfaceRequest<File> file, 319 void FileImpl::Reopen(InterfaceRequest<File> file,
320 uint32_t open_flags, 320 uint32_t open_flags,
321 const ReopenCallback& callback) { 321 const ReopenCallback& callback) {
322 if (!file_fd_.is_valid()) { 322 if (!file_fd_.is_valid()) {
323 callback.Run(ERROR_CLOSED); 323 callback.Run(Error::CLOSED);
324 return; 324 return;
325 } 325 }
326 326
327 // TODO(vtl): FIXME soon 327 // TODO(vtl): FIXME soon
328 NOTIMPLEMENTED(); 328 NOTIMPLEMENTED();
329 callback.Run(ERROR_UNIMPLEMENTED); 329 callback.Run(Error::UNIMPLEMENTED);
330 } 330 }
331 331
332 void FileImpl::AsBuffer(const AsBufferCallback& callback) { 332 void FileImpl::AsBuffer(const AsBufferCallback& callback) {
333 if (!file_fd_.is_valid()) { 333 if (!file_fd_.is_valid()) {
334 callback.Run(ERROR_CLOSED, ScopedSharedBufferHandle()); 334 callback.Run(Error::CLOSED, ScopedSharedBufferHandle());
335 return; 335 return;
336 } 336 }
337 337
338 // TODO(vtl): FIXME soon 338 // TODO(vtl): FIXME soon
339 NOTIMPLEMENTED(); 339 NOTIMPLEMENTED();
340 callback.Run(ERROR_UNIMPLEMENTED, ScopedSharedBufferHandle()); 340 callback.Run(Error::UNIMPLEMENTED, ScopedSharedBufferHandle());
341 } 341 }
342 342
343 void FileImpl::Ioctl(uint32_t request, 343 void FileImpl::Ioctl(uint32_t request,
344 Array<uint32_t> in_values, 344 Array<uint32_t> in_values,
345 const IoctlCallback& callback) { 345 const IoctlCallback& callback) {
346 if (!file_fd_.is_valid()) { 346 if (!file_fd_.is_valid()) {
347 callback.Run(ERROR_CLOSED, Array<uint32_t>()); 347 callback.Run(Error::CLOSED, Array<uint32_t>());
348 return; 348 return;
349 } 349 }
350 350
351 // TODO(vtl): The "correct" error code should be one that can be translated to 351 // TODO(vtl): The "correct" error code should be one that can be translated to
352 // ENOTTY! 352 // ENOTTY!
353 callback.Run(ERROR_UNAVAILABLE, Array<uint32_t>()); 353 callback.Run(Error::UNAVAILABLE, Array<uint32_t>());
354 } 354 }
355 355
356 } // namespace files 356 } // namespace files
357 } // namespace mojo 357 } // namespace mojo
OLDNEW
« no previous file with comments | « services/files/directory_impl_unittest.cc ('k') | services/files/file_impl_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698