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