xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/dict_thash.c (revision 413d532bcc3f62d122e56d92e13ac64825a40baf)
1 /*	$NetBSD: dict_thash.c,v 1.1.1.2 2013/01/02 18:59:12 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;
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      * Sanity checks.
163      */
164     if (open_flags != O_RDONLY)
165 	return (dict_surrogate(DICT_TYPE_THASH, path, open_flags, dict_flags,
166 			       "%s:%s map requires O_RDONLY access mode",
167 			       DICT_TYPE_THASH, path));
168 
169     /*
170      * Read the flat text file into in-memory hash. Read the file again if it
171      * may have changed while we were reading.
172      */
173     for (before = time((time_t *) 0); /* see below */ ; before = after) {
174 	if ((fp = vstream_fopen(path, open_flags, 0644)) == 0) {
175 	    return (dict_surrogate(DICT_TYPE_THASH, path, open_flags, dict_flags,
176 				   "open database %s: %m", path));
177 	}
178 	if (line_buffer == 0)
179 	    line_buffer = vstring_alloc(100);
180 	lineno = 0;
181 	table = htable_create(13);
182 	while (readlline(line_buffer, fp, &lineno)) {
183 
184 	    /*
185 	     * Split on the first whitespace character, then trim leading and
186 	     * trailing whitespace from key and value.
187 	     */
188 	    key = STR(line_buffer);
189 	    value = key + strcspn(key, " \t\r\n");
190 	    if (*value)
191 		*value++ = 0;
192 	    while (ISSPACE(*value))
193 		value++;
194 	    trimblanks(key, 0)[0] = 0;
195 	    trimblanks(value, 0)[0] = 0;
196 
197 	    /*
198 	     * Enforce the "key whitespace value" format. Disallow missing
199 	     * keys or missing values.
200 	     */
201 	    if (*key == 0 || *value == 0) {
202 		msg_warn("%s, line %d: expected format: key whitespace value"
203 			 " -- ignoring this line", path, lineno);
204 		continue;
205 	    }
206 	    if (key[strlen(key) - 1] == ':')
207 		msg_warn("%s, line %d: record is in \"key: value\" format;"
208 			 " is this an alias file?", path, lineno);
209 
210 	    /*
211 	     * Optionally fold the key.
212 	     */
213 	    if (dict_flags & DICT_FLAG_FOLD_FIX)
214 		lowercase(key);
215 
216 	    /*
217 	     * Store the value under the key. Handle duplicates
218 	     * appropriately.
219 	     */
220 	    if ((ht = htable_locate(table, key)) != 0) {
221 		if (dict_flags & DICT_FLAG_DUP_IGNORE) {
222 		     /* void */ ;
223 		} else if (dict_flags & DICT_FLAG_DUP_REPLACE) {
224 		    myfree(ht->value);
225 		    ht->value = mystrdup(value);
226 		} else if (dict_flags & DICT_FLAG_DUP_WARN) {
227 		    msg_warn("%s, line %d: duplicate entry: \"%s\"",
228 			     path, lineno, key);
229 		} else {
230 		    msg_fatal("%s, line %d: duplicate entry: \"%s\"",
231 			      path, lineno, key);
232 		}
233 	    } else {
234 		htable_enter(table, key, mystrdup(value));
235 	    }
236 	}
237 
238 	/*
239 	 * See if the source file is hot.
240 	 */
241 	if (fstat(vstream_fileno(fp), &st) < 0)
242 	    msg_fatal("fstat %s: %m", path);
243 	if (vstream_fclose(fp))
244 	    msg_fatal("read %s: %m", path);
245 	after = time((time_t *) 0);
246 	if (st.st_mtime < before - 1 || st.st_mtime > after)
247 	    break;
248 
249 	/*
250 	 * Yes, it is hot. Discard the result and read the file again.
251 	 */
252 	htable_free(table, myfree);
253 	if (msg_verbose > 1)
254 	    msg_info("pausing to let file %s cool down", path);
255 	doze(300000);
256     }
257     vstring_free(line_buffer);
258 
259     /*
260      * Create the in-memory table.
261      */
262     dict_thash = (DICT_THASH *)
263 	dict_alloc(DICT_TYPE_THASH, path, sizeof(*dict_thash));
264     dict_thash->dict.lookup = dict_thash_lookup;
265     dict_thash->dict.sequence = dict_thash_sequence;
266     dict_thash->dict.close = dict_thash_close;
267     dict_thash->dict.flags = dict_flags | DICT_FLAG_DUP_WARN | DICT_FLAG_FIXED;
268     if (dict_flags & DICT_FLAG_FOLD_FIX)
269 	dict_thash->dict.fold_buf = vstring_alloc(10);
270     dict_thash->info = 0;
271     dict_thash->table = table;
272     dict_thash->dict.owner.uid = st.st_uid;
273     dict_thash->dict.owner.status = (st.st_uid != 0);
274 
275     return (DICT_DEBUG (&dict_thash->dict));
276 }
277