1 /* $NetBSD: dict_thash.c,v 1.1.1.3 2014/07/06 19:27:58 tron Exp $ */ 2 3 /*++ 4 /* NAME 5 /* dict_thash 3 6 /* SUMMARY 7 /* dictionary manager interface to hashed flat text files 8 /* SYNOPSIS 9 /* #include <dict_thash.h> 10 /* 11 /* DICT *dict_thash_open(path, open_flags, dict_flags) 12 /* const char *name; 13 /* const char *path; 14 /* int open_flags; 15 /* int dict_flags; 16 /* DESCRIPTION 17 /* dict_thash_open() opens the named flat text file, creates 18 /* an in-memory hash table, and makes it available via the 19 /* generic interface described in dict_open(3). The input 20 /* format is as with postmap(1). 21 /* DIAGNOSTICS 22 /* Fatal errors: cannot open file, out of memory. 23 /* SEE ALSO 24 /* dict(3) generic dictionary manager 25 /* LICENSE 26 /* .ad 27 /* .fi 28 /* The Secure Mailer license must be distributed with this software. 29 /* AUTHOR(S) 30 /* Wietse Venema 31 /* IBM T.J. Watson Research 32 /* P.O. Box 704 33 /* Yorktown Heights, NY 10598, USA 34 /*--*/ 35 36 /* System library. */ 37 38 #include <sys_defs.h> 39 #include <sys/stat.h> 40 #include <ctype.h> 41 #include <string.h> 42 43 /* Utility library. */ 44 45 #include <msg.h> 46 #include <mymalloc.h> 47 #include <htable.h> 48 #include <iostuff.h> 49 #include <vstring.h> 50 #include <stringops.h> 51 #include <readlline.h> 52 #include <dict.h> 53 #include <dict_thash.h> 54 #include <warn_stat.h> 55 56 /* Application-specific. */ 57 58 typedef struct { 59 DICT dict; /* generic members */ 60 HTABLE *table; /* in-memory hash */ 61 HTABLE_INFO **info; /* for iterator */ 62 HTABLE_INFO **cursor; /* ditto */ 63 } DICT_THASH; 64 65 #define STR vstring_str 66 67 /* dict_thash_lookup - find database entry */ 68 69 static const char *dict_thash_lookup(DICT *dict, const char *name) 70 { 71 DICT_THASH *dict_thash = (DICT_THASH *) dict; 72 const char *result = 0; 73 74 /* 75 * Optionally fold the key. 76 */ 77 if (dict->flags & DICT_FLAG_FOLD_FIX) { 78 if (dict->fold_buf == 0) 79 dict->fold_buf = vstring_alloc(10); 80 vstring_strcpy(dict->fold_buf, name); 81 name = lowercase(vstring_str(dict->fold_buf)); 82 } 83 84 /* 85 * Look up the value. 86 */ 87 result = htable_find(dict_thash->table, name); 88 89 DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, result); 90 } 91 92 /* dict_thash_sequence - traverse the dictionary */ 93 94 static int dict_thash_sequence(DICT *dict, int function, 95 const char **key, const char **value) 96 { 97 const char *myname = "dict_thash_sequence"; 98 DICT_THASH *dict_thash = (DICT_THASH *) dict; 99 100 /* 101 * Determine and execute the seek function. 102 */ 103 switch (function) { 104 case DICT_SEQ_FUN_FIRST: 105 if (dict_thash->info == 0) 106 dict_thash->info = htable_list(dict_thash->table); 107 dict_thash->cursor = dict_thash->info; 108 break; 109 case DICT_SEQ_FUN_NEXT: 110 if (dict_thash->cursor[0]) 111 dict_thash->cursor += 1; 112 break; 113 default: 114 msg_panic("%s: invalid function: %d", myname, function); 115 } 116 117 /* 118 * Return the entry under the cursor. 119 */ 120 if (dict_thash->cursor[0]) { 121 *key = dict_thash->cursor[0]->key; 122 *value = dict_thash->cursor[0]->value; 123 DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_SUCCESS); 124 } else { 125 *key = 0; 126 *value = 0; 127 DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_FAIL); 128 } 129 } 130 131 /* dict_thash_close - disassociate from data base */ 132 133 static void dict_thash_close(DICT *dict) 134 { 135 DICT_THASH *dict_thash = (DICT_THASH *) dict; 136 137 htable_free(dict_thash->table, myfree); 138 if (dict_thash->info) 139 myfree((char *) dict_thash->info); 140 if (dict->fold_buf) 141 vstring_free(dict->fold_buf); 142 dict_free(dict); 143 } 144 145 /* dict_thash_open - open flat text data base */ 146 147 DICT *dict_thash_open(const char *path, int open_flags, int dict_flags) 148 { 149 DICT_THASH *dict_thash; 150 VSTREAM *fp = 0; 151 struct stat st; 152 time_t before; 153 time_t after; 154 VSTRING *line_buffer = 0; 155 int lineno; 156 char *key; 157 char *value; 158 HTABLE *table; 159 HTABLE_INFO *ht; 160 161 /* 162 * Let the optimizer worry about eliminating redundant code. 163 */ 164 #define DICT_THASH_OPEN_RETURN(d) { \ 165 DICT *__d = (d); \ 166 if (fp != 0) \ 167 vstream_fclose(fp); \ 168 if (line_buffer != 0) \ 169 vstring_free(line_buffer); \ 170 return (__d); \ 171 } while (0) 172 173 /* 174 * Sanity checks. 175 */ 176 if (open_flags != O_RDONLY) 177 DICT_THASH_OPEN_RETURN(dict_surrogate(DICT_TYPE_THASH, path, 178 open_flags, dict_flags, 179 "%s:%s map requires O_RDONLY access mode", 180 DICT_TYPE_THASH, path)); 181 182 /* 183 * Read the flat text file into in-memory hash. Read the file again if it 184 * may have changed while we were reading. 185 */ 186 for (before = time((time_t *) 0); /* see below */ ; before = after) { 187 if ((fp = vstream_fopen(path, open_flags, 0644)) == 0) { 188 DICT_THASH_OPEN_RETURN(dict_surrogate(DICT_TYPE_THASH, path, 189 open_flags, dict_flags, 190 "open database %s: %m", path)); 191 } 192 if (line_buffer == 0) 193 line_buffer = vstring_alloc(100); 194 lineno = 0; 195 table = htable_create(13); 196 while (readlline(line_buffer, fp, &lineno)) { 197 198 /* 199 * Split on the first whitespace character, then trim leading and 200 * trailing whitespace from key and value. 201 */ 202 key = STR(line_buffer); 203 value = key + strcspn(key, " \t\r\n"); 204 if (*value) 205 *value++ = 0; 206 while (ISSPACE(*value)) 207 value++; 208 trimblanks(key, 0)[0] = 0; 209 trimblanks(value, 0)[0] = 0; 210 211 /* 212 * Enforce the "key whitespace value" format. Disallow missing 213 * keys or missing values. 214 */ 215 if (*key == 0 || *value == 0) { 216 msg_warn("%s, line %d: expected format: key whitespace value" 217 " -- ignoring this line", path, lineno); 218 continue; 219 } 220 if (key[strlen(key) - 1] == ':') 221 msg_warn("%s, line %d: record is in \"key: value\" format;" 222 " is this an alias file?", path, lineno); 223 224 /* 225 * Optionally fold the key. 226 */ 227 if (dict_flags & DICT_FLAG_FOLD_FIX) 228 lowercase(key); 229 230 /* 231 * Store the value under the key. Handle duplicates 232 * appropriately. 233 */ 234 if ((ht = htable_locate(table, key)) != 0) { 235 if (dict_flags & DICT_FLAG_DUP_IGNORE) { 236 /* void */ ; 237 } else if (dict_flags & DICT_FLAG_DUP_REPLACE) { 238 myfree(ht->value); 239 ht->value = mystrdup(value); 240 } else if (dict_flags & DICT_FLAG_DUP_WARN) { 241 msg_warn("%s, line %d: duplicate entry: \"%s\"", 242 path, lineno, key); 243 } else { 244 msg_fatal("%s, line %d: duplicate entry: \"%s\"", 245 path, lineno, key); 246 } 247 } else { 248 htable_enter(table, key, mystrdup(value)); 249 } 250 } 251 252 /* 253 * See if the source file is hot. 254 */ 255 if (fstat(vstream_fileno(fp), &st) < 0) 256 msg_fatal("fstat %s: %m", path); 257 if (vstream_fclose(fp)) 258 msg_fatal("read %s: %m", path); 259 fp = 0; /* DICT_THASH_OPEN_RETURN() */ 260 after = time((time_t *) 0); 261 if (st.st_mtime < before - 1 || st.st_mtime > after) 262 break; 263 264 /* 265 * Yes, it is hot. Discard the result and read the file again. 266 */ 267 htable_free(table, myfree); 268 if (msg_verbose > 1) 269 msg_info("pausing to let file %s cool down", path); 270 doze(300000); 271 } 272 273 /* 274 * Create the in-memory table. 275 */ 276 dict_thash = (DICT_THASH *) 277 dict_alloc(DICT_TYPE_THASH, path, sizeof(*dict_thash)); 278 dict_thash->dict.lookup = dict_thash_lookup; 279 dict_thash->dict.sequence = dict_thash_sequence; 280 dict_thash->dict.close = dict_thash_close; 281 dict_thash->dict.flags = dict_flags | DICT_FLAG_DUP_WARN | DICT_FLAG_FIXED; 282 if (dict_flags & DICT_FLAG_FOLD_FIX) 283 dict_thash->dict.fold_buf = vstring_alloc(10); 284 dict_thash->info = 0; 285 dict_thash->table = table; 286 dict_thash->dict.owner.uid = st.st_uid; 287 dict_thash->dict.owner.status = (st.st_uid != 0); 288 289 DICT_THASH_OPEN_RETURN(DICT_DEBUG (&dict_thash->dict)); 290 } 291