1/*
2 ** Copyright 2011, 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 "egl_cache.h"
18
19#include "../egl_impl.h"
20
21#include "egl_display.h"
22
23
24#include <private/EGL/cache.h>
25
26#include <inttypes.h>
27#include <sys/mman.h>
28#include <sys/stat.h>
29#include <unistd.h>
30
31#include <thread>
32
33#include <log/log.h>
34
35// Cache size limits.
36static const size_t maxKeySize = 12 * 1024;
37static const size_t maxValueSize = 64 * 1024;
38static const size_t maxTotalSize = 2 * 1024 * 1024;
39
40// Cache file header
41static const char* cacheFileMagic = "EGL$";
42static const size_t cacheFileHeaderSize = 8;
43
44// The time in seconds to wait before saving newly inserted cache entries.
45static const unsigned int deferredSaveDelay = 4;
46
47// ----------------------------------------------------------------------------
48namespace android {
49// ----------------------------------------------------------------------------
50
51#define BC_EXT_STR "EGL_ANDROID_blob_cache"
52
53// called from android_view_ThreadedRenderer.cpp
54void egl_set_cache_filename(const char* filename) {
55    egl_cache_t::get()->setCacheFilename(filename);
56}
57
58//
59// Callback functions passed to EGL.
60//
61static void setBlob(const void* key, EGLsizeiANDROID keySize,
62        const void* value, EGLsizeiANDROID valueSize) {
63    egl_cache_t::get()->setBlob(key, keySize, value, valueSize);
64}
65
66static EGLsizeiANDROID getBlob(const void* key, EGLsizeiANDROID keySize,
67        void* value, EGLsizeiANDROID valueSize) {
68    return egl_cache_t::get()->getBlob(key, keySize, value, valueSize);
69}
70
71//
72// egl_cache_t definition
73//
74egl_cache_t::egl_cache_t() :
75        mInitialized(false) {
76}
77
78egl_cache_t::~egl_cache_t() {
79}
80
81egl_cache_t egl_cache_t::sCache;
82
83egl_cache_t* egl_cache_t::get() {
84    return &sCache;
85}
86
87void egl_cache_t::initialize(egl_display_t *display) {
88    std::lock_guard<std::mutex> lock(mMutex);
89
90    egl_connection_t* const cnx = &gEGLImpl;
91    if (cnx->dso && cnx->major >= 0 && cnx->minor >= 0) {
92        const char* exts = display->disp.queryString.extensions;
93        size_t bcExtLen = strlen(BC_EXT_STR);
94        size_t extsLen = strlen(exts);
95        bool equal = !strcmp(BC_EXT_STR, exts);
96        bool atStart = !strncmp(BC_EXT_STR " ", exts, bcExtLen+1);
97        bool atEnd = (bcExtLen+1) < extsLen &&
98                !strcmp(" " BC_EXT_STR, exts + extsLen - (bcExtLen+1));
99        bool inMiddle = strstr(exts, " " BC_EXT_STR " ") != nullptr;
100        if (equal || atStart || atEnd || inMiddle) {
101            PFNEGLSETBLOBCACHEFUNCSANDROIDPROC eglSetBlobCacheFuncsANDROID;
102            eglSetBlobCacheFuncsANDROID =
103                    reinterpret_cast<PFNEGLSETBLOBCACHEFUNCSANDROIDPROC>(
104                            cnx->egl.eglGetProcAddress(
105                                    "eglSetBlobCacheFuncsANDROID"));
106            if (eglSetBlobCacheFuncsANDROID == NULL) {
107                ALOGE("EGL_ANDROID_blob_cache advertised, "
108                        "but unable to get eglSetBlobCacheFuncsANDROID");
109                return;
110            }
111
112            eglSetBlobCacheFuncsANDROID(display->disp.dpy,
113                    android::setBlob, android::getBlob);
114            EGLint err = cnx->egl.eglGetError();
115            if (err != EGL_SUCCESS) {
116                ALOGE("eglSetBlobCacheFuncsANDROID resulted in an error: "
117                        "%#x", err);
118            }
119        }
120    }
121
122    mInitialized = true;
123}
124
125void egl_cache_t::terminate() {
126    std::lock_guard<std::mutex> lock(mMutex);
127    saveBlobCacheLocked();
128    mBlobCache = NULL;
129}
130
131void egl_cache_t::setBlob(const void* key, EGLsizeiANDROID keySize,
132        const void* value, EGLsizeiANDROID valueSize) {
133    std::lock_guard<std::mutex> lock(mMutex);
134
135    if (keySize < 0 || valueSize < 0) {
136        ALOGW("EGL_ANDROID_blob_cache set: negative sizes are not allowed");
137        return;
138    }
139
140    if (mInitialized) {
141        BlobCache* bc = getBlobCacheLocked();
142        bc->set(key, keySize, value, valueSize);
143
144        if (!mSavePending) {
145            mSavePending = true;
146            std::thread deferredSaveThread([this]() {
147                sleep(deferredSaveDelay);
148                std::lock_guard<std::mutex> lock(mMutex);
149                if (mInitialized) {
150                    saveBlobCacheLocked();
151                }
152                mSavePending = false;
153            });
154            deferredSaveThread.detach();
155        }
156    }
157}
158
159EGLsizeiANDROID egl_cache_t::getBlob(const void* key, EGLsizeiANDROID keySize,
160        void* value, EGLsizeiANDROID valueSize) {
161    std::lock_guard<std::mutex> lock(mMutex);
162
163    if (keySize < 0 || valueSize < 0) {
164        ALOGW("EGL_ANDROID_blob_cache set: negative sizes are not allowed");
165        return 0;
166    }
167
168    if (mInitialized) {
169        BlobCache* bc = getBlobCacheLocked();
170        return bc->get(key, keySize, value, valueSize);
171    }
172    return 0;
173}
174
175void egl_cache_t::setCacheFilename(const char* filename) {
176    std::lock_guard<std::mutex> lock(mMutex);
177    mFilename = filename;
178}
179
180BlobCache* egl_cache_t::getBlobCacheLocked() {
181    if (mBlobCache == nullptr) {
182        mBlobCache.reset(new BlobCache(maxKeySize, maxValueSize, maxTotalSize));
183        loadBlobCacheLocked();
184    }
185    return mBlobCache.get();
186}
187
188static uint32_t crc32c(const uint8_t* buf, size_t len) {
189    const uint32_t polyBits = 0x82F63B78;
190    uint32_t r = 0;
191    for (size_t i = 0; i < len; i++) {
192        r ^= buf[i];
193        for (int j = 0; j < 8; j++) {
194            if (r & 1) {
195                r = (r >> 1) ^ polyBits;
196            } else {
197                r >>= 1;
198            }
199        }
200    }
201    return r;
202}
203
204void egl_cache_t::saveBlobCacheLocked() {
205    if (mFilename.length() > 0 && mBlobCache != NULL) {
206        size_t cacheSize = mBlobCache->getFlattenedSize();
207        size_t headerSize = cacheFileHeaderSize;
208        const char* fname = mFilename.c_str();
209
210        // Try to create the file with no permissions so we can write it
211        // without anyone trying to read it.
212        int fd = open(fname, O_CREAT | O_EXCL | O_RDWR, 0);
213        if (fd == -1) {
214            if (errno == EEXIST) {
215                // The file exists, delete it and try again.
216                if (unlink(fname) == -1) {
217                    // No point in retrying if the unlink failed.
218                    ALOGE("error unlinking cache file %s: %s (%d)", fname,
219                            strerror(errno), errno);
220                    return;
221                }
222                // Retry now that we've unlinked the file.
223                fd = open(fname, O_CREAT | O_EXCL | O_RDWR, 0);
224            }
225            if (fd == -1) {
226                ALOGE("error creating cache file %s: %s (%d)", fname,
227                        strerror(errno), errno);
228                return;
229            }
230        }
231
232        size_t fileSize = headerSize + cacheSize;
233
234        uint8_t* buf = new uint8_t [fileSize];
235        if (!buf) {
236            ALOGE("error allocating buffer for cache contents: %s (%d)",
237                    strerror(errno), errno);
238            close(fd);
239            unlink(fname);
240            return;
241        }
242
243        int err = mBlobCache->flatten(buf + headerSize, cacheSize);
244        if (err < 0) {
245            ALOGE("error writing cache contents: %s (%d)", strerror(-err),
246                    -err);
247            delete [] buf;
248            close(fd);
249            unlink(fname);
250            return;
251        }
252
253        // Write the file magic and CRC
254        memcpy(buf, cacheFileMagic, 4);
255        uint32_t* crc = reinterpret_cast<uint32_t*>(buf + 4);
256        *crc = crc32c(buf + headerSize, cacheSize);
257
258        if (write(fd, buf, fileSize) == -1) {
259            ALOGE("error writing cache file: %s (%d)", strerror(errno),
260                    errno);
261            delete [] buf;
262            close(fd);
263            unlink(fname);
264            return;
265        }
266
267        delete [] buf;
268        fchmod(fd, S_IRUSR);
269        close(fd);
270    }
271}
272
273void egl_cache_t::loadBlobCacheLocked() {
274    if (mFilename.length() > 0) {
275        size_t headerSize = cacheFileHeaderSize;
276
277        int fd = open(mFilename.c_str(), O_RDONLY, 0);
278        if (fd == -1) {
279            if (errno != ENOENT) {
280                ALOGE("error opening cache file %s: %s (%d)", mFilename.c_str(),
281                        strerror(errno), errno);
282            }
283            return;
284        }
285
286        struct stat statBuf;
287        if (fstat(fd, &statBuf) == -1) {
288            ALOGE("error stat'ing cache file: %s (%d)", strerror(errno), errno);
289            close(fd);
290            return;
291        }
292
293        // Sanity check the size before trying to mmap it.
294        size_t fileSize = statBuf.st_size;
295        if (fileSize > maxTotalSize * 2) {
296            ALOGE("cache file is too large: %#" PRIx64,
297                  static_cast<off64_t>(statBuf.st_size));
298            close(fd);
299            return;
300        }
301
302        uint8_t* buf = reinterpret_cast<uint8_t*>(mmap(NULL, fileSize,
303                PROT_READ, MAP_PRIVATE, fd, 0));
304        if (buf == MAP_FAILED) {
305            ALOGE("error mmaping cache file: %s (%d)", strerror(errno),
306                    errno);
307            close(fd);
308            return;
309        }
310
311        // Check the file magic and CRC
312        size_t cacheSize = fileSize - headerSize;
313        if (memcmp(buf, cacheFileMagic, 4) != 0) {
314            ALOGE("cache file has bad mojo");
315            close(fd);
316            return;
317        }
318        uint32_t* crc = reinterpret_cast<uint32_t*>(buf + 4);
319        if (crc32c(buf + headerSize, cacheSize) != *crc) {
320            ALOGE("cache file failed CRC check");
321            close(fd);
322            return;
323        }
324
325        int err = mBlobCache->unflatten(buf + headerSize, cacheSize);
326        if (err < 0) {
327            ALOGE("error reading cache contents: %s (%d)", strerror(-err),
328                    -err);
329            munmap(buf, fileSize);
330            close(fd);
331            return;
332        }
333
334        munmap(buf, fileSize);
335        close(fd);
336    }
337}
338
339// ----------------------------------------------------------------------------
340}; // namespace android
341// ----------------------------------------------------------------------------
342