1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // Copyright (C) 2016 Intel Corporation. |
3 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
4 | |
5 | #include "qfsfileengine_p.h" |
6 | #include "qfsfileengine_iterator_p.h" |
7 | #include "qfilesystemengine_p.h" |
8 | #include "qdatetime.h" |
9 | #include "qset.h" |
10 | #include <QtCore/qdebug.h> |
11 | |
12 | #ifndef QT_NO_FSFILEENGINE |
13 | |
14 | #include <errno.h> |
15 | #if defined(Q_OS_UNIX) |
16 | #include "private/qcore_unix_p.h" |
17 | #endif |
18 | #include <stdio.h> |
19 | #include <stdlib.h> |
20 | #if defined(Q_OS_DARWIN) |
21 | # include <private/qcore_mac_p.h> |
22 | #endif |
23 | |
24 | QT_BEGIN_NAMESPACE |
25 | |
26 | using namespace Qt::StringLiterals; |
27 | |
28 | #ifdef Q_OS_WIN |
29 | # ifndef S_ISREG |
30 | # define S_ISREG(x) (((x) & S_IFMT) == S_IFREG) |
31 | # endif |
32 | # ifndef S_ISCHR |
33 | # define S_ISCHR(x) (((x) & S_IFMT) == S_IFCHR) |
34 | # endif |
35 | # ifndef S_ISFIFO |
36 | # define S_ISFIFO(x) false |
37 | # endif |
38 | # ifndef S_ISSOCK |
39 | # define S_ISSOCK(x) false |
40 | # endif |
41 | # ifndef INVALID_FILE_ATTRIBUTES |
42 | # define INVALID_FILE_ATTRIBUTES (DWORD (-1)) |
43 | # endif |
44 | #endif |
45 | |
46 | #ifdef Q_OS_WIN |
47 | // on Windows, read() and write() use int and unsigned int |
48 | typedef int SignedIOType; |
49 | typedef unsigned int UnsignedIOType; |
50 | #else |
51 | typedef ssize_t SignedIOType; |
52 | typedef size_t UnsignedIOType; |
53 | static_assert(sizeof(SignedIOType) == sizeof(UnsignedIOType), |
54 | "Unsupported: read/write return a type with different size as the len parameter" ); |
55 | #endif |
56 | |
57 | /*! \class QFSFileEngine |
58 | \inmodule QtCore |
59 | \brief The QFSFileEngine class implements Qt's default file engine. |
60 | \since 4.1 |
61 | \internal |
62 | |
63 | This class is part of the file engine framework in Qt. If you only want to |
64 | access files or directories, use QFile, QFileInfo or QDir instead. |
65 | |
66 | QFSFileEngine is the default file engine for accessing regular files. It |
67 | is provided for convenience; by subclassing this class, you can alter its |
68 | behavior slightly, without having to write a complete QAbstractFileEngine |
69 | subclass. To install your custom file engine, you must also subclass |
70 | QAbstractFileEngineHandler and create an instance of your handler. |
71 | |
72 | It can also be useful to create a QFSFileEngine object directly if you |
73 | need to use the local file system inside QAbstractFileEngine::create(), in |
74 | order to avoid recursion (as higher-level classes tend to call |
75 | QAbstractFileEngine::create()). |
76 | */ |
77 | |
78 | //**************** QFSFileEnginePrivate |
79 | QFSFileEnginePrivate::QFSFileEnginePrivate() : QAbstractFileEnginePrivate() |
80 | { |
81 | init(); |
82 | } |
83 | |
84 | /*! |
85 | \internal |
86 | */ |
87 | void QFSFileEnginePrivate::init() |
88 | { |
89 | is_sequential = 0; |
90 | tried_stat = 0; |
91 | need_lstat = 1; |
92 | is_link = 0; |
93 | openMode = QIODevice::NotOpen; |
94 | fd = -1; |
95 | fh = nullptr; |
96 | lastIOCommand = IOFlushCommand; |
97 | lastFlushFailed = false; |
98 | closeFileHandle = false; |
99 | #ifdef Q_OS_WIN |
100 | fileAttrib = INVALID_FILE_ATTRIBUTES; |
101 | fileHandle = INVALID_HANDLE_VALUE; |
102 | mapHandle = NULL; |
103 | cachedFd = -1; |
104 | #endif |
105 | } |
106 | |
107 | /*! |
108 | Constructs a QFSFileEngine for the file name \a file. |
109 | */ |
110 | QFSFileEngine::QFSFileEngine(const QString &file) |
111 | : QAbstractFileEngine(*new QFSFileEnginePrivate) |
112 | { |
113 | Q_D(QFSFileEngine); |
114 | d->fileEntry = QFileSystemEntry(file); |
115 | } |
116 | |
117 | /*! |
118 | Constructs a QFSFileEngine. |
119 | */ |
120 | QFSFileEngine::QFSFileEngine() : QAbstractFileEngine(*new QFSFileEnginePrivate) |
121 | { |
122 | } |
123 | |
124 | /*! |
125 | \internal |
126 | */ |
127 | QFSFileEngine::QFSFileEngine(QFSFileEnginePrivate &dd) |
128 | : QAbstractFileEngine(dd) |
129 | { |
130 | } |
131 | |
132 | /*! |
133 | \internal |
134 | */ |
135 | ProcessOpenModeResult processOpenModeFlags(QIODevice::OpenMode openMode) |
136 | { |
137 | ProcessOpenModeResult result; |
138 | result.ok = false; |
139 | if ((openMode & QFile::NewOnly) && (openMode & QFile::ExistingOnly)) { |
140 | qWarning(msg: "NewOnly and ExistingOnly are mutually exclusive" ); |
141 | result.error = "NewOnly and ExistingOnly are mutually exclusive"_L1 ; |
142 | return result; |
143 | } |
144 | |
145 | if ((openMode & QFile::ExistingOnly) && !(openMode & (QFile::ReadOnly | QFile::WriteOnly))) { |
146 | qWarning(msg: "ExistingOnly must be specified alongside ReadOnly, WriteOnly, or ReadWrite" ); |
147 | result.error = |
148 | "ExistingOnly must be specified alongside ReadOnly, WriteOnly, or ReadWrite"_L1 ; |
149 | return result; |
150 | } |
151 | |
152 | // Either Append or NewOnly implies WriteOnly |
153 | if (openMode & (QFile::Append | QFile::NewOnly)) |
154 | openMode |= QFile::WriteOnly; |
155 | |
156 | // WriteOnly implies Truncate when ReadOnly, Append, and NewOnly are not set. |
157 | if ((openMode & QFile::WriteOnly) && !(openMode & (QFile::ReadOnly | QFile::Append | QFile::NewOnly))) |
158 | openMode |= QFile::Truncate; |
159 | |
160 | result.ok = true; |
161 | result.openMode = openMode; |
162 | return result; |
163 | } |
164 | |
165 | /*! |
166 | Destructs the QFSFileEngine. |
167 | */ |
168 | QFSFileEngine::~QFSFileEngine() |
169 | { |
170 | Q_D(QFSFileEngine); |
171 | if (d->closeFileHandle) { |
172 | if (d->fh) { |
173 | fclose(stream: d->fh); |
174 | } else if (d->fd != -1) { |
175 | QT_CLOSE(fd: d->fd); |
176 | } |
177 | } |
178 | d->unmapAll(); |
179 | } |
180 | |
181 | /*! |
182 | \reimp |
183 | */ |
184 | void QFSFileEngine::setFileName(const QString &file) |
185 | { |
186 | Q_D(QFSFileEngine); |
187 | d->init(); |
188 | d->fileEntry = QFileSystemEntry(file); |
189 | } |
190 | |
191 | /*! |
192 | \reimp |
193 | */ |
194 | bool QFSFileEngine::open(QIODevice::OpenMode openMode, |
195 | std::optional<QFile::Permissions> permissions) |
196 | { |
197 | Q_ASSERT_X(openMode & QIODevice::Unbuffered, "QFSFileEngine::open" , |
198 | "QFSFileEngine no longer supports buffered mode; upper layer must buffer" ); |
199 | |
200 | Q_D(QFSFileEngine); |
201 | if (d->fileEntry.isEmpty()) { |
202 | qWarning(msg: "QFSFileEngine::open: No file name specified" ); |
203 | setError(error: QFile::OpenError, str: "No file name specified"_L1 ); |
204 | return false; |
205 | } |
206 | |
207 | const ProcessOpenModeResult res = processOpenModeFlags(openMode); |
208 | if (!res.ok) { |
209 | setError(error: QFileDevice::OpenError, str: res.error); |
210 | return false; |
211 | } |
212 | |
213 | d->openMode = res.openMode; |
214 | d->lastFlushFailed = false; |
215 | d->tried_stat = 0; |
216 | d->fh = nullptr; |
217 | d->fd = -1; |
218 | |
219 | return d->nativeOpen(openMode: d->openMode, permissions); |
220 | } |
221 | |
222 | /*! |
223 | Opens the file handle \a fh in \a openMode mode. Returns \c true on |
224 | success; otherwise returns \c false. |
225 | */ |
226 | bool QFSFileEngine::open(QIODevice::OpenMode openMode, FILE *fh) |
227 | { |
228 | return open(flags: openMode, fh, handleFlags: QFile::DontCloseHandle); |
229 | } |
230 | |
231 | bool QFSFileEngine::open(QIODevice::OpenMode openMode, FILE *fh, QFile::FileHandleFlags handleFlags) |
232 | { |
233 | Q_ASSERT_X(openMode & QIODevice::Unbuffered, "QFSFileEngine::open" , |
234 | "QFSFileEngine no longer supports buffered mode; upper layer must buffer" ); |
235 | |
236 | Q_D(QFSFileEngine); |
237 | |
238 | const ProcessOpenModeResult res = processOpenModeFlags(openMode); |
239 | if (!res.ok) { |
240 | setError(error: QFileDevice::OpenError, str: res.error); |
241 | return false; |
242 | } |
243 | |
244 | d->openMode = res.openMode; |
245 | d->lastFlushFailed = false; |
246 | d->closeFileHandle = handleFlags.testAnyFlag(flag: QFile::AutoCloseHandle); |
247 | d->fileEntry.clear(); |
248 | d->tried_stat = 0; |
249 | d->fd = -1; |
250 | |
251 | return d->openFh(flags: d->openMode, fh); |
252 | } |
253 | |
254 | /*! |
255 | Opens the file handle \a fh using the open mode \a flags. |
256 | */ |
257 | bool QFSFileEnginePrivate::openFh(QIODevice::OpenMode openMode, FILE *fh) |
258 | { |
259 | Q_ASSERT_X(openMode & QIODevice::Unbuffered, "QFSFileEngine::open" , |
260 | "QFSFileEngine no longer supports buffered mode; upper layer must buffer" ); |
261 | |
262 | Q_Q(QFSFileEngine); |
263 | this->fh = fh; |
264 | fd = -1; |
265 | |
266 | // Seek to the end when in Append mode. |
267 | if (openMode & QIODevice::Append) { |
268 | int ret; |
269 | do { |
270 | ret = QT_FSEEK(stream: fh, off: 0, SEEK_END); |
271 | } while (ret != 0 && errno == EINTR); |
272 | |
273 | if (ret != 0) { |
274 | q->setError(errno == EMFILE ? QFile::ResourceError : QFile::OpenError, |
275 | str: QSystemError::stdString(errno)); |
276 | |
277 | this->openMode = QIODevice::NotOpen; |
278 | this->fh = nullptr; |
279 | |
280 | return false; |
281 | } |
282 | } |
283 | |
284 | return true; |
285 | } |
286 | |
287 | /*! |
288 | Opens the file descriptor \a fd in \a openMode mode. Returns \c true |
289 | on success; otherwise returns \c false. |
290 | */ |
291 | bool QFSFileEngine::open(QIODevice::OpenMode openMode, int fd) |
292 | { |
293 | return open(flags: openMode, fd, handleFlags: QFile::DontCloseHandle); |
294 | } |
295 | |
296 | bool QFSFileEngine::open(QIODevice::OpenMode openMode, int fd, QFile::FileHandleFlags handleFlags) |
297 | { |
298 | Q_D(QFSFileEngine); |
299 | |
300 | const ProcessOpenModeResult res = processOpenModeFlags(openMode); |
301 | if (!res.ok) { |
302 | setError(error: QFileDevice::OpenError, str: res.error); |
303 | return false; |
304 | } |
305 | |
306 | d->openMode = res.openMode; |
307 | d->lastFlushFailed = false; |
308 | d->closeFileHandle = handleFlags.testAnyFlag(flag: QFile::AutoCloseHandle); |
309 | d->fileEntry.clear(); |
310 | d->fh = nullptr; |
311 | d->fd = -1; |
312 | d->tried_stat = 0; |
313 | |
314 | return d->openFd(flags: d->openMode, fd); |
315 | } |
316 | |
317 | |
318 | /*! |
319 | Opens the file descriptor \a fd to the file engine, using the open mode \a |
320 | flags. |
321 | */ |
322 | bool QFSFileEnginePrivate::openFd(QIODevice::OpenMode openMode, int fd) |
323 | { |
324 | Q_Q(QFSFileEngine); |
325 | this->fd = fd; |
326 | fh = nullptr; |
327 | |
328 | // Seek to the end when in Append mode. |
329 | if (openMode & QFile::Append) { |
330 | QT_OFF_T ret; |
331 | do { |
332 | ret = QT_LSEEK(fd: fd, offset: 0, SEEK_END); |
333 | } while (ret == -1 && errno == EINTR); |
334 | |
335 | if (ret == -1) { |
336 | q->setError(errno == EMFILE ? QFile::ResourceError : QFile::OpenError, |
337 | str: QSystemError::stdString(errno)); |
338 | |
339 | this->openMode = QIODevice::NotOpen; |
340 | this->fd = -1; |
341 | |
342 | return false; |
343 | } |
344 | } |
345 | |
346 | return true; |
347 | } |
348 | |
349 | /*! |
350 | \reimp |
351 | */ |
352 | bool QFSFileEngine::close() |
353 | { |
354 | Q_D(QFSFileEngine); |
355 | d->openMode = QIODevice::NotOpen; |
356 | return d->nativeClose(); |
357 | } |
358 | |
359 | /*! |
360 | \internal |
361 | */ |
362 | bool QFSFileEnginePrivate::closeFdFh() |
363 | { |
364 | Q_Q(QFSFileEngine); |
365 | if (fd == -1 && !fh) |
366 | return false; |
367 | |
368 | // Flush the file if it's buffered, and if the last flush didn't fail. |
369 | bool flushed = !fh || (!lastFlushFailed && q->flush()); |
370 | bool closed = true; |
371 | tried_stat = 0; |
372 | |
373 | // Close the file if we created the handle. |
374 | if (closeFileHandle) { |
375 | int ret; |
376 | |
377 | if (fh) { |
378 | // Close buffered file. |
379 | ret = fclose(stream: fh); |
380 | } else { |
381 | // Close unbuffered file. |
382 | ret = QT_CLOSE(fd); |
383 | } |
384 | |
385 | // We must reset these guys regardless; calling close again after a |
386 | // failed close causes crashes on some systems. |
387 | fh = nullptr; |
388 | fd = -1; |
389 | closed = (ret == 0); |
390 | } |
391 | |
392 | // Report errors. |
393 | if (!flushed || !closed) { |
394 | if (flushed) { |
395 | // If not flushed, we want the flush error to fall through. |
396 | q->setError(error: QFile::UnspecifiedError, str: QSystemError::stdString(errno)); |
397 | } |
398 | return false; |
399 | } |
400 | |
401 | return true; |
402 | } |
403 | |
404 | /*! |
405 | \reimp |
406 | */ |
407 | bool QFSFileEngine::flush() |
408 | { |
409 | Q_D(QFSFileEngine); |
410 | if ((d->openMode & QIODevice::WriteOnly) == 0) { |
411 | // Nothing in the write buffers, so flush succeeds in doing |
412 | // nothing. |
413 | return true; |
414 | } |
415 | return d->nativeFlush(); |
416 | } |
417 | |
418 | /*! |
419 | \reimp |
420 | */ |
421 | bool QFSFileEngine::syncToDisk() |
422 | { |
423 | Q_D(QFSFileEngine); |
424 | if ((d->openMode & QIODevice::WriteOnly) == 0) |
425 | return true; |
426 | return d->nativeSyncToDisk(); |
427 | } |
428 | |
429 | /*! |
430 | \internal |
431 | */ |
432 | bool QFSFileEnginePrivate::flushFh() |
433 | { |
434 | Q_Q(QFSFileEngine); |
435 | |
436 | // Never try to flush again if the last flush failed. Otherwise you can |
437 | // get crashes on some systems (AIX). |
438 | if (lastFlushFailed) |
439 | return false; |
440 | |
441 | int ret = fflush(stream: fh); |
442 | |
443 | lastFlushFailed = (ret != 0); |
444 | lastIOCommand = QFSFileEnginePrivate::IOFlushCommand; |
445 | |
446 | if (ret != 0) { |
447 | q->setError(errno == ENOSPC ? QFile::ResourceError : QFile::WriteError, |
448 | str: QSystemError::stdString(errno)); |
449 | return false; |
450 | } |
451 | return true; |
452 | } |
453 | |
454 | /*! |
455 | \reimp |
456 | */ |
457 | qint64 QFSFileEngine::size() const |
458 | { |
459 | Q_D(const QFSFileEngine); |
460 | return d->nativeSize(); |
461 | } |
462 | |
463 | /*! |
464 | \internal |
465 | */ |
466 | void QFSFileEnginePrivate::unmapAll() |
467 | { |
468 | if (!maps.isEmpty()) { |
469 | const QList<uchar*> keys = maps.keys(); // Make a copy since unmap() modifies the map. |
470 | for (int i = 0; i < keys.size(); ++i) |
471 | unmap(ptr: keys.at(i)); |
472 | } |
473 | } |
474 | |
475 | #ifndef Q_OS_WIN |
476 | /*! |
477 | \internal |
478 | */ |
479 | qint64 QFSFileEnginePrivate::sizeFdFh() const |
480 | { |
481 | Q_Q(const QFSFileEngine); |
482 | const_cast<QFSFileEngine *>(q)->flush(); |
483 | |
484 | tried_stat = 0; |
485 | metaData.clearFlags(flags: QFileSystemMetaData::SizeAttribute); |
486 | if (!doStat(flags: QFileSystemMetaData::SizeAttribute)) |
487 | return 0; |
488 | return metaData.size(); |
489 | } |
490 | #endif |
491 | |
492 | /*! |
493 | \reimp |
494 | */ |
495 | qint64 QFSFileEngine::pos() const |
496 | { |
497 | Q_D(const QFSFileEngine); |
498 | return d->nativePos(); |
499 | } |
500 | |
501 | /*! |
502 | \internal |
503 | */ |
504 | qint64 QFSFileEnginePrivate::posFdFh() const |
505 | { |
506 | if (fh) |
507 | return qint64(QT_FTELL(stream: fh)); |
508 | return QT_LSEEK(fd: fd, offset: 0, SEEK_CUR); |
509 | } |
510 | |
511 | /*! |
512 | \reimp |
513 | */ |
514 | bool QFSFileEngine::seek(qint64 pos) |
515 | { |
516 | Q_D(QFSFileEngine); |
517 | return d->nativeSeek(pos); |
518 | } |
519 | |
520 | /*! |
521 | \reimp |
522 | */ |
523 | QDateTime QFSFileEngine::fileTime(QFile::FileTime time) const |
524 | { |
525 | Q_D(const QFSFileEngine); |
526 | |
527 | if (time == QFile::FileAccessTime) { |
528 | // always refresh for the access time |
529 | d->metaData.clearFlags(flags: QFileSystemMetaData::AccessTime); |
530 | } |
531 | |
532 | if (d->doStat(flags: QFileSystemMetaData::Times)) |
533 | return d->metaData.fileTime(time); |
534 | |
535 | return QDateTime(); |
536 | } |
537 | |
538 | |
539 | /*! |
540 | \internal |
541 | */ |
542 | bool QFSFileEnginePrivate::seekFdFh(qint64 pos) |
543 | { |
544 | Q_Q(QFSFileEngine); |
545 | |
546 | // On Windows' stdlib implementation, the results of calling fread and |
547 | // fwrite are undefined if not called either in sequence, or if preceded |
548 | // with a call to fflush(). |
549 | if (lastIOCommand != QFSFileEnginePrivate::IOFlushCommand && !q->flush()) |
550 | return false; |
551 | |
552 | if (pos < 0 || pos != qint64(QT_OFF_T(pos))) |
553 | return false; |
554 | |
555 | if (fh) { |
556 | // Buffered stdlib mode. |
557 | int ret; |
558 | do { |
559 | ret = QT_FSEEK(stream: fh, QT_OFF_T(pos), SEEK_SET); |
560 | } while (ret != 0 && errno == EINTR); |
561 | |
562 | if (ret != 0) { |
563 | q->setError(error: QFile::ReadError, str: QSystemError::stdString(errno)); |
564 | return false; |
565 | } |
566 | } else { |
567 | // Unbuffered stdio mode. |
568 | if (QT_LSEEK(fd: fd, QT_OFF_T(pos), SEEK_SET) == -1) { |
569 | q->setError(error: QFile::PositionError, str: QSystemError::stdString(errno)); |
570 | qWarning(msg: "QFile::at: Cannot set file position %lld" , pos); |
571 | return false; |
572 | } |
573 | } |
574 | return true; |
575 | } |
576 | |
577 | /*! |
578 | \reimp |
579 | */ |
580 | int QFSFileEngine::handle() const |
581 | { |
582 | Q_D(const QFSFileEngine); |
583 | return d->nativeHandle(); |
584 | } |
585 | |
586 | /*! |
587 | \reimp |
588 | */ |
589 | qint64 QFSFileEngine::read(char *data, qint64 maxlen) |
590 | { |
591 | Q_D(QFSFileEngine); |
592 | |
593 | // On Windows' stdlib implementation, the results of calling fread and |
594 | // fwrite are undefined if not called either in sequence, or if preceded |
595 | // with a call to fflush(). |
596 | if (d->lastIOCommand != QFSFileEnginePrivate::IOReadCommand) { |
597 | flush(); |
598 | d->lastIOCommand = QFSFileEnginePrivate::IOReadCommand; |
599 | } |
600 | |
601 | return d->nativeRead(data, maxlen); |
602 | } |
603 | |
604 | /*! |
605 | \internal |
606 | */ |
607 | qint64 QFSFileEnginePrivate::readFdFh(char *data, qint64 len) |
608 | { |
609 | Q_Q(QFSFileEngine); |
610 | |
611 | if (len < 0 || len != qint64(size_t(len))) { |
612 | q->setError(error: QFile::ReadError, str: QSystemError::stdString(EINVAL)); |
613 | return -1; |
614 | } |
615 | |
616 | qint64 readBytes = 0; |
617 | bool eof = false; |
618 | |
619 | if (fh) { |
620 | // Buffered stdlib mode. |
621 | |
622 | size_t result; |
623 | do { |
624 | result = fread(ptr: data + readBytes, size: 1, n: size_t(len - readBytes), stream: fh); |
625 | eof = feof(stream: fh); // Doesn't change errno |
626 | if (eof && result == 0) { |
627 | // On OS X, this is needed, e.g., if a file was written to |
628 | // through another stream since our last read. See test |
629 | // tst_QFile::appendAndRead |
630 | QT_FSEEK(stream: fh, QT_FTELL(stream: fh), SEEK_SET); // re-sync stream. |
631 | break; |
632 | } |
633 | readBytes += result; |
634 | } while (!eof && (result == 0 ? errno == EINTR : readBytes < len)); |
635 | |
636 | } else if (fd != -1) { |
637 | // Unbuffered stdio mode. |
638 | |
639 | SignedIOType result; |
640 | do { |
641 | // calculate the chunk size |
642 | // on Windows or 32-bit no-largefile Unix, we'll need to read in chunks |
643 | // we limit to the size of the signed type, otherwise we could get a negative number as a result |
644 | quint64 wantedBytes = quint64(len) - quint64(readBytes); |
645 | UnsignedIOType chunkSize = std::numeric_limits<SignedIOType>::max(); |
646 | if (chunkSize > wantedBytes) |
647 | chunkSize = wantedBytes; |
648 | result = QT_READ(fd, data: data + readBytes, maxlen: chunkSize); |
649 | } while (result > 0 && (readBytes += result) < len); |
650 | |
651 | // QT_READ (::read()) returns 0 to indicate end-of-file |
652 | eof = result == 0; |
653 | } |
654 | |
655 | if (!eof && readBytes == 0) { |
656 | readBytes = -1; |
657 | q->setError(error: QFile::ReadError, str: QSystemError::stdString(errno)); |
658 | } |
659 | |
660 | return readBytes; |
661 | } |
662 | |
663 | /*! |
664 | \reimp |
665 | */ |
666 | qint64 QFSFileEngine::readLine(char *data, qint64 maxlen) |
667 | { |
668 | Q_D(QFSFileEngine); |
669 | |
670 | // On Windows' stdlib implementation, the results of calling fread and |
671 | // fwrite are undefined if not called either in sequence, or if preceded |
672 | // with a call to fflush(). |
673 | if (d->lastIOCommand != QFSFileEnginePrivate::IOReadCommand) { |
674 | flush(); |
675 | d->lastIOCommand = QFSFileEnginePrivate::IOReadCommand; |
676 | } |
677 | |
678 | return d->nativeReadLine(data, maxlen); |
679 | } |
680 | |
681 | /*! |
682 | \internal |
683 | */ |
684 | qint64 QFSFileEnginePrivate::readLineFdFh(char *data, qint64 maxlen) |
685 | { |
686 | Q_Q(QFSFileEngine); |
687 | if (!fh) |
688 | return q->QAbstractFileEngine::readLine(data, maxlen); |
689 | |
690 | QT_OFF_T oldPos = 0; |
691 | #ifdef Q_OS_WIN |
692 | bool seq = q->isSequential(); |
693 | if (!seq) |
694 | #endif |
695 | oldPos = QT_FTELL(stream: fh); |
696 | |
697 | // QIODevice::readLine() passes maxlen - 1 to QFile::readLineData() |
698 | // because it has made space for the '\0' at the end of data. But fgets |
699 | // does the same, so we'd get two '\0' at the end - passing maxlen + 1 |
700 | // solves this. |
701 | if (!fgets(s: data, n: int(maxlen + 1), stream: fh)) { |
702 | if (!feof(stream: fh)) // Doesn't change errno |
703 | q->setError(error: QFile::ReadError, str: QSystemError::stdString(errno)); |
704 | return -1; // error |
705 | } |
706 | |
707 | #ifdef Q_OS_WIN |
708 | if (seq) |
709 | return qstrlen(data); |
710 | #endif |
711 | |
712 | qint64 lineLength = QT_FTELL(stream: fh) - oldPos; |
713 | return lineLength > 0 ? lineLength : qstrlen(str: data); |
714 | } |
715 | |
716 | /*! |
717 | \reimp |
718 | */ |
719 | qint64 QFSFileEngine::write(const char *data, qint64 len) |
720 | { |
721 | Q_D(QFSFileEngine); |
722 | d->metaData.clearFlags(flags: QFileSystemMetaData::Times); |
723 | |
724 | // On Windows' stdlib implementation, the results of calling fread and |
725 | // fwrite are undefined if not called either in sequence, or if preceded |
726 | // with a call to fflush(). |
727 | if (d->lastIOCommand != QFSFileEnginePrivate::IOWriteCommand) { |
728 | flush(); |
729 | d->lastIOCommand = QFSFileEnginePrivate::IOWriteCommand; |
730 | } |
731 | |
732 | return d->nativeWrite(data, len); |
733 | } |
734 | |
735 | /*! |
736 | \internal |
737 | */ |
738 | qint64 QFSFileEnginePrivate::writeFdFh(const char *data, qint64 len) |
739 | { |
740 | Q_Q(QFSFileEngine); |
741 | |
742 | if (len < 0 || len != qint64(size_t(len))) { |
743 | q->setError(error: QFile::WriteError, str: QSystemError::stdString(EINVAL)); |
744 | return -1; |
745 | } |
746 | |
747 | qint64 writtenBytes = 0; |
748 | |
749 | if (len) { // avoid passing nullptr to fwrite() or QT_WRITE() (UB) |
750 | |
751 | if (fh) { |
752 | // Buffered stdlib mode. |
753 | |
754 | size_t result; |
755 | do { |
756 | result = fwrite(ptr: data + writtenBytes, size: 1, n: size_t(len - writtenBytes), s: fh); |
757 | writtenBytes += result; |
758 | } while (result == 0 ? errno == EINTR : writtenBytes < len); |
759 | |
760 | } else if (fd != -1) { |
761 | // Unbuffered stdio mode. |
762 | |
763 | SignedIOType result; |
764 | do { |
765 | // calculate the chunk size |
766 | // on Windows or 32-bit no-largefile Unix, we'll need to read in chunks |
767 | // we limit to the size of the signed type, otherwise we could get a negative number as a result |
768 | quint64 wantedBytes = quint64(len) - quint64(writtenBytes); |
769 | UnsignedIOType chunkSize = std::numeric_limits<SignedIOType>::max(); |
770 | if (chunkSize > wantedBytes) |
771 | chunkSize = wantedBytes; |
772 | result = QT_WRITE(fd, data: data + writtenBytes, len: chunkSize); |
773 | } while (result > 0 && (writtenBytes += result) < len); |
774 | } |
775 | |
776 | } |
777 | |
778 | if (len && writtenBytes == 0) { |
779 | writtenBytes = -1; |
780 | q->setError(errno == ENOSPC ? QFile::ResourceError : QFile::WriteError, |
781 | str: QSystemError::stdString(errno)); |
782 | } else { |
783 | // reset the cached size, if any |
784 | metaData.clearFlags(flags: QFileSystemMetaData::SizeAttribute); |
785 | } |
786 | |
787 | return writtenBytes; |
788 | } |
789 | |
790 | #ifndef QT_NO_FILESYSTEMITERATOR |
791 | /*! |
792 | \internal |
793 | */ |
794 | QAbstractFileEngine::IteratorUniquePtr |
795 | QFSFileEngine::beginEntryList(const QString &path, QDirListing::IteratorFlags filters, |
796 | const QStringList &filterNames) |
797 | { |
798 | return std::make_unique<QFSFileEngineIterator>(args: path, args&: filters, args: filterNames); |
799 | } |
800 | #endif // QT_NO_FILESYSTEMITERATOR |
801 | |
802 | /*! |
803 | \reimp |
804 | */ |
805 | bool QFSFileEngine::isSequential() const |
806 | { |
807 | Q_D(const QFSFileEngine); |
808 | if (d->is_sequential == 0) |
809 | d->is_sequential = d->nativeIsSequential() ? 1 : 2; |
810 | return d->is_sequential == 1; |
811 | } |
812 | |
813 | /*! |
814 | \internal |
815 | */ |
816 | #ifdef Q_OS_UNIX |
817 | bool QFSFileEnginePrivate::isSequentialFdFh() const |
818 | { |
819 | if (doStat(flags: QFileSystemMetaData::SequentialType)) |
820 | return metaData.isSequential(); |
821 | return true; |
822 | } |
823 | #endif |
824 | |
825 | /*! |
826 | \reimp |
827 | */ |
828 | bool QFSFileEngine::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output) |
829 | { |
830 | Q_D(QFSFileEngine); |
831 | if (extension == AtEndExtension && d->fh && isSequential()) |
832 | return feof(stream: d->fh); |
833 | |
834 | if (extension == MapExtension) { |
835 | const MapExtensionOption *options = (const MapExtensionOption*)(option); |
836 | MapExtensionReturn *returnValue = static_cast<MapExtensionReturn*>(output); |
837 | returnValue->address = d->map(offset: options->offset, size: options->size, flags: options->flags); |
838 | return (returnValue->address != nullptr); |
839 | } |
840 | if (extension == UnMapExtension) { |
841 | const UnMapExtensionOption *options = (const UnMapExtensionOption*)option; |
842 | return d->unmap(ptr: options->address); |
843 | } |
844 | |
845 | return false; |
846 | } |
847 | |
848 | /*! |
849 | \reimp |
850 | */ |
851 | bool QFSFileEngine::supportsExtension(Extension extension) const |
852 | { |
853 | Q_D(const QFSFileEngine); |
854 | if (extension == AtEndExtension && d->fh && isSequential()) |
855 | return true; |
856 | if (extension == FastReadLineExtension && d->fh) |
857 | return true; |
858 | if (extension == FastReadLineExtension && d->fd != -1 && isSequential()) |
859 | return true; |
860 | if (extension == UnMapExtension || extension == MapExtension) |
861 | return true; |
862 | return false; |
863 | } |
864 | |
865 | /*! \fn QString QFSFileEngine::currentPath(const QString &fileName) |
866 | For Unix, returns the current working directory for the file |
867 | engine. |
868 | |
869 | For Windows, returns the canonicalized form of the current path used |
870 | by the file engine for the drive specified by \a fileName. On |
871 | Windows, each drive has its own current directory, so a different |
872 | path is returned for file names that include different drive names |
873 | (e.g. A: or C:). |
874 | |
875 | \sa setCurrentPath() |
876 | */ |
877 | |
878 | /*! \fn QFileInfoList QFSFileEngine::drives() |
879 | For Windows, returns the list of drives in the file system as a list |
880 | of QFileInfo objects. On Unix, only the root path is returned. |
881 | On Windows, this function returns all drives (A:\, C:\, D:\, and so on). |
882 | |
883 | For Unix, the list contains just the root path "/". |
884 | */ |
885 | |
886 | /*! \fn QString QFSFileEngine::fileName(QAbstractFileEngine::FileName file) const |
887 | \reimp |
888 | */ |
889 | |
890 | /*! \fn bool QFSFileEngine::setFileTime(const QDateTime &newDate, QFile::FileTime time) |
891 | \reimp |
892 | */ |
893 | |
894 | /*! |
895 | Returns the home path of the current user. |
896 | |
897 | \sa rootPath() |
898 | */ |
899 | QString QFSFileEngine::homePath() |
900 | { |
901 | return QFileSystemEngine::homePath(); |
902 | } |
903 | |
904 | /*! |
905 | Returns the root path. |
906 | |
907 | \sa homePath() |
908 | */ |
909 | QString QFSFileEngine::rootPath() |
910 | { |
911 | return QFileSystemEngine::rootPath(); |
912 | } |
913 | |
914 | /*! |
915 | Returns the temporary path (i.e., a path in which it is safe |
916 | to store temporary files). |
917 | */ |
918 | QString QFSFileEngine::tempPath() |
919 | { |
920 | return QFileSystemEngine::tempPath(); |
921 | } |
922 | |
923 | /*! \fn bool QFSFileEngine::isRelativePath() const |
924 | \reimp |
925 | */ |
926 | |
927 | /*! \fn bool QFSFileEngine::link(const QString &newName) |
928 | |
929 | Creates a link from the file currently specified by fileName() to |
930 | \a newName. What a link is depends on the underlying filesystem |
931 | (be it a shortcut on Windows or a symbolic link on Unix). Returns |
932 | \c true if successful; otherwise returns \c false. |
933 | |
934 | \note On Windows \a newName is expected to end with .lnk as the filename |
935 | extension. |
936 | */ |
937 | |
938 | |
939 | /*! \fn uint QFSFileEngine::ownerId(QAbstractFileEngine::FileOwner own) const |
940 | In Unix, if stat() is successful, the \c uid is returned if |
941 | \a own is the owner. Otherwise the \c gid is returned. If stat() |
942 | is unsuccessful, -2 is reuturned. |
943 | |
944 | For Windows, -2 is always returned. |
945 | */ |
946 | |
947 | /*! \fn QString QFSFileEngine::owner(QAbstractFileEngine::FileOwner own) const |
948 | \reimp |
949 | */ |
950 | |
951 | /*! |
952 | For Windows or Apple platforms, copy the file to file \a copyName. |
953 | |
954 | Not implemented for other Unix platforms. |
955 | */ |
956 | bool QFSFileEngine::copy(const QString ©Name) |
957 | { |
958 | Q_D(QFSFileEngine); |
959 | QSystemError error; |
960 | bool ret = QFileSystemEngine::copyFile(source: d->fileEntry, target: QFileSystemEntry(copyName), error); |
961 | if (!ret) |
962 | setError(error: QFile::CopyError, str: error.toString()); |
963 | return ret; |
964 | } |
965 | |
966 | /*! |
967 | \reimp |
968 | */ |
969 | bool QFSFileEngine::remove() |
970 | { |
971 | Q_D(QFSFileEngine); |
972 | QSystemError error; |
973 | bool ret = QFileSystemEngine::removeFile(entry: d->fileEntry, error); |
974 | d->metaData.clear(); |
975 | if (!ret) |
976 | setError(error: QFile::RemoveError, str: error.toString()); |
977 | return ret; |
978 | } |
979 | |
980 | /* |
981 | An alternative to setFileName() when you have already constructed |
982 | a QFileSystemEntry. |
983 | */ |
984 | void QFSFileEngine::setFileEntry(QFileSystemEntry &&entry) |
985 | { |
986 | Q_D(QFSFileEngine); |
987 | d->init(); |
988 | d->fileEntry = std::move(entry); |
989 | } |
990 | |
991 | bool QFSFileEngine::rename_helper(const QString &newName, RenameMode mode) |
992 | { |
993 | Q_D(QFSFileEngine); |
994 | |
995 | auto func = mode == Rename ? QFileSystemEngine::renameFile |
996 | : QFileSystemEngine::renameOverwriteFile; |
997 | QSystemError error; |
998 | auto newEntry = QFileSystemEntry(newName); |
999 | const bool ret = func(d->fileEntry, newEntry, error); |
1000 | if (!ret) { |
1001 | setError(error: QFile::RenameError, str: error.toString()); |
1002 | return false; |
1003 | } |
1004 | setFileEntry(std::move(newEntry)); |
1005 | return true; |
1006 | } |
1007 | |
1008 | /*! |
1009 | \reimp |
1010 | */ |
1011 | bool QFSFileEngine::mkdir(const QString &name, bool createParentDirectories, |
1012 | std::optional<QFile::Permissions> permissions) const |
1013 | { |
1014 | return QFileSystemEngine::createDirectory(entry: QFileSystemEntry(name), createParents: createParentDirectories, |
1015 | permissions); |
1016 | } |
1017 | |
1018 | /*! |
1019 | \reimp |
1020 | */ |
1021 | bool QFSFileEngine::rmdir(const QString &name, bool recurseParentDirectories) const |
1022 | { |
1023 | return QFileSystemEngine::removeDirectory(entry: QFileSystemEntry(name), removeEmptyParents: recurseParentDirectories); |
1024 | } |
1025 | |
1026 | |
1027 | /*! |
1028 | Sets the current path (e.g., for QDir), to \a path. Returns \c true if the |
1029 | new path exists; otherwise this function does nothing, and returns \c false. |
1030 | |
1031 | \sa currentPath() |
1032 | */ |
1033 | bool QFSFileEngine::setCurrentPath(const QString &path) |
1034 | { |
1035 | return QFileSystemEngine::setCurrentPath(QFileSystemEntry(path)); |
1036 | } |
1037 | |
1038 | /*! |
1039 | Returns whether the file system considers the file name to be |
1040 | case sensitive. |
1041 | */ |
1042 | bool QFSFileEngine::caseSensitive() const |
1043 | { |
1044 | Q_D(const QFSFileEngine); |
1045 | return QFileSystemEngine::isCaseSensitive(entry: d->fileEntry, data&: d->metaData); |
1046 | } |
1047 | |
1048 | /*! \fn bool QFSFileEngine::setPermissions(uint perms) |
1049 | \reimp |
1050 | */ |
1051 | |
1052 | /*! \fn bool QFSFileEngine::setSize(qint64 size) |
1053 | \reimp |
1054 | */ |
1055 | |
1056 | /*! \fn QAbstractFileEngine::FileFlags QFSFileEnginePrivate::getPermissions(QAbstractFileEngine::FileFlags type) const |
1057 | \internal |
1058 | */ |
1059 | |
1060 | QT_END_NAMESPACE |
1061 | |
1062 | #endif // QT_NO_FSFILEENGINE |
1063 | |