1/*
2 * Copyright (C) 2007 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#include "cutils/event_tag_map.h"
17#include "cutils/log.h"
18
19#include <stdlib.h>
20#include <string.h>
21#include <fcntl.h>
22#include <sys/mman.h>
23#include <errno.h>
24#include <assert.h>
25
26#define OUT_TAG "EventTagMap"
27
28/*
29 * Single entry.
30 */
31typedef struct EventTag {
32    unsigned int    tagIndex;
33    const char*     tagStr;
34} EventTag;
35
36/*
37 * Map.
38 */
39struct EventTagMap {
40    /* memory-mapped source file; we get strings from here */
41    void*           mapAddr;
42    size_t          mapLen;
43
44    /* array of event tags, sorted numerically by tag index */
45    EventTag*       tagArray;
46    int             numTags;
47};
48
49/* fwd */
50static int processFile(EventTagMap* map);
51static int countMapLines(const EventTagMap* map);
52static int parseMapLines(EventTagMap* map);
53static int scanTagLine(char** pData, EventTag* tag, int lineNum);
54static int sortTags(EventTagMap* map);
55static void dumpTags(const EventTagMap* map);
56
57
58/*
59 * Open the map file and allocate a structure to manage it.
60 *
61 * We create a private mapping because we want to terminate the log tag
62 * strings with '\0'.
63 */
64EventTagMap* android_openEventTagMap(const char* fileName)
65{
66    EventTagMap* newTagMap;
67    off_t end;
68    int fd = -1;
69
70    newTagMap = calloc(1, sizeof(EventTagMap));
71    if (newTagMap == NULL)
72        return NULL;
73
74    fd = open(fileName, O_RDONLY);
75    if (fd < 0) {
76        fprintf(stderr, "%s: unable to open map '%s': %s\n",
77            OUT_TAG, fileName, strerror(errno));
78        goto fail;
79    }
80
81    end = lseek(fd, 0L, SEEK_END);
82    (void) lseek(fd, 0L, SEEK_SET);
83    if (end < 0) {
84        fprintf(stderr, "%s: unable to seek map '%s'\n", OUT_TAG, fileName);
85        goto fail;
86    }
87
88    newTagMap->mapAddr = mmap(NULL, end, PROT_READ | PROT_WRITE, MAP_PRIVATE,
89                                fd, 0);
90    if (newTagMap->mapAddr == MAP_FAILED) {
91        fprintf(stderr, "%s: mmap(%s) failed: %s\n",
92            OUT_TAG, fileName, strerror(errno));
93        goto fail;
94    }
95    newTagMap->mapLen = end;
96
97    if (processFile(newTagMap) != 0)
98        goto fail;
99
100    return newTagMap;
101
102fail:
103    android_closeEventTagMap(newTagMap);
104    if (fd >= 0)
105        close(fd);
106    return NULL;
107}
108
109/*
110 * Close the map.
111 */
112void android_closeEventTagMap(EventTagMap* map)
113{
114    if (map == NULL)
115        return;
116
117    munmap(map->mapAddr, map->mapLen);
118    free(map);
119}
120
121/*
122 * Look up an entry in the map.
123 *
124 * The entries are sorted by tag number, so we can do a binary search.
125 */
126const char* android_lookupEventTag(const EventTagMap* map, int tag)
127{
128    int hi, lo, mid;
129
130    lo = 0;
131    hi = map->numTags-1;
132
133    while (lo <= hi) {
134        int cmp;
135
136        mid = (lo+hi)/2;
137        cmp = map->tagArray[mid].tagIndex - tag;
138        if (cmp < 0) {
139            /* tag is bigger */
140            lo = mid + 1;
141        } else if (cmp > 0) {
142            /* tag is smaller */
143            hi = mid - 1;
144        } else {
145            /* found */
146            return map->tagArray[mid].tagStr;
147        }
148    }
149
150    return NULL;
151}
152
153
154
155/*
156 * Determine whether "c" is a whitespace char.
157 */
158static inline int isCharWhitespace(char c)
159{
160    return (c == ' ' || c == '\n' || c == '\r' || c == '\t');
161}
162
163/*
164 * Determine whether "c" is a valid tag char.
165 */
166static inline int isCharValidTag(char c)
167{
168    return ((c >= 'A' && c <= 'Z') ||
169            (c >= 'a' && c <= 'z') ||
170            (c >= '0' && c <= '9') ||
171            (c == '_'));
172}
173
174/*
175 * Determine whether "c" is a valid decimal digit.
176 */
177static inline int isCharDigit(char c)
178{
179    return (c >= '0' && c <= '9');
180}
181
182
183/*
184 * Crunch through the file, parsing the contents and creating a tag index.
185 */
186static int processFile(EventTagMap* map)
187{
188    EventTag* tagArray = NULL;
189
190    /* get a tag count */
191    map->numTags = countMapLines(map);
192    if (map->numTags < 0)
193        return -1;
194
195    //printf("+++ found %d tags\n", map->numTags);
196
197    /* allocate storage for the tag index array */
198    map->tagArray = calloc(1, sizeof(EventTag) * map->numTags);
199    if (map->tagArray == NULL)
200        return -1;
201
202    /* parse the file, null-terminating tag strings */
203    if (parseMapLines(map) != 0) {
204        fprintf(stderr, "%s: file parse failed\n", OUT_TAG);
205        return -1;
206    }
207
208    /* sort the tags and check for duplicates */
209    if (sortTags(map) != 0)
210        return -1;
211
212    return 0;
213}
214
215/*
216 * Run through all lines in the file, determining whether they're blank,
217 * comments, or possibly have a tag entry.
218 *
219 * This is a very "loose" scan.  We don't try to detect syntax errors here.
220 * The later pass is more careful, but the number of tags found there must
221 * match the number of tags found here.
222 *
223 * Returns the number of potential tag entries found.
224 */
225static int countMapLines(const EventTagMap* map)
226{
227    int numTags, unknown;
228    const char* cp;
229    const char* endp;
230
231    cp = (const char*) map->mapAddr;
232    endp = cp + map->mapLen;
233
234    numTags = 0;
235    unknown = 1;
236    while (cp < endp) {
237        if (*cp == '\n') {
238            unknown = 1;
239        } else if (unknown) {
240            if (isCharDigit(*cp)) {
241                /* looks like a tag to me */
242                numTags++;
243                unknown = 0;
244            } else if (isCharWhitespace(*cp)) {
245                /* might be leading whitespace before tag num, keep going */
246            } else {
247                /* assume comment; second pass can complain in detail */
248                unknown = 0;
249            }
250        } else {
251            /* we've made up our mind; just scan to end of line */
252        }
253        cp++;
254    }
255
256    return numTags;
257}
258
259/*
260 * Parse the tags out of the file.
261 */
262static int parseMapLines(EventTagMap* map)
263{
264    int tagNum, lineStart, lineNum;
265    char* cp;
266    char* endp;
267
268    cp = (char*) map->mapAddr;
269    endp = cp + map->mapLen;
270
271    /* insist on EOL at EOF; simplifies parsing and null-termination */
272    if (*(endp-1) != '\n') {
273        fprintf(stderr, "%s: map file missing EOL on last line\n", OUT_TAG);
274        return -1;
275    }
276
277    tagNum = 0;
278    lineStart = 1;
279    lineNum = 1;
280    while (cp < endp) {
281        //printf("{%02x}", *cp); fflush(stdout);
282        if (*cp == '\n') {
283            lineStart = 1;
284            lineNum++;
285        } else if (lineStart) {
286            if (*cp == '#') {
287                /* comment; just scan to end */
288                lineStart = 0;
289            } else if (isCharDigit(*cp)) {
290                /* looks like a tag; scan it out */
291                if (tagNum >= map->numTags) {
292                    fprintf(stderr,
293                        "%s: more tags than expected (%d)\n", OUT_TAG, tagNum);
294                    return -1;
295                }
296                if (scanTagLine(&cp, &map->tagArray[tagNum], lineNum) != 0)
297                    return -1;
298                tagNum++;
299                lineNum++;      // we eat the '\n'
300                /* leave lineStart==1 */
301            } else if (isCharWhitespace(*cp)) {
302                /* looks like leading whitespace; keep scanning */
303            } else {
304                fprintf(stderr,
305                    "%s: unexpected chars (0x%02x) in tag number on line %d\n",
306                    OUT_TAG, *cp, lineNum);
307                return -1;
308            }
309        } else {
310            /* this is a blank or comment line */
311        }
312        cp++;
313    }
314
315    if (tagNum != map->numTags) {
316        fprintf(stderr, "%s: parsed %d tags, expected %d\n",
317            OUT_TAG, tagNum, map->numTags);
318        return -1;
319    }
320
321    return 0;
322}
323
324/*
325 * Scan one tag line.
326 *
327 * "*pData" should be pointing to the first digit in the tag number.  On
328 * successful return, it will be pointing to the last character in the
329 * tag line (i.e. the character before the start of the next line).
330 *
331 * Returns 0 on success, nonzero on failure.
332 */
333static int scanTagLine(char** pData, EventTag* tag, int lineNum)
334{
335    char* cp = *pData;
336    char* startp;
337    char* endp;
338    unsigned long val;
339
340    startp = cp;
341    while (isCharDigit(*++cp))
342        ;
343    *cp = '\0';
344
345    val = strtoul(startp, &endp, 10);
346    assert(endp == cp);
347    if (endp != cp)
348        fprintf(stderr, "ARRRRGH\n");
349
350    tag->tagIndex = val;
351
352    while (*++cp != '\n' && isCharWhitespace(*cp))
353        ;
354
355    if (*cp == '\n') {
356        fprintf(stderr,
357            "%s: missing tag string on line %d\n", OUT_TAG, lineNum);
358        return -1;
359    }
360
361    tag->tagStr = cp;
362
363    while (isCharValidTag(*++cp))
364        ;
365
366    if (*cp == '\n') {
367        /* null terminate and return */
368        *cp = '\0';
369    } else if (isCharWhitespace(*cp)) {
370        /* CRLF or trailin spaces; zap this char, then scan for the '\n' */
371        *cp = '\0';
372
373        /* just ignore the rest of the line till \n
374        TODO: read the tag description that follows the tag name
375        */
376        while (*++cp != '\n') {
377        }
378    } else {
379        fprintf(stderr,
380            "%s: invalid tag chars on line %d\n", OUT_TAG, lineNum);
381        return -1;
382    }
383
384    *pData = cp;
385
386    //printf("+++ Line %d: got %d '%s'\n", lineNum, tag->tagIndex, tag->tagStr);
387    return 0;
388}
389
390/*
391 * Compare two EventTags.
392 */
393static int compareEventTags(const void* v1, const void* v2)
394{
395    const EventTag* tag1 = (const EventTag*) v1;
396    const EventTag* tag2 = (const EventTag*) v2;
397
398    return tag1->tagIndex - tag2->tagIndex;
399}
400
401/*
402 * Sort the EventTag array so we can do fast lookups by tag index.  After
403 * the sort we do a quick check for duplicate tag indices.
404 *
405 * Returns 0 on success.
406 */
407static int sortTags(EventTagMap* map)
408{
409    int i;
410
411    qsort(map->tagArray, map->numTags, sizeof(EventTag), compareEventTags);
412
413    for (i = 1; i < map->numTags; i++) {
414        if (map->tagArray[i].tagIndex == map->tagArray[i-1].tagIndex) {
415            fprintf(stderr, "%s: duplicate tag entries (%d:%s and %d:%s)\n",
416                OUT_TAG,
417                map->tagArray[i].tagIndex, map->tagArray[i].tagStr,
418                map->tagArray[i-1].tagIndex, map->tagArray[i-1].tagStr);
419            return -1;
420        }
421    }
422
423    return 0;
424}
425
426/*
427 * Dump the tag array for debugging.
428 */
429static void dumpTags(const EventTagMap* map)
430{
431    int i;
432
433    for (i = 0; i < map->numTags; i++) {
434        const EventTag* tag = &map->tagArray[i];
435        printf("  %3d: %6d '%s'\n", i, tag->tagIndex, tag->tagStr);
436    }
437}
438
439