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: