Home | History | Annotate | Download | only in liblog
      1 /*
      2  * Copyright (C) 2007-2016 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 <assert.h>
     18 #include <ctype.h>
     19 #include <errno.h>
     20 #include <fcntl.h>
     21 #include <inttypes.h>
     22 #include <limits.h>
     23 #include <stdio.h>
     24 #include <stdlib.h>
     25 #include <string.h>
     26 #include <sys/mman.h>
     27 
     28 #include <functional>
     29 #include <string>
     30 #include <string_view>
     31 #include <unordered_map>
     32 
     33 #include <log/event_tag_map.h>
     34 #include <log/log_properties.h>
     35 #include <private/android_logger.h>
     36 #include <utils/FastStrcmp.h>
     37 #include <utils/RWLock.h>
     38 
     39 #include "log_portability.h"
     40 #include "logd_reader.h"
     41 
     42 #define OUT_TAG "EventTagMap"
     43 
     44 class MapString {
     45  private:
     46   const std::string* alloc;                  // HAS-AN
     47   const std::string_view str;                // HAS-A
     48 
     49  public:
     50   operator const std::string_view() const {
     51     return str;
     52   }
     53 
     54   const char* data() const {
     55     return str.data();
     56   }
     57   size_t length() const {
     58     return str.length();
     59   }
     60 
     61   bool operator==(const MapString& rval) const {
     62     if (length() != rval.length()) return false;
     63     if (length() == 0) return true;
     64     return fastcmp<strncmp>(data(), rval.data(), length()) == 0;
     65   }
     66   bool operator!=(const MapString& rval) const {
     67     return !(*this == rval);
     68   }
     69 
     70   MapString(const char* str, size_t len) : alloc(NULL), str(str, len) {
     71   }
     72   explicit MapString(const std::string& str)
     73       : alloc(new std::string(str)), str(alloc->data(), alloc->length()) {
     74   }
     75   MapString(MapString&& rval)
     76       : alloc(rval.alloc), str(rval.data(), rval.length()) {
     77     rval.alloc = NULL;
     78   }
     79   explicit MapString(const MapString& rval)
     80       : alloc(rval.alloc ? new std::string(*rval.alloc) : NULL),
     81         str(alloc ? alloc->data() : rval.data(), rval.length()) {
     82   }
     83 
     84   ~MapString() {
     85     if (alloc) delete alloc;
     86   }
     87 };
     88 
     89 // Hash for MapString
     90 template <>
     91 struct std::hash<MapString>
     92     : public std::unary_function<const MapString&, size_t> {
     93   size_t operator()(const MapString& __t) const noexcept {
     94     if (!__t.length()) return 0;
     95     return std::hash<std::string_view>()(std::string_view(__t));
     96   }
     97 };
     98 
     99 typedef std::pair<MapString, MapString> TagFmt;
    100 
    101 template <>
    102 struct std::hash<TagFmt> : public std::unary_function<const TagFmt&, size_t> {
    103   size_t operator()(const TagFmt& __t) const noexcept {
    104     // Tag is typically unique.  Will cost us an extra 100ns for the
    105     // unordered_map lookup if we instead did a hash that combined
    106     // both of tag and fmt members, e.g.:
    107     //
    108     // return std::hash<MapString>()(__t.first) ^
    109     //        std::hash<MapString>()(__t.second);
    110     return std::hash<MapString>()(__t.first);
    111   }
    112 };
    113 
    114 // Map
    115 struct EventTagMap {
    116 #define NUM_MAPS 2
    117   // memory-mapped source file; we get strings from here
    118   void* mapAddr[NUM_MAPS];
    119   size_t mapLen[NUM_MAPS];
    120 
    121  private:
    122   std::unordered_map<uint32_t, TagFmt> Idx2TagFmt;
    123   std::unordered_map<TagFmt, uint32_t> TagFmt2Idx;
    124   std::unordered_map<MapString, uint32_t> Tag2Idx;
    125   // protect unordered sets
    126   android::RWLock rwlock;
    127 
    128  public:
    129   EventTagMap() {
    130     memset(mapAddr, 0, sizeof(mapAddr));
    131     memset(mapLen, 0, sizeof(mapLen));
    132   }
    133 
    134   ~EventTagMap() {
    135     Idx2TagFmt.clear();
    136     TagFmt2Idx.clear();
    137     Tag2Idx.clear();
    138     for (size_t which = 0; which < NUM_MAPS; ++which) {
    139       if (mapAddr[which]) {
    140         munmap(mapAddr[which], mapLen[which]);
    141         mapAddr[which] = 0;
    142       }
    143     }
    144   }
    145 
    146   bool emplaceUnique(uint32_t tag, const TagFmt& tagfmt, bool verbose = false);
    147   const TagFmt* find(uint32_t tag) const;
    148   int find(TagFmt&& tagfmt) const;
    149   int find(MapString&& tag) const;
    150 };
    151 
    152 bool EventTagMap::emplaceUnique(uint32_t tag, const TagFmt& tagfmt,
    153                                 bool verbose) {
    154   bool ret = true;
    155   static const char errorFormat[] =
    156       OUT_TAG ": duplicate tag entries %" PRIu32 ":%.*s:%.*s and %" PRIu32
    157               ":%.*s:%.*s)\n";
    158   android::RWLock::AutoWLock writeLock(rwlock);
    159   {
    160     std::unordered_map<uint32_t, TagFmt>::const_iterator it;
    161     it = Idx2TagFmt.find(tag);
    162     if (it != Idx2TagFmt.end()) {
    163       if (verbose) {
    164         fprintf(stderr, errorFormat, it->first, (int)it->second.first.length(),
    165                 it->second.first.data(), (int)it->second.second.length(),
    166                 it->second.second.data(), tag, (int)tagfmt.first.length(),
    167                 tagfmt.first.data(), (int)tagfmt.second.length(),
    168                 tagfmt.second.data());
    169       }
    170       ret = false;
    171     } else {
    172       Idx2TagFmt.emplace(std::make_pair(tag, tagfmt));
    173     }
    174   }
    175 
    176   {
    177     std::unordered_map<TagFmt, uint32_t>::const_iterator it;
    178     it = TagFmt2Idx.find(tagfmt);
    179     if (it != TagFmt2Idx.end()) {
    180       if (verbose) {
    181         fprintf(stderr, errorFormat, it->second, (int)it->first.first.length(),
    182                 it->first.first.data(), (int)it->first.second.length(),
    183                 it->first.second.data(), tag, (int)tagfmt.first.length(),
    184                 tagfmt.first.data(), (int)tagfmt.second.length(),
    185                 tagfmt.second.data());
    186       }
    187       ret = false;
    188     } else {
    189       TagFmt2Idx.emplace(std::make_pair(tagfmt, tag));
    190     }
    191   }
    192 
    193   {
    194     std::unordered_map<MapString, uint32_t>::const_iterator it;
    195     it = Tag2Idx.find(tagfmt.first);
    196     if (!tagfmt.second.length() && (it != Tag2Idx.end())) {
    197       Tag2Idx.erase(it);
    198       it = Tag2Idx.end();
    199     }
    200     if (it == Tag2Idx.end()) {
    201       Tag2Idx.emplace(std::make_pair(tagfmt.first, tag));
    202     }
    203   }
    204 
    205   return ret;
    206 }
    207 
    208 const TagFmt* EventTagMap::find(uint32_t tag) const {
    209   std::unordered_map<uint32_t, TagFmt>::const_iterator it;
    210   android::RWLock::AutoRLock readLock(const_cast<android::RWLock&>(rwlock));
    211   it = Idx2TagFmt.find(tag);
    212   if (it == Idx2TagFmt.end()) return NULL;
    213   return &(it->second);
    214 }
    215 
    216 int EventTagMap::find(TagFmt&& tagfmt) const {
    217   std::unordered_map<TagFmt, uint32_t>::const_iterator it;
    218   android::RWLock::AutoRLock readLock(const_cast<android::RWLock&>(rwlock));
    219   it = TagFmt2Idx.find(std::move(tagfmt));
    220   if (it == TagFmt2Idx.end()) return -1;
    221   return it->second;
    222 }
    223 
    224 int EventTagMap::find(MapString&& tag) const {
    225   std::unordered_map<MapString, uint32_t>::const_iterator it;
    226   android::RWLock::AutoRLock readLock(const_cast<android::RWLock&>(rwlock));
    227   it = Tag2Idx.find(std::move(tag));
    228   if (it == Tag2Idx.end()) return -1;
    229   return it->second;
    230 }
    231 
    232 // The position after the end of a valid section of the tag string,
    233 // caller makes sure delimited appropriately.
    234 static const char* endOfTag(const char* cp) {
    235   while (*cp && (isalnum(*cp) || strchr("_.-@,", *cp))) ++cp;
    236   return cp;
    237 }
    238 
    239 // Scan one tag line.
    240 //
    241 // "pData" should be pointing to the first digit in the tag number.  On
    242 // successful return, it will be pointing to the last character in the
    243 // tag line (i.e. the character before the start of the next line).
    244 //
    245 // lineNum = 0 removes verbose comments and requires us to cache the
    246 // content rather than make direct raw references since the content
    247 // will disappear after the call. A non-zero lineNum means we own the
    248 // data and it will outlive the call.
    249 //
    250 // Returns 0 on success, nonzero on failure.
    251 static int scanTagLine(EventTagMap* map, const char*& pData, int lineNum) {
    252   char* ep;
    253   unsigned long val = strtoul(pData, &ep, 10);
    254   const char* cp = ep;
    255   if (cp == pData) {
    256     if (lineNum) {
    257       fprintf(stderr, OUT_TAG ": malformed tag number on line %d\n", lineNum);
    258     }
    259     errno = EINVAL;
    260     return -1;
    261   }
    262 
    263   uint32_t tagIndex = val;
    264   if (tagIndex != val) {
    265     if (lineNum) {
    266       fprintf(stderr, OUT_TAG ": tag number too large on line %d\n", lineNum);
    267     }
    268     errno = ERANGE;
    269     return -1;
    270   }
    271 
    272   while ((*++cp != '\n') && isspace(*cp)) {
    273   }
    274 
    275   if (*cp == '\n') {
    276     if (lineNum) {
    277       fprintf(stderr, OUT_TAG ": missing tag string on line %d\n", lineNum);
    278     }
    279     errno = EINVAL;
    280     return -1;
    281   }
    282 
    283   const char* tag = cp;
    284   cp = endOfTag(cp);
    285   size_t tagLen = cp - tag;
    286 
    287   if (!isspace(*cp)) {
    288     if (lineNum) {
    289       fprintf(stderr, OUT_TAG ": invalid tag char %c on line %d\n", *cp,
    290               lineNum);
    291     }
    292     errno = EINVAL;
    293     return -1;
    294   }
    295 
    296   while (isspace(*cp) && (*cp != '\n')) ++cp;
    297   const char* fmt = NULL;
    298   size_t fmtLen = 0;
    299   if (*cp && (*cp != '#')) {
    300     fmt = cp;
    301     while (*cp && (*cp != '\n') && (*cp != '#')) ++cp;
    302     while ((cp > fmt) && isspace(*(cp - 1))) --cp;
    303     fmtLen = cp - fmt;
    304   }
    305 
    306   // KISS Only report identicals if they are global
    307   // Ideally we want to check if there are identicals
    308   // recorded for the same uid, but recording that
    309   // unused detail in our database is too burdensome.
    310   bool verbose = true;
    311   while (*cp && (*cp != '#') && (*cp != '\n')) ++cp;
    312   if (*cp == '#') {
    313     do {
    314       ++cp;
    315     } while (isspace(*cp) && (*cp != '\n'));
    316     verbose = !!fastcmp<strncmp>(cp, "uid=", strlen("uid="));
    317   }
    318 
    319   while (*cp && (*cp != '\n')) ++cp;
    320 #ifdef DEBUG
    321   fprintf(stderr, "%d: %p: %.*s\n", lineNum, tag, (int)(cp - pData), pData);
    322 #endif
    323   pData = cp;
    324 
    325   if (lineNum) {
    326     if (map->emplaceUnique(tagIndex,
    327                            TagFmt(std::make_pair(MapString(tag, tagLen),
    328                                                  MapString(fmt, fmtLen))),
    329                            verbose)) {
    330       return 0;
    331     }
    332   } else {
    333     // cache
    334     if (map->emplaceUnique(
    335             tagIndex,
    336             TagFmt(std::make_pair(MapString(std::string(tag, tagLen)),
    337                                   MapString(std::string(fmt, fmtLen)))))) {
    338       return 0;
    339     }
    340   }
    341   errno = EMLINK;
    342   return -1;
    343 }
    344 
    345 static const char* eventTagFiles[NUM_MAPS] = {
    346   EVENT_TAG_MAP_FILE, "/dev/event-log-tags",
    347 };
    348 
    349 // Parse the tags out of the file.
    350 static int parseMapLines(EventTagMap* map, size_t which) {
    351   const char* cp = static_cast<char*>(map->mapAddr[which]);
    352   size_t len = map->mapLen[which];
    353   const char* endp = cp + len;
    354 
    355   // insist on EOL at EOF; simplifies parsing and null-termination
    356   if (!len || (*(endp - 1) != '\n')) {
    357 #ifdef DEBUG
    358     fprintf(stderr, OUT_TAG ": map file %zu[%zu] missing EOL on last line\n",
    359             which, len);
    360 #endif
    361     if (which) {  // do not propagate errors for other files
    362       return 0;
    363     }
    364     errno = EINVAL;
    365     return -1;
    366   }
    367 
    368   bool lineStart = true;
    369   int lineNum = 1;
    370   while (cp < endp) {
    371     if (*cp == '\n') {
    372       lineStart = true;
    373       lineNum++;
    374     } else if (lineStart) {
    375       if (*cp == '#') {
    376         // comment; just scan to end
    377         lineStart = false;
    378       } else if (isdigit(*cp)) {
    379         // looks like a tag; scan it out
    380         if (scanTagLine(map, cp, lineNum) != 0) {
    381           if (!which || (errno != EMLINK)) {
    382             return -1;
    383           }
    384         }
    385         lineNum++;  // we eat the '\n'
    386                     // leave lineStart==true
    387       } else if (isspace(*cp)) {
    388         // looks like leading whitespace; keep scanning
    389       } else {
    390         fprintf(stderr,
    391                 OUT_TAG
    392                 ": unexpected chars (0x%02x) in tag number on line %d\n",
    393                 *cp, lineNum);
    394         errno = EINVAL;
    395         return -1;
    396       }
    397     } else {
    398       // this is a blank or comment line
    399     }
    400     cp++;
    401   }
    402 
    403   return 0;
    404 }
    405 
    406 // Open the map file and allocate a structure to manage it.
    407 //
    408 // We create a private mapping because we want to terminate the log tag
    409 // strings with '\0'.
    410 LIBLOG_ABI_PUBLIC EventTagMap* android_openEventTagMap(const char* fileName) {
    411   EventTagMap* newTagMap;
    412   off_t end[NUM_MAPS];
    413   int save_errno, fd[NUM_MAPS];
    414   size_t which;
    415 
    416   memset(fd, -1, sizeof(fd));
    417   memset(end, 0, sizeof(end));
    418 
    419   for (which = 0; which < NUM_MAPS; ++which) {
    420     const char* tagfile = fileName ? fileName : eventTagFiles[which];
    421 
    422     fd[which] = open(tagfile, O_RDONLY | O_CLOEXEC);
    423     if (fd[which] < 0) {
    424       if (!which) {
    425         save_errno = errno;
    426         fprintf(stderr, OUT_TAG ": unable to open map '%s': %s\n", tagfile,
    427                 strerror(save_errno));
    428         goto fail_errno;
    429       }
    430       continue;
    431     }
    432     end[which] = lseek(fd[which], 0L, SEEK_END);
    433     save_errno = errno;
    434     (void)lseek(fd[which], 0L, SEEK_SET);
    435     if (!which && (end[0] < 0)) {
    436       fprintf(stderr, OUT_TAG ": unable to seek map '%s' %s\n", tagfile,
    437               strerror(save_errno));
    438       goto fail_close;
    439     }
    440     if (fileName) break;  // Only allow one as specified
    441   }
    442 
    443   newTagMap = new EventTagMap;
    444   if (newTagMap == NULL) {
    445     save_errno = errno;
    446     goto fail_close;
    447   }
    448 
    449   for (which = 0; which < NUM_MAPS; ++which) {
    450     if (fd[which] >= 0) {
    451       newTagMap->mapAddr[which] =
    452           mmap(NULL, end[which], which ? PROT_READ : PROT_READ | PROT_WRITE,
    453                which ? MAP_SHARED : MAP_PRIVATE, fd[which], 0);
    454       save_errno = errno;
    455       close(fd[which]); /* fd DONE */
    456       fd[which] = -1;
    457       if ((newTagMap->mapAddr[which] != MAP_FAILED) &&
    458           (newTagMap->mapAddr[which] != NULL)) {
    459         newTagMap->mapLen[which] = end[which];
    460       } else if (!which) {
    461         const char* tagfile = fileName ? fileName : eventTagFiles[which];
    462 
    463         fprintf(stderr, OUT_TAG ": mmap(%s) failed: %s\n", tagfile,
    464                 strerror(save_errno));
    465         goto fail_unmap;
    466       }
    467     }
    468   }
    469 
    470   for (which = 0; which < NUM_MAPS; ++which) {
    471     if (parseMapLines(newTagMap, which) != 0) {
    472       delete newTagMap;
    473       return NULL;
    474     }
    475     /* See 'fd DONE' comments above and below, no need to clean up here */
    476   }
    477 
    478   return newTagMap;
    479 
    480 fail_unmap:
    481   save_errno = EINVAL;
    482   delete newTagMap;
    483 fail_close:
    484   for (which = 0; which < NUM_MAPS; ++which) close(fd[which]); /* fd DONE */
    485 fail_errno:
    486   errno = save_errno;
    487   return NULL;
    488 }
    489 
    490 // Close the map.
    491 LIBLOG_ABI_PUBLIC void android_closeEventTagMap(EventTagMap* map) {
    492   if (map) delete map;
    493 }
    494 
    495 // Cache miss, go to logd to acquire a public reference.
    496 // Because we lack access to a SHARED PUBLIC /dev/event-log-tags file map?
    497 static const TagFmt* __getEventTag(EventTagMap* map, unsigned int tag) {
    498   // call event tag service to arrange for a new tag
    499   char* buf = NULL;
    500   // Can not use android::base::StringPrintf, asprintf + free instead.
    501   static const char command_template[] = "getEventTag id=%u";
    502   int ret = asprintf(&buf, command_template, tag);
    503   if (ret > 0) {
    504     // Add some buffer margin for an estimate of the full return content.
    505     size_t size =
    506         ret - strlen(command_template) +
    507         strlen("65535\n4294967295\t?\t\t\t?\t# uid=32767\n\n\f?success?");
    508     if (size > (size_t)ret) {
    509       char* np = static_cast<char*>(realloc(buf, size));
    510       if (np) {
    511         buf = np;
    512       } else {
    513         size = ret;
    514       }
    515     } else {
    516       size = ret;
    517     }
    518     // Ask event log tag service for an existing entry
    519     if (__send_log_msg(buf, size) >= 0) {
    520       buf[size - 1] = '\0';
    521       char* ep;
    522       unsigned long val = strtoul(buf, &ep, 10);  // return size
    523       const char* cp = ep;
    524       if ((buf != cp) && (val > 0) && (*cp == '\n')) {  // truncation OK
    525         ++cp;
    526         if (!scanTagLine(map, cp, 0)) {
    527           free(buf);
    528           return map->find(tag);
    529         }
    530       }
    531     }
    532     free(buf);
    533   }
    534   return NULL;
    535 }
    536 
    537 // Look up an entry in the map.
    538 LIBLOG_ABI_PUBLIC const char* android_lookupEventTag_len(const EventTagMap* map,
    539                                                          size_t* len,
    540                                                          unsigned int tag) {
    541   if (len) *len = 0;
    542   const TagFmt* str = map->find(tag);
    543   if (!str) {
    544     str = __getEventTag(const_cast<EventTagMap*>(map), tag);
    545   }
    546   if (!str) return NULL;
    547   if (len) *len = str->first.length();
    548   return str->first.data();
    549 }
    550 
    551 // Look up an entry in the map.
    552 LIBLOG_ABI_PUBLIC const char* android_lookupEventFormat_len(
    553     const EventTagMap* map, size_t* len, unsigned int tag) {
    554   if (len) *len = 0;
    555   const TagFmt* str = map->find(tag);
    556   if (!str) {
    557     str = __getEventTag(const_cast<EventTagMap*>(map), tag);
    558   }
    559   if (!str) return NULL;
    560   if (len) *len = str->second.length();
    561   return str->second.data();
    562 }
    563 
    564 // This function is deprecated and replaced with android_lookupEventTag_len
    565 // since it will cause the map to change from Shared and backed by a file,
    566 // to Private Dirty and backed up by swap, albeit highly compressible. By
    567 // deprecating this function everywhere, we save 100s of MB of memory space.
    568 LIBLOG_ABI_PUBLIC const char* android_lookupEventTag(const EventTagMap* map,
    569                                                      unsigned int tag) {
    570   size_t len;
    571   const char* tagStr = android_lookupEventTag_len(map, &len, tag);
    572 
    573   if (!tagStr) return tagStr;
    574   char* cp = const_cast<char*>(tagStr);
    575   cp += len;
    576   if (*cp) *cp = '\0';  // Trigger copy on write :-( and why deprecated.
    577   return tagStr;
    578 }
    579 
    580 // Look up tagname, generate one if necessary, and return a tag
    581 LIBLOG_ABI_PUBLIC int android_lookupEventTagNum(EventTagMap* map,
    582                                                 const char* tagname,
    583                                                 const char* format, int prio) {
    584   const char* ep = endOfTag(tagname);
    585   size_t len = ep - tagname;
    586   if (!len || *ep) {
    587     errno = EINVAL;
    588     return -1;
    589   }
    590 
    591   if ((prio != ANDROID_LOG_UNKNOWN) && (prio < ANDROID_LOG_SILENT) &&
    592       !__android_log_is_loggable_len(prio, tagname, len,
    593                                      __android_log_is_debuggable()
    594                                          ? ANDROID_LOG_VERBOSE
    595                                          : ANDROID_LOG_DEBUG)) {
    596     errno = EPERM;
    597     return -1;
    598   }
    599 
    600   if (!format) format = "";
    601   ssize_t fmtLen = strlen(format);
    602   int ret = map->find(TagFmt(
    603       std::make_pair(MapString(tagname, len), MapString(format, fmtLen))));
    604   if (ret != -1) return ret;
    605 
    606   // call event tag service to arrange for a new tag
    607   char* buf = NULL;
    608   // Can not use android::base::StringPrintf, asprintf + free instead.
    609   static const char command_template[] = "getEventTag name=%s format=\"%s\"";
    610   ret = asprintf(&buf, command_template, tagname, format);
    611   if (ret > 0) {
    612     // Add some buffer margin for an estimate of the full return content.
    613     char* cp;
    614     size_t size =
    615         ret - strlen(command_template) +
    616         strlen("65535\n4294967295\t?\t\t\t?\t# uid=32767\n\n\f?success?");
    617     if (size > (size_t)ret) {
    618       cp = static_cast<char*>(realloc(buf, size));
    619       if (cp) {
    620         buf = cp;
    621       } else {
    622         size = ret;
    623       }
    624     } else {
    625       size = ret;
    626     }
    627     // Ask event log tag service for an allocation
    628     if (__send_log_msg(buf, size) >= 0) {
    629       buf[size - 1] = '\0';
    630       unsigned long val = strtoul(buf, &cp, 10);        // return size
    631       if ((buf != cp) && (val > 0) && (*cp == '\n')) {  // truncation OK
    632         val = strtoul(cp + 1, &cp, 10);                 // allocated tag number
    633         if ((val > 0) && (val < UINT32_MAX) && (*cp == '\t')) {
    634           free(buf);
    635           ret = val;
    636           // cache
    637           map->emplaceUnique(ret, TagFmt(std::make_pair(
    638                                       MapString(std::string(tagname, len)),
    639                                       MapString(std::string(format, fmtLen)))));
    640           return ret;
    641         }
    642       }
    643     }
    644     free(buf);
    645   }
    646 
    647   // Hail Mary
    648   ret = map->find(MapString(tagname, len));
    649   if (ret == -1) errno = ESRCH;
    650   return ret;
    651 }
    652