Switch to side-by-side view

--- a/src/utils/circache.cpp
+++ b/src/utils/circache.cpp
@@ -162,6 +162,8 @@
     off_t m_nheadoffs;
     // Pad size for newest entry. 
     int   m_npadsize;
+    // Keep history or only last entry
+    bool  m_uniquentries; 
     ///////////////////// End header entries
 
     // A place to hold data when reading
@@ -214,6 +216,10 @@
                     it->first.asHexString().c_str(), (ULONG)it->second));
         }
     }
+
+    // Return list of candidate offsets for udi (possibly several
+    // because there may be hash collisions, and also multiple
+    // instances).
     bool khFind(const string& udi, vector<off_t>& ofss)
     {
         ofss.clear();
@@ -223,8 +229,7 @@
         LOGDEB2(("Circache::khFind: h %s udi [%s]\n", 
                 h.asHexString().c_str(), udi.c_str()));
 
-        pair<kh_type::iterator, kh_type::iterator> p = 
-            m_ofskh.equal_range(h);
+        pair<kh_type::iterator, kh_type::iterator> p = m_ofskh.equal_range(h);
 
 #if 0
         if (p.first == m_ofskh.end()) LOGDEB(("KHFIND: FIRST END()\n"));
@@ -243,11 +248,45 @@
         }
         return true;
     }
-
+    // Clear entry for udi/offs
+    bool khClear(const pair<string, off_t>& ref)
+    {
+        UdiH h(ref.first);
+        pair<kh_type::iterator, kh_type::iterator> p = m_ofskh.equal_range(h);
+        if (p.first != m_ofskh.end() && (p.first->first == h)) {
+            for (kh_type::iterator it = p.first; it != p.second; ) {
+                kh_type::iterator tmp = it++;
+                if (tmp->second == ref.second)
+                    m_ofskh.erase(tmp);
+            }
+        }
+        return true;
+    }
+    // Clear entries for list of udi/offs
+    bool khClear(const list<pair<string, off_t> >& udis)
+    {
+        for (list<pair<string, off_t> >::const_iterator it = udis.begin(); 
+             it != udis.end(); it++)
+            khClear(*it);
+        return true;
+    }
+    // Clear all entries for udi
+    bool khClear(const string& udi)
+    {
+        UdiH h(udi);
+        pair<kh_type::iterator, kh_type::iterator> p = m_ofskh.equal_range(h);
+        if (p.first != m_ofskh.end() && (p.first->first == h)) {
+            for (kh_type::iterator it = p.first; it != p.second; ) {
+                kh_type::iterator tmp = it++;
+                m_ofskh.erase(tmp);
+            }
+        }
+        return true;
+    }
     CirCacheInternal()
         : m_fd(-1), m_maxsize(-1), m_oheadoffs(-1), 
-          m_nheadoffs(0), m_npadsize(0), m_buffer(0), m_bufsiz(0), 
-          m_ofskhcplt(false)
+          m_nheadoffs(0), m_npadsize(0), m_uniquentries(false),
+          m_buffer(0), m_bufsiz(0), m_ofskhcplt(false)
     {}
 
     ~CirCacheInternal()
@@ -286,7 +325,8 @@
             "maxsize = " << m_maxsize << "\n" <<
             "oheadoffs = " << m_oheadoffs << "\n" <<
             "nheadoffs = " << m_nheadoffs << "\n" <<
-            "npadsize = " << m_npadsize << "\n" <<
+            "npadsize = " << m_npadsize   << "\n" <<
+            "unient = " << m_uniquentries << "\n" <<
             "                                                              " <<
             "                                                              " <<
             "                                                              " <<
@@ -306,9 +346,8 @@
     {
         assert(m_fd >= 0);
 
-        char *bf = buf(CIRCACHE_FIRSTBLOCK_SIZE);
-        if (!bf)
-            return false;
+        char bf[CIRCACHE_FIRSTBLOCK_SIZE];
+
         lseek(m_fd, 0, 0);
         if (read(m_fd, bf, CIRCACHE_FIRSTBLOCK_SIZE) != 
             CIRCACHE_FIRSTBLOCK_SIZE) {
@@ -338,14 +377,17 @@
             return false;
         }
         m_npadsize = atol(value.c_str());
+        if (!conf.get("unient", value, "")) {
+            m_uniquentries = false;
+        } else {
+            m_uniquentries = stringToBool(value);
+        }
         return true;
     }            
 
-    bool writeentryheader(off_t offset, const EntryHeaderData& d)
-    {
-        char *bf = buf(CIRCACHE_HEADER_SIZE);
-        if (bf == 0)
-            return false;
+    bool writeEntryHeader(off_t offset, const EntryHeaderData& d)
+    {
+        char bf[CIRCACHE_HEADER_SIZE];
         memset(bf, 0, CIRCACHE_HEADER_SIZE);
         sprintf(bf, headerformat, d.dicsize, d.datasize, d.padsize, d.flags);
         if (lseek(m_fd, offset, 0) != offset) {
@@ -360,19 +402,17 @@
         return true;
     }
 
-    CCScanHook::status readentryheader(off_t offset, EntryHeaderData& d)
+    CCScanHook::status readEntryHeader(off_t offset, EntryHeaderData& d)
     {
         assert(m_fd >= 0);
 
         if (lseek(m_fd, offset, 0) != offset) {
-            m_reason << "readentryheader: lseek(" << offset << 
+            m_reason << "readEntryHeader: lseek(" << offset << 
                 ") failed: errno " << errno;
             return CCScanHook::Error;
         }
-        char *bf = buf(CIRCACHE_HEADER_SIZE);
-        if (bf == 0) {
-            return CCScanHook::Error;
-        }
+        char bf[CIRCACHE_HEADER_SIZE];
+
         int ret = read(m_fd, bf, CIRCACHE_HEADER_SIZE);
         if (ret == 0) {
             // Eof
@@ -385,11 +425,11 @@
         }
         if (sscanf(bf, headerformat, &d.dicsize, &d.datasize, 
                    &d.padsize, &d.flags) != 4) {
-            m_reason << " readentryheader: bad header at " << 
+            m_reason << " readEntryHeader: bad header at " << 
                 offset << " [" << bf << "]";
             return CCScanHook::Error;
         }
-        LOGDEB2(("Circache:readentryheader: dcsz %u dtsz %u pdsz %u flgs %hu\n",
+        LOGDEB2(("Circache:readEntryHeader: dcsz %u dtsz %u pdsz %u flgs %hu\n",
                  d.dicsize, d.datasize, d.padsize, d.flags));
         return CCScanHook::Continue;
     }
@@ -410,7 +450,7 @@
 
             EntryHeaderData d;
             CCScanHook::status st;
-            switch ((st = readentryheader(startoffset, d))) {
+            switch ((st = readEntryHeader(startoffset, d))) {
             case CCScanHook::Continue: break;
             case CCScanHook::Eof:
                 if (fold && !already_folded) {
@@ -422,25 +462,28 @@
             default:
                 return st;
             }
-
-            char *bf;
-            if ((bf = buf(d.dicsize+1)) == 0) {
-                return CCScanHook::Error;
-            }
-            bf[d.dicsize] = 0;
-            if (read(m_fd, bf, d.dicsize) != int(d.dicsize)) {
-                m_reason << "scan: read failed errno " << errno;
-                return CCScanHook::Error;
-            }
-            string b(bf, d.dicsize);
-            ConfSimple conf(b, 1);
             
             string udi;
-            if (!conf.get("udi", udi, "")) {
-                m_reason << "scan: no udi in dic";
-                return CCScanHook::Error;
-            }
-            khEnter(udi, startoffset);
+            if (d.dicsize) {
+                // d.dicsize is 0 for erased entries
+                char *bf;
+                if ((bf = buf(d.dicsize+1)) == 0) {
+                    return CCScanHook::Error;
+                }
+                bf[d.dicsize] = 0;
+                if (read(m_fd, bf, d.dicsize) != int(d.dicsize)) {
+                    m_reason << "scan: read failed errno " << errno;
+                    return CCScanHook::Error;
+                }
+                string b(bf, d.dicsize);
+                ConfSimple conf(b, 1);
+            
+                if (!conf.get("udi", udi, "")) {
+                    m_reason << "scan: no udi in dic";
+                    return CCScanHook::Error;
+                }
+                khEnter(udi, startoffset);
+            }
 
             // Call callback
             CCScanHook::status a = 
@@ -451,17 +494,30 @@
             default:
                 return a;
             }
+
             startoffset += CIRCACHE_HEADER_SIZE + d.dicsize + 
                 d.datasize + d.padsize;
         }
     }
 
-    bool readHDicData(off_t hoffs, EntryHeaderData& d, string& dic, 
-                      string* data)
-    {
-        if (readentryheader(hoffs, d) != CCScanHook::Continue)
-            return false;
-        return readDicData(hoffs, d, dic, data);
+    bool readHUdi(off_t hoffs, EntryHeaderData& d, string& udi)
+    {
+        if (readEntryHeader(hoffs, d) != CCScanHook::Continue)
+            return false;
+        string dic;
+        if (!readDicData(hoffs, d, dic, 0))
+            return false;
+        if (d.dicsize == 0) {
+            // This is an erase entry
+            udi.erase();
+            return true;
+        }
+        ConfSimple conf(dic);
+        if (!conf.get("udi", udi)) {
+            m_reason << "Bad file: no udi in dic";
+            return false;
+        }
+        return true;
     }
 
     bool readDicData(off_t hoffs, EntryHeaderData& hd, string& dic, 
@@ -476,42 +532,49 @@
                 errno;
             return false;
         }
-        char *bf = buf(hd.dicsize);
-        if (bf == 0)
-            return false;
-        if (read(m_fd, bf, hd.dicsize) != int(hd.dicsize)) {
-            m_reason << "CirCache::get: read() failed: errno " << errno;
-            return false;
-        }
-        dic.assign(bf, hd.dicsize);
-
+        char *bf = 0;
+        if (hd.dicsize) {
+            bf = buf(hd.dicsize);
+            if (bf == 0)
+                return false;
+            if (read(m_fd, bf, hd.dicsize) != int(hd.dicsize)) {
+                m_reason << "CirCache::get: read() failed: errno " << errno;
+                return false;
+            }
+            dic.assign(bf, hd.dicsize);
+        } else {
+            dic.erase();
+        }
         if (data == 0)
             return true;
 
-        bf = buf(hd.datasize);
-        if (bf == 0)
-            return false;
-        if (read(m_fd, bf, hd.datasize) != int(hd.datasize)){
-            m_reason << "CirCache::get: read() failed: errno " << errno;
-            return false;
-        }
-
-        if (hd.flags & EFDataCompressed) {
-            LOGDEB1(("Circache:readdicdata: data compressed\n"));
-            char *uncomp;
-            unsigned int uncompsize;
-            if (!inflateToDynBuf(bf, hd.datasize, 
-                                 (void **)&uncomp, &uncompsize)) {
-                m_reason << "CirCache: decompression failed ";
+        if (hd.datasize) {
+            bf = buf(hd.datasize);
+            if (bf == 0)
                 return false;
-            }
-            data->assign(uncomp, uncompsize);
-            free(uncomp);
+            if (read(m_fd, bf, hd.datasize) != int(hd.datasize)){
+                m_reason << "CirCache::get: read() failed: errno " << errno;
+                return false;
+            }
+
+            if (hd.flags & EFDataCompressed) {
+                LOGDEB1(("Circache:readdicdata: data compressed\n"));
+                char *uncomp;
+                unsigned int uncompsize;
+                if (!inflateToDynBuf(bf, hd.datasize, 
+                                     (void **)&uncomp, &uncompsize)) {
+                    m_reason << "CirCache: decompression failed ";
+                    return false;
+                }
+                data->assign(uncomp, uncompsize);
+                free(uncomp);
+            } else {
+                LOGDEB1(("Circache:readdicdata: data NOT compressed\n"));
+                data->assign(bf, hd.datasize);
+            }
         } else {
-            LOGDEB1(("Circache:readdicdata: data NOT compressed\n"));
-            data->assign(bf, hd.datasize);
-        }
-
+            data->erase();
+        }
         return true;
     }
 
@@ -535,9 +598,9 @@
     return m_d ? m_d->m_reason.str() : "Not initialized";
 }
 
-bool CirCache::create(off_t m_maxsize, bool onlyifnotexists)
-{
-    LOGDEB(("CirCache::create: [%s]\n", m_dir.c_str()));
+bool CirCache::create(off_t m_maxsize, int flags)
+{
+    LOGDEB(("CirCache::create: [%s] flags 0x%x\n", m_dir.c_str(), flags));
     assert(m_d != 0);
     struct stat st;
     if (stat(m_dir.c_str(), &st) < 0) {
@@ -547,7 +610,7 @@
             return false;
         }
     } else {
-        if (onlyifnotexists)
+        if (!(flags & CC_CRTRUNCATE))
             return open(CC_OPWRITE);
     }
 
@@ -561,6 +624,7 @@
 
     m_d->m_maxsize = m_maxsize;
     m_d->m_oheadoffs = CIRCACHE_FIRSTBLOCK_SIZE;
+    m_d->m_uniquentries = ((flags & CC_CRUNIQUE) != 0);
 
     char buf[CIRCACHE_FIRSTBLOCK_SIZE];
     memset(buf, 0, CIRCACHE_FIRSTBLOCK_SIZE);
@@ -662,7 +726,6 @@
 bool CirCache::get(const string& udi, string& dic, string& data, int instance)
 {
     Chrono chron;
-
     assert(m_d != 0);
     if (m_d->m_fd < 0) {
         m_d->m_reason << "CirCache::get: not open";
@@ -684,16 +747,10 @@
             for (vector<off_t>::iterator it = ofss.begin();
                  it != ofss.end(); it++) {
                 LOGDEB1(("Circache::get: trying offs %lu\n", (ULONG)*it));
-                string fdic;
                 EntryHeaderData d;
-                if (!m_d->readHDicData(*it, d, fdic, 0)) 
+                string fudi;
+                if (!m_d->readHUdi(*it, d, fudi)) 
                     return false;
-                ConfSimple conf(fdic);
-                string fudi;
-                if (!conf.get("udi", fudi, "")) {
-                    m_d->m_reason << "get: bad file: no udi in dic";
-                    return false;
-                }
                 if (!fudi.compare(udi)) {
                     // Found one, memorize offset. Done if instance
                     // matches, else go on. If instance is -1 need to
@@ -735,13 +792,64 @@
     return bret;
 }
 
+bool CirCache::erase(const string& udi)
+{
+    assert(m_d != 0);
+    if (m_d->m_fd < 0) {
+        m_d->m_reason << "CirCache::erase: not open";
+        return false;
+    }
+
+    LOGDEB0(("CirCache::erase: udi [%s]\n", udi.c_str()));
+
+    // If the mem cache is not up to date, update it, we're too lazy
+    // to do a scan
+    if (!m_d->m_ofskhcplt) {
+        string dic, data;
+        get("nosuchudi probably exists", dic, data);
+        if (!m_d->m_ofskhcplt) {
+            LOGERR(("CirCache::erase : cache not updated after get\n"));
+            return false;
+        }
+    }
+
+    vector<off_t> ofss;
+    if (!m_d->khFind(udi, ofss)) {
+        // Udi not in there,  erase ok
+        LOGDEB(("CirCache::erase: khFind returns none\n"));
+        return true;
+    }
+
+    for (vector<off_t>::iterator it = ofss.begin(); it != ofss.end(); it++) {
+        LOGDEB(("CirCache::erase: reading at %lu\n", (unsigned long)*it));
+        EntryHeaderData d;
+        string fudi;
+        if (!m_d->readHUdi(*it, d, fudi)) 
+            return false;
+        LOGDEB(("CirCache::erase: found fudi [%s]\n", fudi.c_str()));
+        if (!fudi.compare(udi)) {
+            EntryHeaderData nd;
+            nd.padsize = d.dicsize + d.datasize + d.padsize;
+            LOGDEB(("CirCache::erase: rewriting at %lu\n", (unsigned long)*it));
+            if (*it == m_d->m_nheadoffs)
+                m_d->m_npadsize = nd.padsize;
+            if(!m_d->writeEntryHeader(*it, nd)) {
+                LOGERR(("CirCache::erase: write header failed\n"));
+                return false;
+            }
+        }
+    }
+    m_d->khClear(udi);
+    return true;
+}
+
 // Used to scan the file ahead until we accumulated enough space for the new
 // entry. 
 class CCScanHookSpacer : public  CCScanHook {
 public:
     UINT sizewanted;
     UINT sizeseen;
-    
+    list<pair<string, off_t> > squashed_udis;
     CCScanHookSpacer(int sz)
         : sizewanted(sz), sizeseen(0) {assert(sz > 0);}
 
@@ -751,6 +859,7 @@
         LOGDEB2(("Circache:ScanSpacer:off %u dcsz %u dtsz %u pdsz %u udi[%s]\n",
                 (UINT)offs, d.dicsize, d.datasize, d.padsize, udi.c_str()));
         sizeseen += CIRCACHE_HEADER_SIZE + d.dicsize + d.datasize + d.padsize;
+        squashed_udis.push_back(make_pair(udi, offs));
         if (sizeseen >= sizewanted)
             return Stop;
         return Continue;
@@ -766,15 +875,22 @@
         return false;
     }
 
-    // We want udi in metadata
+    // We need the udi in input metadata
     string dic;
     if (!iconf || !iconf->get("udi", dic) || dic.empty() || dic.compare(udi)) {
         m_d->m_reason << "No/bad 'udi' entry in input dic";
         LOGERR(("Circache::put: no/bad udi: DIC:[%s] UDI [%s]\n", 
                 dic.c_str(), udi.c_str()));
-        iconf->write(cerr);
-        return false;
-    }
+        return false;
+    }
+
+    // Possibly erase older entries. Need to do this first because we may be
+    // able to reuse the space if the same udi was last written
+    if (m_d->m_uniquentries && !erase(udi)) {
+        LOGERR(("CirCache::put: can't erase older entries\n"));
+        return false;
+    }
+
     ostringstream s;
     iconf->write(s);
     dic = s.str();
@@ -783,10 +899,11 @@
     const char *datap = data.c_str();
     unsigned int datalen = data.size();
     unsigned short flags = 0;
+    TempBuf compbuf;
     if (!(iflags & NoCompHint)) {
         ULONG len = compressBound(data.size());
-        char *bf = m_d->buf(len);
-        if (bf != 0 && 
+        char *bf = compbuf.setsize(len);
+        if (bf != 0 &&
             compress((Bytef*)bf, &len, (Bytef*)data.c_str(), data.size()) 
             == Z_OK) {
             if (float(len) < 0.9 * float(data.size())) {
@@ -804,85 +921,82 @@
         return false;
     }
 
-    // Characteristics for the new entry
+    // Characteristics for the new entry.
     int nsize = CIRCACHE_HEADER_SIZE + dic.size() + datalen;
-    int nwriteoffs = 0;
+    int nwriteoffs = m_d->m_oheadoffs;
     int npadsize = 0;
     bool extending = false;
 
-    LOGDEB2(("CirCache::put: nsz %d oheadoffs %d\n", nsize, m_d->m_oheadoffs));
-
-    if (st.st_size < m_d->m_maxsize) {
-        // If we are still growing the file, things are simple
-        nwriteoffs = lseek(m_d->m_fd, 0, SEEK_END);
+    LOGDEB(("CirCache::put: nsz %d oheadoffs %d\n", nsize, m_d->m_oheadoffs));
+
+    // Check if we can recover some pad space from the (physically) previous
+    // entry.
+    int recovpadsize = m_d->m_oheadoffs == CIRCACHE_FIRSTBLOCK_SIZE ?
+        0 : m_d->m_npadsize;
+    if (recovpadsize != 0) {
+        // Need to read the latest entry's header, to rewrite it with a 
+        // zero pad size
+        EntryHeaderData pd;
+        if (m_d->readEntryHeader(m_d->m_nheadoffs, pd) != CCScanHook::Continue){
+            return false;
+        }
+        assert(int(pd.padsize) == m_d->m_npadsize);
+        if (pd.dicsize == 0) {
+            // erased entry. Also recover the header space, no need to rewrite
+            // the header, we're going to write on it.
+            recovpadsize += CIRCACHE_HEADER_SIZE;
+        } else {
+            LOGDEB(("CirCache::put: recov. prev. padsize %d\n", pd.padsize));
+            pd.padsize = 0;
+            if (!m_d->writeEntryHeader(m_d->m_nheadoffs, pd))
+                return false;
+            // If we fail between here and the end, the file is broken.
+        }
+        nwriteoffs = m_d->m_oheadoffs - recovpadsize;
+    }
+
+    if (nsize <= recovpadsize) {
+        // If the new entry fits entirely in the pad area from the
+        // latest one, no need to recycle stuff
+        LOGDEB(("CirCache::put: new fits in old padsize %d\n", recovpadsize));
+        npadsize = recovpadsize - nsize;
+    } else if (st.st_size < m_d->m_maxsize) {
+        // Still growing the file. 
         npadsize = 0;
         extending = true;
     } else {
-        // We'll write at the oldest header, minus the possible
-        // padsize for the previous (latest) one.
-        int recovpadsize = m_d->m_oheadoffs == CIRCACHE_FIRSTBLOCK_SIZE ?
-            0 : m_d->m_npadsize;
-        if (recovpadsize == 0) {
-            // No padsize to recover
-            nwriteoffs = m_d->m_oheadoffs;
-        } else {
-            // Need to read the latest entry's header, to rewrite it with a 
-            // zero pad size
-            EntryHeaderData pd;
-            if (m_d->readentryheader(m_d->m_nheadoffs, pd) != 
-                CCScanHook::Continue) {
-                return false;
-            }
-            assert(int(pd.padsize) == m_d->m_npadsize);
-            LOGDEB2(("CirCache::put: recovering previous padsize %d\n",
-                     pd.padsize));
-            pd.padsize = 0;
-            if (!m_d->writeentryheader(m_d->m_nheadoffs, pd)) {
-                return false;
-            }
-            nwriteoffs = m_d->m_oheadoffs - recovpadsize;
-            // If we fail between here and the end, the file is hosed.
-        }
-
-        if (nsize <= recovpadsize) {
-            // If the new entry fits entirely in the pad area from the
-            // latest one, no need to recycle the oldest entries.
-            LOGDEB2(("CirCache::put: new fits in old padsize %d\n,"
-                     recovpadsize));
-            npadsize = recovpadsize - nsize;
-        } else {
-            // Scan the file until we have enough space for the new entry,
-            // and determine the pad size up to the 1st preserved entry
-            int scansize = nsize - recovpadsize;
-            LOGDEB2(("CirCache::put: scanning for size %d from offs %u\n",
-                    scansize, (UINT)m_d->m_oheadoffs));
-            CCScanHookSpacer spacer(scansize);
-            switch (m_d->scan(m_d->m_oheadoffs, &spacer)) {
-            case CCScanHook::Stop: 
-                LOGDEB2(("CirCache::put: Scan ok, sizeseen %d\n", 
-                         spacer.sizeseen));
-                npadsize = spacer.sizeseen - scansize;
-                break;
-            case CCScanHook::Eof:
-                // npadsize is 0
-                extending = true;
-                break;
-            case CCScanHook::Continue: 
-            case CCScanHook::Error: 
-                return false;
-            }
-        }
+        // Scan the file until we have enough space for the new entry,
+        // and determine the pad size up to the 1st preserved entry
+        int scansize = nsize - recovpadsize;
+        LOGDEB(("CirCache::put: scanning for size %d from offs %u\n",
+                scansize, (UINT)m_d->m_oheadoffs));
+        CCScanHookSpacer spacer(scansize);
+        switch (m_d->scan(m_d->m_oheadoffs, &spacer)) {
+        case CCScanHook::Stop: 
+            LOGDEB(("CirCache::put: Scan ok, sizeseen %d\n", 
+                    spacer.sizeseen));
+            npadsize = spacer.sizeseen - scansize;
+            break;
+        case CCScanHook::Eof:
+            npadsize = 0;
+            extending = true;
+            break;
+        case CCScanHook::Continue: 
+        case CCScanHook::Error: 
+            return false;
+        }
+        // Take the recycled entries off the multimap
+        m_d->khClear(spacer.squashed_udis);
     }
     
-    LOGDEB2(("CirCache::put: writing %d at %d padsize %d\n", 
-             nsize, nwriteoffs, npadsize));
+    LOGDEB(("CirCache::put: writing %d at %d padsize %d\n", 
+            nsize, nwriteoffs, npadsize));
+
     if (lseek(m_d->m_fd, nwriteoffs, 0) != nwriteoffs) {
         m_d->m_reason << "CirCache::put: lseek failed: " << errno;
         return false;
     }
-    char *bf = m_d->buf(CIRCACHE_HEADER_SIZE);
-    if (bf == 0) 
-        return false;
+
     char head[CIRCACHE_HEADER_SIZE];
     memset(head, 0, CIRCACHE_HEADER_SIZE);
     sprintf(head, headerformat, dic.size(), datalen, npadsize, flags);
@@ -908,12 +1022,10 @@
     // New oldest header is the one just after the one we just wrote.
     m_d->m_oheadoffs = nwriteoffs + nsize + npadsize;
     if (nwriteoffs + nsize >= m_d->m_maxsize) {
-        // If we are at the biggest allowed size or we are currently
-        // growing a young file, the oldest header is at BOT.
+        // Max size or top of file reached, next write at BOT.
         m_d->m_oheadoffs = CIRCACHE_FIRSTBLOCK_SIZE;
     }
     return m_d->writefirstblock();
-    return true;
 }
 
 bool CirCache::rewind(bool& eof)
@@ -924,7 +1036,7 @@
 
     // Read oldest header
     m_d->m_itoffs = m_d->m_oheadoffs;
-    CCScanHook::status st = m_d->readentryheader(m_d->m_itoffs, m_d->m_ithd);
+    CCScanHook::status st = m_d->readEntryHeader(m_d->m_itoffs, m_d->m_ithd);
 
     switch(st) {
     case CCScanHook::Eof:
@@ -954,7 +1066,7 @@
     }
 
     // Read. If we hit physical eof, fold.
-    CCScanHook::status st = m_d->readentryheader(m_d->m_itoffs, m_d->m_ithd);
+    CCScanHook::status st = m_d->readEntryHeader(m_d->m_itoffs, m_d->m_ithd);
     if (st == CCScanHook::Eof) {
         m_d->m_itoffs = CIRCACHE_FIRSTBLOCK_SIZE;
         if (m_d->m_itoffs == m_d->m_oheadoffs) {
@@ -962,7 +1074,7 @@
             eof = true;
             return false;
         }
-        st = m_d->readentryheader(m_d->m_itoffs, m_d->m_ithd);
+        st = m_d->readEntryHeader(m_d->m_itoffs, m_d->m_ithd);
     }
 
     if (st == CCScanHook::Continue)
@@ -1095,7 +1207,7 @@
 static char *thisprog;
 
 static char usage [] =
-" -c <dirname> : create\n"
+" -c [-u] <dirname> : create\n"
 " -p <dirname> <apath> [apath ...] : put files\n"
 " -d <dirname> : dump\n"
 " -g [-i instance] [-D] <dirname> <udi>: get\n"
@@ -1116,6 +1228,8 @@
 #define OPT_d     0x20
 #define OPT_i     0x40
 #define OPT_D     0x80
+#define OPT_u     0x100
+
 int main(int argc, char **argv)
 {
   int instance = -1;
@@ -1135,6 +1249,7 @@
       case 'g':	op_flags |= OPT_g; break;
       case 'd':	op_flags |= OPT_d; break;
       case 'D':	op_flags |= OPT_D; break;
+      case 'u':	op_flags |= OPT_u; break;
       case 'i':	op_flags |= OPT_i; if (argc < 2)  Usage();
 	if ((sscanf(*(++argv), "%d", &instance)) != 1) 
 	  Usage(); 
@@ -1155,7 +1270,10 @@
   CirCache cc(dir);
 
   if (op_flags & OPT_c) {
-      if (!cc.create(100*1024)) {
+      int flags = 0;
+      if (op_flags & OPT_u)
+          flags |= CirCache::CC_CRUNIQUE;
+      if (!cc.create(100*1024, flags)) {
           cerr << "Create failed:" << cc.getReason() << endl;
           exit(1);
       }