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