Annotation of parser3/src/types/pa_vhashfile.C, revision 1.80

1.1       parser      1: /** @file
                      2:        Parser: @b table class.
                      3: 
1.78      moko        4:        Copyright (c) 2001-2026 Art. Lebedev Studio (https://www.artlebedev.com)
1.74      moko        5:        Authors: Konstantin Morshnev <moko@design.ru>, Alexandr Petrosian <paf@design.ru>
1.1       parser      6: */
                      7: 
1.32      paf         8: #include "pa_globals.h"
1.48      paf         9: #include "pa_common.h"
1.79      moko       10: #include "pa_file.h"
1.1       parser     11: #include "pa_vtable.h"
                     12: #include "pa_vstring.h"
                     13: #include "pa_vhashfile.h"
1.32      paf        14: #include "pa_vdate.h"
                     15: 
1.80    ! moko       16: volatile const char * IDENT_PA_VHASHFILE_C="$Id: pa_vhashfile.C,v 1.79 2026/09/27 17:13:40 moko Exp $" IDENT_PA_VHASHFILE_H;
1.64      moko       17: 
1.32      paf        18: // consts
                     19: 
1.41      paf        20: const uint HASHFILE_VALUE_SERIALIZED_VERSION=0x0001;
1.1       parser     21: 
                     22: // methods
                     23: 
1.54      misha      24: void check_dir(const char* file_name){
1.51      misha      25:        String& sfile_name = *new String(file_name);
                     26:        if(!entry_exists(sfile_name))
                     27:                create_dir_for_file(sfile_name);
1.54      misha      28: }
1.51      misha      29: 
1.70      moko       30: void VHashfile::check(const char *step, pa_status_t status) {
                     31:        if(status==PA_SUCCESS)
                     32:                return;
                     33: 
                     34:        const char* str=strerror(status);
                     35:        throw Exception("file.access", 0, "%s error: %s (%d), actual filename '%s'", step, str ? str : "<unknown>", status, file_name);
                     36: }
1.51      misha      37: 
1.22      paf        38: void VHashfile::open(const String& afile_name) {
1.80    ! moko       39:        if(file_name) // ^h.open[] of an opened object
        !            40:                throw Exception(PARSER_RUNTIME, 0, "hashfile is already opened");
1.61      misha      41:        file_name=afile_name.taint_cstr(String::L_FILE_SPEC);
1.22      paf        42: }
                     43: 
1.34      paf        44: void VHashfile::close() {
1.51      misha      45:        if(!is_open())
                     46:                return;
1.34      paf        47: 
1.63      moko       48:        check("pa_sdbm_close", pa_sdbm_close(m_db));
1.51      misha      49:        m_db=0;
1.34      paf        50: }
                     51: 
1.54      misha      52: bool VHashfile::is_open() {
                     53:        return m_db != 0;
                     54: }
                     55: 
1.63      moko       56: pa_sdbm_t *VHashfile::get_db_for_reading() {
1.54      misha      57:        if(is_open()){
                     58:                return m_db;
                     59:        }
1.51      misha      60: 
1.54      misha      61:        if(file_name){
                     62:                check_dir(file_name);
1.69      moko       63:                check("pa_sdbm_open(shared)", pa_sdbm_open(&m_db, file_name, PA_CREATE|PA_READ|PA_SHARELOCK, 0664, 0));
1.54      misha      64:        }
1.51      misha      65: 
                     66:        if(!is_open())
1.69      moko       67:                throw Exception("file.read", 0, "can't open %s for reading", type());
1.34      paf        68: 
                     69:        return m_db;
                     70: }
                     71: 
1.63      moko       72: pa_sdbm_t *VHashfile::get_db_for_writing() {
1.54      misha      73:        if(is_open()){
1.63      moko       74:                if(pa_sdbm_rdonly(m_db)) {
1.54      misha      75:                        close(); // close if was opened for reading
                     76:                } else {
                     77:                        return m_db;
                     78:                }
                     79:        }
1.34      paf        80: 
1.54      misha      81:        if(file_name) {
                     82:                check_dir(file_name);
1.34      paf        83:                // reopen in write mode & exclusive lock
1.69      moko       84:                check("pa_sdbm_open(exclusive)", pa_sdbm_open(&m_db, file_name, PA_CREATE|PA_WRITE, 0664, 0));
1.27      paf        85:        }
1.34      paf        86: 
1.54      misha      87:        if(!is_open())
1.69      moko       88:                throw Exception("file.access", 0, "can't open %s for writing", type());
1.54      misha      89: 
1.34      paf        90:        return m_db;
1.27      paf        91: }
                     92: 
1.22      paf        93: VHashfile::~VHashfile() {
1.51      misha      94:        if(is_open())
1.34      paf        95:                close();
1.22      paf        96: }
                     97: 
1.32      paf        98: struct Hashfile_value_serialized_prolog {
1.41      paf        99:        uint version;
1.32      paf       100:        time_t time_to_die;
                    101: };
                    102: 
1.63      moko      103: pa_sdbm_datum_t VHashfile::serialize_value(const String& string, time_t time_to_die) const {
                    104:        pa_sdbm_datum_t result;
1.32      paf       105: 
                    106:        size_t length=string.length();
                    107:        result.dsize=sizeof(Hashfile_value_serialized_prolog)+length;
                    108:        result.dptr=new(PointerFreeGC) char[result.dsize];
                    109: 
                    110:        Hashfile_value_serialized_prolog& prolog=*reinterpret_cast<Hashfile_value_serialized_prolog*>(result.dptr);
                    111:        char *output_cstr=result.dptr+sizeof(Hashfile_value_serialized_prolog);
                    112: 
                    113:        prolog.version=HASHFILE_VALUE_SERIALIZED_VERSION;
                    114:        prolog.time_to_die=time_to_die;
1.45      paf       115:        if(length) // reported errors on storing empty values to hashfiles, but without details. maybe here [win32, intel:solaris, freebsd were OK...]
                    116:                memcpy(output_cstr, string.cstr(), length);
1.32      paf       117: 
                    118:        return result;
                    119: }
                    120: 
1.63      moko      121: const String* VHashfile::deserialize_value(pa_sdbm_datum_t key, const pa_sdbm_datum_t value) {
1.35      paf       122:        // key not found || it's surely not in our format
1.38      paf       123:        if(!value.dptr || (size_t)value.dsize<sizeof(Hashfile_value_serialized_prolog))
1.35      paf       124:                return 0; 
                    125: 
1.41      paf       126:        // [WARNING: not cast, addresses must be %4=0 on sparc]
1.42      paf       127:        Hashfile_value_serialized_prolog prolog;
                    128:        memcpy(&prolog, value.dptr, sizeof(prolog));
1.41      paf       129:        
1.42      paf       130:        if(prolog.version!=HASHFILE_VALUE_SERIALIZED_VERSION
                    131:                || (prolog.time_to_die/*specified*/ 
                    132:                        && (prolog.time_to_die <= time(0)/*expired*/))) {
1.35      paf       133:                // old format || exipred value
                    134:                remove(key);
1.32      paf       135:                return 0;
1.35      paf       136:        }
1.32      paf       137:        
1.35      paf       138:        char *input_cstr=value.dptr+sizeof(Hashfile_value_serialized_prolog);
                    139:        size_t input_length=value.dsize-sizeof(Hashfile_value_serialized_prolog);
1.32      paf       140: 
1.69      moko      141:        return new String(input_length ? pa_strdup(input_cstr, input_length) : 0, String::L_TAINTED);
1.32      paf       142: }
                    143: 
1.65      misha     144: #define CHECK(aname) if(aname.is_empty()) throw Exception(PARSER_RUNTIME, 0, "hashfile key must not be empty");
                    145: 
1.27      paf       146: void VHashfile::put_field(const String& aname, Value *avalue) {
1.65      misha     147:        CHECK(aname)
1.57      misha     148: 
1.63      moko      149:        pa_sdbm_t *db=get_db_for_writing();
1.22      paf       150: 
1.8       parser    151:        time_t time_to_die=0;
                    152:        const String *value_string;
                    153: 
1.23      paf       154:        if(HashStringValue *hash=avalue->get_hash()) {
                    155:                if(Value *value_value=hash->get(value_name)) {
1.9       parser    156:                        if(value_value->get_junction())
1.69      moko      157:                                throw Exception(PARSER_RUNTIME, 0, VALUE_NAME" must not be code");
1.8       parser    158: 
                    159:                        value_string=&value_value->as_string();
                    160: 
1.32      paf       161:                        if(Value *expires=hash->get(expires_name)) {
1.75      moko      162:                                if(VDate* vdate=dynamic_cast<VDate*>(expires))
                    163:                                        time_to_die=vdate->get_time(); // $expires[DATE]
1.32      paf       164:                                else if(double days_till_expire=expires->as_double())
                    165:                                        time_to_die=time(NULL)+(time_t)(60*60*24*days_till_expire); // $expires(days)
                    166:                        }
1.8       parser    167:                } else
1.69      moko      168:                        throw Exception(PARSER_RUNTIME, &aname, "put hash value must contain ." VALUE_NAME);
1.8       parser    169:        } else
                    170:                value_string=&avalue->as_string();
1.43      paf       171: 
1.63      moko      172:        pa_sdbm_datum_t key;
1.23      paf       173:        key.dptr=const_cast<char*>(aname.cstr());
1.24      paf       174:        key.dsize=aname.length();
1.23      paf       175: 
1.63      moko      176:        pa_sdbm_datum_t value=serialize_value(*value_string, time_to_die);
1.23      paf       177: 
1.57      misha     178: #ifndef PAIRMAX
                    179: // !see PAIRMAX definition in sdbm_private.h. values should be the same
                    180: #define PAIRMAX 8008
                    181: #endif
                    182: 
                    183:        if(key.dsize+value.dsize > PAIRMAX)
1.69      moko      184:                throw Exception(PARSER_RUNTIME, 0,"hashfile record length (key+value) exceeds limit (%d bytes)", PAIRMAX);
1.57      misha     185: 
1.63      moko      186:        check("pa_sdbm_store", pa_sdbm_store(db, key, value, PA_SDBM_REPLACE));
1.1       parser    187: }
                    188: 
1.11      paf       189: Value *VHashfile::get_field(const String& aname) {
1.65      misha     190:        CHECK(aname)
                    191: 
1.63      moko      192:        pa_sdbm_t *db=get_db_for_reading();
1.34      paf       193: 
1.63      moko      194:        pa_sdbm_datum_t key;
1.23      paf       195:        key.dptr=const_cast<char*>(aname.cstr());
1.24      paf       196:        key.dsize=aname.length();
1.23      paf       197: 
1.63      moko      198:        pa_sdbm_datum_t value;
1.23      paf       199: 
1.63      moko      200:        check("pa_sdbm_fetch", pa_sdbm_fetch(db, &value, key));
1.23      paf       201: 
1.35      paf       202:        const String *sresult=deserialize_value(key, value);
1.32      paf       203:        return sresult? new VString(*sresult): 0;
1.24      paf       204: }
                    205: 
1.63      moko      206: void VHashfile::remove(const pa_sdbm_datum_t key) {
                    207:        pa_sdbm_t *db=get_db_for_writing();
1.27      paf       208: 
1.63      moko      209:        check("pa_sdbm_delete", pa_sdbm_delete(db, key));
1.35      paf       210: }
                    211: 
                    212: void VHashfile::remove(const String& aname) {
1.65      misha     213:        CHECK(aname)
                    214: 
1.63      moko      215:        pa_sdbm_datum_t key;
1.24      paf       216:        key.dptr=const_cast<char*>(aname.cstr());
                    217:        key.dsize=aname.length();
                    218: 
1.35      paf       219:        remove(key);
1.1       parser    220: }
                    221: 
1.63      moko      222: void VHashfile::for_each(bool callback(pa_sdbm_datum_t, void*), void* info) {
                    223:        pa_sdbm_t *db=get_db_for_reading();
1.34      paf       224: 
1.30      paf       225:        // collect keys
1.63      moko      226:        Array<pa_sdbm_datum_t>* keys=0;
                    227:        check("pa_sdbm_lock", pa_sdbm_lock(db, PA_FLOCK_SHARED));
1.25      paf       228:        try {
1.63      moko      229:                pa_sdbm_datum_t key;
                    230:                if(pa_sdbm_firstkey(db, &key)==PA_SUCCESS)
1.49      paf       231:                {
                    232:                        size_t count=0;
1.25      paf       233:                        do {
1.49      paf       234:                                // must cound beforehead, becase doing reallocs later would be VERY slow and cause HUGE fragmentation
                    235:                                count++;
1.63      moko      236:                        } while(pa_sdbm_nextkey(db, &key)==PA_SUCCESS);
1.49      paf       237: 
1.63      moko      238:                        keys=new Array<pa_sdbm_datum_t>(count);
1.49      paf       239: 
1.63      moko      240:                        if(pa_sdbm_firstkey(db, &key)==PA_SUCCESS)
1.49      paf       241:                                do {
                    242:                                        // must clone because it points to page which may go away 
                    243:                                        // [if they modify hashfile inside foreach]
                    244:                                        key.dptr = pa_strdup(key.dptr, key.dsize);
                    245:                                        *keys+=key;
1.63      moko      246:                                } while(pa_sdbm_nextkey(db, &key)==PA_SUCCESS);
1.49      paf       247:                }
1.25      paf       248:        } catch(...) {
1.69      moko      249:                check("pa_sdbm_unlock", pa_sdbm_unlock(db));
                    250:                rethrow;
1.4       parser    251:        }
1.63      moko      252:        check("pa_sdbm_unlock", pa_sdbm_unlock(db));
1.4       parser    253: 
1.30      paf       254:        // iterate them
1.49      paf       255:        if(keys)
                    256:                keys->for_each(callback, info);
1.26      paf       257: }
1.27      paf       258: 
                    259: #ifndef DOXYGEN
                    260: struct For_each_string_callback_info {
1.35      paf       261:        VHashfile* self;
1.27      paf       262:        void* nested_info;
1.50      paf       263:        bool (*nested_callback)(const String::Body, const String&, void*);
1.27      paf       264: };
                    265: #endif
1.63      moko      266: static bool for_each_string_callback(pa_sdbm_datum_t apkey, void* ainfo) {
1.27      paf       267:        For_each_string_callback_info& info=*static_cast<For_each_string_callback_info *>(ainfo);
1.63      moko      268:        pa_sdbm_t *db=info.self->get_db_for_reading();
1.27      paf       269: 
1.63      moko      270:        pa_sdbm_datum_t apvalue;
1.70      moko      271:        info.self->check("pa_sdbm_fetch", pa_sdbm_fetch(db, &apvalue, apkey));
1.35      paf       272: 
                    273:        if(const String* svalue=info.self->deserialize_value(apkey, apvalue)) {
                    274:                const char *clkey=pa_strdup(apkey.dptr, apkey.dsize);
1.27      paf       275: 
1.77      moko      276:                return info.nested_callback(String::Body(clkey), *svalue, info.nested_info);
1.35      paf       277:        }
1.50      paf       278:        return false;
1.27      paf       279: }
1.50      paf       280: void VHashfile::for_each(bool callback(const String::Body, const String&, void*), void* ainfo) {
1.27      paf       281:        For_each_string_callback_info info;
                    282:        
1.35      paf       283:        info.self=this;
1.27      paf       284:        info.nested_info=ainfo;
                    285:        info.nested_callback=callback;
                    286: 
                    287:        for_each(for_each_string_callback, &info);
                    288: }
                    289: 
1.50      paf       290: static bool get_hash__put(const String::Body key, const String& value, void* aresult) {
1.26      paf       291:        static_cast<HashStringValue*>(aresult)->put(key, new VString(value));
1.50      paf       292:        return false;
1.26      paf       293: }
                    294: HashStringValue *VHashfile::get_hash() {
                    295:        HashStringValue& result=*new HashStringValue();
                    296: 
                    297:        for_each(get_hash__put, &result);
                    298:        return &result;
1.34      paf       299: }
                    300: 
                    301: static void delete_file(const char* base_name, const char* ext) {
1.59      misha     302:        String sfile_name(base_name);
1.34      paf       303:        sfile_name<<ext;
1.54      misha     304:        file_delete(sfile_name, false);
1.34      paf       305: }
                    306: 
                    307: void VHashfile::delete_files() {
1.54      misha     308:        if(is_open())
1.51      misha     309:                close();
1.54      misha     310: 
                    311:        if(file_name){
1.63      moko      312:                delete_file(file_name, PA_SDBM_DIRFEXT);
                    313:                delete_file(file_name, PA_SDBM_PAGFEXT);
1.51      misha     314:        }
1.1       parser    315: }

E-mail: