1 | /* |
2 | This file is part of the KDE libraries |
3 | SPDX-FileCopyrightText: 2008 Christian Ehrlicher <ch.ehrlicher@gmx.de> |
4 | |
5 | SPDX-License-Identifier: LGPL-2.0-or-later |
6 | */ |
7 | |
8 | #include "kmemfile_p.h" |
9 | |
10 | #ifndef QT_NO_SHAREDMEMORY |
11 | |
12 | #include <QCoreApplication> |
13 | #include <QCryptographicHash> |
14 | #include <QDir> |
15 | #include <QFile> |
16 | #include <QSharedMemory> |
17 | |
18 | class KMemFile::Private |
19 | { |
20 | public: |
21 | struct sharedInfoData { |
22 | int shmCounter; |
23 | qint64 shmDataSize; |
24 | |
25 | sharedInfoData() |
26 | { |
27 | memset(s: this, c: 0, n: sizeof(*this)); |
28 | } |
29 | }; |
30 | Private(KMemFile *_parent) |
31 | : readWritePos(0) |
32 | , shmDataSize(0) |
33 | , parent(_parent) |
34 | { |
35 | } |
36 | |
37 | QString getShmKey(int iCounter = -1); |
38 | static QString getShmKey(const QString &filename, int iCounter = -1); |
39 | bool loadContentsFromFile(); |
40 | void close(); |
41 | |
42 | QString filename; |
43 | QSharedMemory shmInfo; |
44 | QSharedMemory shmData; |
45 | qint64 readWritePos; |
46 | qint64 shmDataSize; |
47 | |
48 | KMemFile *parent; |
49 | }; |
50 | |
51 | QString KMemFile::Private::getShmKey(int iCounter) |
52 | { |
53 | return getShmKey(filename, iCounter); |
54 | } |
55 | |
56 | QString KMemFile::Private::getShmKey(const QString &filename, int iCounter) |
57 | { |
58 | QByteArray tmp = QString(QDir(filename).canonicalPath() + QString::number(iCounter)).toUtf8(); |
59 | return QString::fromLatin1(ba: QCryptographicHash::hash(data: tmp, method: QCryptographicHash::Sha1)); |
60 | } |
61 | |
62 | bool KMemFile::Private::loadContentsFromFile() |
63 | { |
64 | QFile f(filename); |
65 | if (!f.exists()) { |
66 | close(); |
67 | parent->setErrorString(QCoreApplication::translate(context: "" , key: "File %1 does not exist" ).arg(a: filename)); |
68 | return false; |
69 | } |
70 | if (!f.open(flags: QIODevice::ReadOnly)) { |
71 | close(); |
72 | parent->setErrorString(QCoreApplication::translate(context: "" , key: "Cannot open %1 for reading" ).arg(a: filename)); |
73 | return false; |
74 | } |
75 | |
76 | sharedInfoData *infoPtr = static_cast<sharedInfoData *>(shmInfo.data()); |
77 | |
78 | infoPtr->shmDataSize = f.size(); |
79 | shmData.setKey(getShmKey(iCounter: infoPtr->shmCounter)); |
80 | if (!shmData.create(size: infoPtr->shmDataSize)) { |
81 | close(); |
82 | parent->setErrorString(QCoreApplication::translate(context: "" , key: "Cannot create memory segment for file %1" ).arg(a: filename)); |
83 | return false; |
84 | } |
85 | shmData.lock(); |
86 | qint64 size = 0; |
87 | qint64 bytesRead; |
88 | char *data = static_cast<char *>(shmData.data()); |
89 | bytesRead = f.read(data, maxlen: infoPtr->shmDataSize); |
90 | if (bytesRead != infoPtr->shmDataSize) { |
91 | close(); |
92 | parent->setErrorString(QCoreApplication::translate(context: "" , key: "Could not read data from %1 into shm" ).arg(a: filename)); |
93 | return false; |
94 | } |
95 | shmDataSize = size; |
96 | shmData.unlock(); |
97 | return true; |
98 | } |
99 | |
100 | void KMemFile::Private::close() |
101 | { |
102 | shmData.unlock(); |
103 | shmData.detach(); |
104 | shmInfo.unlock(); |
105 | shmInfo.detach(); |
106 | readWritePos = 0; |
107 | shmDataSize = 0; |
108 | } |
109 | |
110 | KMemFile::KMemFile(const QString &filename, QObject *parent) |
111 | : QIODevice(parent) |
112 | , d(new Private(this)) |
113 | { |
114 | d->filename = filename; |
115 | } |
116 | |
117 | KMemFile::~KMemFile() |
118 | { |
119 | close(); |
120 | } |
121 | |
122 | void KMemFile::close() |
123 | { |
124 | QIODevice::close(); |
125 | if (!isOpen()) { |
126 | return; |
127 | } |
128 | d->close(); |
129 | } |
130 | |
131 | bool KMemFile::isSequential() const |
132 | { |
133 | return false; |
134 | } |
135 | |
136 | bool KMemFile::open(OpenMode mode) |
137 | { |
138 | if (isOpen()) { |
139 | QIODevice::open(mode); |
140 | return false; |
141 | } |
142 | |
143 | if (mode != QIODevice::ReadOnly) { |
144 | setErrorString(QCoreApplication::translate(context: "" , key: "Only 'ReadOnly' allowed" )); |
145 | return false; |
146 | } |
147 | |
148 | if (!QFile::exists(fileName: d->filename)) { |
149 | setErrorString(QCoreApplication::translate(context: "" , key: "File %1 does not exist" ).arg(a: d->filename)); |
150 | return false; |
151 | } |
152 | |
153 | QSharedMemory lock(QDir(d->filename).canonicalPath()); |
154 | lock.lock(); |
155 | |
156 | Private::sharedInfoData *infoPtr; |
157 | d->shmInfo.setKey(d->getShmKey()); |
158 | // see if it's already in memory |
159 | if (!d->shmInfo.attach(mode: QSharedMemory::ReadWrite)) { |
160 | if (!d->shmInfo.create(size: sizeof(Private::sharedInfoData))) { |
161 | lock.unlock(); |
162 | setErrorString(QCoreApplication::translate(context: "" , key: "Cannot create memory segment for file %1" ).arg(a: d->filename)); |
163 | return false; |
164 | } |
165 | d->shmInfo.lock(); |
166 | // no -> create it |
167 | infoPtr = static_cast<Private::sharedInfoData *>(d->shmInfo.data()); |
168 | memset(s: infoPtr, c: 0, n: sizeof(Private::sharedInfoData)); |
169 | infoPtr->shmCounter = 1; |
170 | if (!d->loadContentsFromFile()) { |
171 | d->shmInfo.unlock(); |
172 | d->shmInfo.detach(); |
173 | lock.unlock(); |
174 | return false; |
175 | } |
176 | } else { |
177 | d->shmInfo.lock(); |
178 | infoPtr = static_cast<Private::sharedInfoData *>(d->shmInfo.data()); |
179 | d->shmData.setKey(d->getShmKey(iCounter: infoPtr->shmCounter)); |
180 | if (!d->shmData.attach(mode: QSharedMemory::ReadOnly)) { |
181 | if (!d->loadContentsFromFile()) { |
182 | d->shmInfo.unlock(); |
183 | d->shmInfo.detach(); |
184 | lock.unlock(); |
185 | return false; |
186 | } |
187 | } |
188 | } |
189 | d->shmDataSize = infoPtr->shmDataSize; |
190 | d->shmInfo.unlock(); |
191 | lock.unlock(); |
192 | |
193 | setOpenMode(mode); |
194 | return true; |
195 | } |
196 | |
197 | bool KMemFile::seek(qint64 pos) |
198 | { |
199 | if (d->shmDataSize < pos) { |
200 | setErrorString(QCoreApplication::translate(context: "" , key: "Cannot seek past eof" )); |
201 | return false; |
202 | } |
203 | d->readWritePos = pos; |
204 | QIODevice::seek(pos); |
205 | return true; |
206 | } |
207 | |
208 | qint64 KMemFile::size() const |
209 | { |
210 | return d->shmDataSize; |
211 | } |
212 | |
213 | qint64 KMemFile::readData(char *data, qint64 maxSize) |
214 | { |
215 | if ((openMode() & QIODevice::ReadOnly) == 0) { |
216 | return -1; |
217 | } |
218 | |
219 | qint64 maxRead = size() - d->readWritePos; |
220 | qint64 bytesToRead = qMin(a: maxRead, b: maxSize); |
221 | const char *src = static_cast<const char *>(d->shmData.data()); |
222 | memcpy(dest: data, src: &src[d->readWritePos], n: bytesToRead); |
223 | d->readWritePos += bytesToRead; |
224 | return bytesToRead; |
225 | } |
226 | |
227 | qint64 KMemFile::writeData(const char *, qint64) |
228 | { |
229 | return -1; |
230 | } |
231 | |
232 | void KMemFile::fileContentsChanged(const QString &filename) |
233 | { |
234 | QSharedMemory lock(QDir(filename).canonicalPath()); |
235 | lock.lock(); |
236 | |
237 | QSharedMemory shmData(Private::getShmKey(filename)); |
238 | if (!shmData.attach()) { |
239 | return; |
240 | } |
241 | shmData.lock(); |
242 | Private::sharedInfoData *infoPtr = static_cast<Private::sharedInfoData *>(shmData.data()); |
243 | infoPtr->shmCounter++; |
244 | infoPtr->shmDataSize = 0; |
245 | shmData.unlock(); |
246 | } |
247 | |
248 | #endif // QT_NO_SHAREDMEMORY |
249 | |
250 | #include "moc_kmemfile_p.cpp" |
251 | |