]> git.cworth.org Git - mnemon/blob - mnemon.c
.gitignore: ignore mnemon binary
[mnemon] / mnemon.c
1 /*
2  * Copyright © 2006 Carl Worth
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2, or (at your option)
7  * any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA."
17  */
18
19 /* for asprintf */
20 #define _GNU_SOURCE
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <stdarg.h>
24
25 #include <sys/types.h>
26 #include <dirent.h>
27 #include <errno.h>
28 #include <string.h>
29 #include <assert.h>
30
31 typedef struct _item {
32     int count;
33     char *challenge;
34     char *response;
35 } item_t;
36
37 typedef struct _bin {
38     int count;
39     int items_size;
40     int num_items;
41     item_t **items;
42 } bin_t;
43
44 typedef struct _category {
45     char *name;
46     int items_size;
47     int num_items;
48     item_t *items;
49 } category_t;
50
51 typedef struct _mnemon {
52     char *dir_name;
53
54     int categories_size;
55     int num_categories;
56     category_t *categories;
57
58     int bins_size;
59     int num_bins;
60     bin_t *bins;
61 } mnemon_t;
62
63 static void *
64 xmalloc (size_t size)
65 {
66     void *ret;
67
68     ret = malloc (size);
69     if (ret == NULL) {
70         fprintf (stderr, "Error: out of memory\n");
71         exit (1);
72     }
73
74     return ret;
75 }
76
77 static void *
78 xrealloc (void *ptr, size_t size)
79 {
80     void *ret;
81
82     ret = realloc (ptr, size);
83     if (ret == NULL) {
84         fprintf (stderr, "Error: out of memory\n");
85         exit (1);
86     }
87
88     return ret;
89 }
90
91 static char *
92 xstrdup (const char *s)
93 {
94     char *ret;
95
96     ret = strdup (s);
97     if (s == NULL) {
98         fprintf (stderr, "Error: out of memory\n");
99         exit (1);
100     }
101
102     return ret;
103 }
104
105 static void
106 xasprintf (char **strp, const char *fmt, ...)
107 {
108     va_list ap;
109     int ret;
110
111     va_start (ap, fmt);
112     ret = vasprintf (strp, fmt, ap);
113     va_end (ap);
114
115     if (ret < 0) {
116         fprintf (stderr, "Error: out of memory\n");
117         exit (1);
118     }
119 }
120
121 static void
122 item_init (item_t       *item,
123            int           count,
124            const char   *challenge,
125            const char   *response)
126 {
127     item->count = count;
128
129     item->challenge = xmalloc (strlen (challenge) + 1 +
130                                strlen (response) + 1);
131     item->response = item->challenge + strlen (challenge) + 1;
132
133     strcpy (item->challenge, challenge);
134     strcpy (item->response, response);
135 }
136
137 static void
138 item_fini (item_t *item)
139 {
140     /* item->response shares allocation with item->challenge, so
141      * doesn't require a separate call to free */
142     free (item->challenge);
143 }
144
145 static void
146 category_init (category_t *category,
147                const char *name)
148 {
149     category->name = xstrdup (name);
150     
151     category->items_size = 0;
152     category->num_items = 0;
153     category->items = NULL;
154 }
155
156 static void
157 category_fini (category_t *category)
158 {
159     int i;
160
161     for (i = 0; i < category->num_items; i++)
162         item_fini (&category->items[i]);
163
164     free (category->items);
165
166     free (category->name);
167 }
168
169 static void
170 category_grow (category_t *category)
171 {
172     if (category->items_size)
173         category->items_size *= 2;
174     else
175         category->items_size = 1;
176
177     category->items = xrealloc (category->items,
178                                 category->items_size * sizeof (item_t));
179 }
180
181 static item_t *
182 category_add_item (category_t   *category,
183                    int           count,
184                    const char   *challenge,
185                    const char   *response)
186 {
187     item_t *item;
188
189     if (category->num_items == category->items_size)
190         category_grow (category);
191
192     item = &category->items[category->num_items++];
193
194     item_init (item, count, challenge, response);
195
196     return item;
197 }
198
199 static void
200 category_print (category_t      *category,
201                 FILE            *file)
202 {
203     int i;
204     item_t *item;
205
206     for (i = 0; i < category->num_items; i++) {
207         item = &category->items[i];
208         if (i != 0)
209             fprintf (file, "\n");
210         fprintf (file, "%d\n%s\n%s\n",
211                  item->count,
212                  item->challenge,
213                  item->response);
214     }
215 }
216
217 static void
218 bin_init (bin_t *bin,
219           int    count)
220 {
221     bin->count = count;
222
223     bin->items_size = 0;
224     bin->num_items = 0;
225     bin->items = NULL;
226 }
227
228 static void
229 bin_fini (bin_t *bin)
230 {
231     free (bin->items);
232 }
233
234 static void
235 bin_grow (bin_t *bin)
236 {
237     if (bin->items_size)
238         bin->items_size *= 2;
239     else
240         bin->items_size = 1;
241
242     bin->items = xrealloc (bin->items,
243                            bin->items_size * sizeof (item_t*));
244 }
245
246 static void
247 bin_add_item (bin_t     *bin,
248               item_t    *item)
249 {
250     assert (item->count == bin->count);
251
252     if (bin->num_items == bin->items_size)
253         bin_grow (bin);
254
255     bin->items[bin->num_items++] = item;
256 }
257
258 static void
259 mnemon_init (mnemon_t *mnemon)
260 {
261     char *home;
262
263     home = getenv ("HOME");
264     if (home == NULL)
265         home = "";
266
267     xasprintf (&mnemon->dir_name, "%s/.mnemon", getenv ("HOME"));
268
269     mnemon->categories_size = 0;
270     mnemon->num_categories = 0;
271     mnemon->categories = NULL;
272
273     mnemon->bins_size = 0;
274     mnemon->num_bins = 0;
275     mnemon->bins = NULL;
276 }
277
278 static void
279 mnemon_fini (mnemon_t *mnemon)
280 {
281     int i;
282
283     for (i = 0; i < mnemon->num_bins; i++)
284         bin_fini (&mnemon->bins[i]);
285     free (mnemon->bins);
286
287     for (i = 0; i < mnemon->num_categories; i++)
288         category_fini (&mnemon->categories[i]);
289     free (mnemon->categories);
290
291     free (mnemon->dir_name);
292 }
293
294 static void
295 mnemon_categories_grow (mnemon_t *mnemon)
296 {
297     if (mnemon->categories_size)
298         mnemon->categories_size *= 2;
299     else
300         mnemon->categories_size = 1;
301
302     mnemon->categories = xrealloc (mnemon->categories,
303                                    mnemon->categories_size * sizeof (category_t));
304 }
305
306 static category_t *
307 mnemon_get_category (mnemon_t   *mnemon,
308                      const char *name)
309 {
310     int i;
311     category_t *category;
312
313     for (i = 0; i < mnemon->num_categories; i++)
314         if (strcmp (mnemon->categories[i].name, name) == 0)
315             return &mnemon->categories[i];
316
317     mnemon_categories_grow (mnemon);
318
319     category = &mnemon->categories[mnemon->num_categories++];
320
321     category_init (category, name);
322
323     return category;
324 }
325
326 static void
327 mnemon_bins_grow (mnemon_t *mnemon)
328 {
329     if (mnemon->bins_size)
330         mnemon->bins_size *= 2;
331     else
332         mnemon->bins_size = 1;
333
334     mnemon->bins = xrealloc (mnemon->bins,
335                              mnemon->bins_size * sizeof (bin_t));
336 }
337
338 static bin_t *
339 mnemon_get_bin (mnemon_t        *mnemon,
340                 int              count)
341 {
342     int i;
343     bin_t *bin;
344
345     for (i = 0; i < mnemon->num_bins; i++)
346         if (mnemon->bins[i].count == count)
347             return &mnemon->bins[i];
348         else if (mnemon->bins[i].count > count)
349             break;
350
351     mnemon_bins_grow (mnemon);
352
353     bin = &mnemon->bins[i];
354
355     /* Make room to insert new bin at its sorted location. */
356     memmove (bin + 1, bin, (mnemon->num_bins - i) * sizeof (bin_t));
357     mnemon->num_bins++;
358
359     bin_init (bin, count);
360
361     return bin;
362 }
363
364 static void
365 mnemon_add_item (mnemon_t       *mnemon,
366                  category_t     *category,
367                  int             count,
368                  const char     *challenge,
369                  const char     *response)
370 {
371     item_t *item;
372     bin_t *bin;
373
374     item = category_add_item (category, count, challenge, response);
375
376     bin = mnemon_get_bin (mnemon, count);
377
378     bin_add_item (bin, item);
379 }
380
381 static void
382 chomp (char *s)
383 {
384     int len = strlen (s);
385     if (len == 0)
386         return;
387     if (s[len - 1] == '\n')
388         s[len - 1] = '\0';
389 }
390
391 static void
392 mnemon_load_category (mnemon_t          *mnemon,
393                       const char        *name)
394 {
395     FILE *file;
396     char *line = NULL, *end;
397     size_t line_size = 0;
398     ssize_t bytes_read;
399     int line_count = 0;
400     char *path;
401     category_t *category;
402
403     path = xmalloc (strlen (mnemon->dir_name) + 1 + strlen (name) + 1);
404     sprintf (path, "%s/%s", mnemon->dir_name, name);
405
406     file = fopen (path, "r");
407     if (file == NULL) {
408         fprintf (stderr, "Error: Failed to open %s: %s\n",
409                  path, strerror (errno));
410         exit (1);
411     }
412
413     category = mnemon_get_category (mnemon, name);
414
415     while (1) {
416         int count;
417         char *challenge, *response;
418
419         /* Read bin number (ignoring blank separator lines) */
420         do {
421             bytes_read = getline (&line, &line_size, file);
422             if (bytes_read == -1)
423                 goto END_OF_FILE;
424             line_count++;
425             chomp (line);
426         } while (*line == '\0');
427
428         count = strtol (line, &end, 10);
429         if (*end != '\0') {
430             fprintf (stderr, "Failed to parse bin number from \"%s\" at %s:%d\n",
431                      line, path, line_count);
432             exit (1);
433         }
434
435         /* Read challenge */
436         bytes_read = getline (&line, &line_size, file);
437         if (bytes_read == -1)
438             break;
439         line_count++;
440         chomp (line);
441         challenge = strdup (line);
442
443         /* Read response */
444         bytes_read = getline (&line, &line_size, file);
445         if (bytes_read == -1)
446             break;
447         line_count++;
448         chomp (line);
449         response = line;
450
451         mnemon_add_item (mnemon, category, count, challenge, response);
452
453         free (challenge);
454     }
455   END_OF_FILE:
456
457     free (line);
458     fclose (file);
459     free (path);
460 }
461
462 static void
463 mnemon_load (mnemon_t *mnemon)
464 {
465     DIR *dir;
466     struct dirent *dirent;
467
468     dir = opendir (mnemon->dir_name);
469     if (dir == NULL) {
470         fprintf (stderr, "Error: Failed to open directory %s: %s\n",
471                  mnemon->dir_name, strerror (errno));
472         exit (1);
473     }
474
475     while (1) {
476         dirent = readdir (dir);
477         if (dirent == NULL)
478             break;
479
480         if (dirent->d_type == DT_REG) {
481             /* Ignore files matching *~, (yes, this shouldn't be
482              * hard-coded in such an ad-hoc way, but there you go. */
483             if (dirent->d_name[strlen(dirent->d_name)-1] != '~')
484                 mnemon_load_category (mnemon, dirent->d_name);
485         }
486     }
487
488     closedir (dir);
489 }
490
491 static void
492 mnemon_save (mnemon_t *mnemon)
493 {
494     int i, err;
495     char *filename, *lock_filename;
496     FILE *file;
497     category_t *category;
498
499     for (i = 0; i < mnemon->num_categories; i++) {
500         category = &mnemon->categories[i];
501
502         xasprintf (&filename, "%s/%s",
503                    mnemon->dir_name, category->name);
504         xasprintf (&lock_filename, "%s/.#%s",
505                    mnemon->dir_name, category->name);
506
507         file = fopen (lock_filename, "w");
508         if (file == NULL) {
509             fprintf (stderr, "Error: Failed to open %s for writing: %s\n",
510                      lock_filename, strerror (errno));
511             continue;
512         }
513
514         category_print (category, file);
515
516         fclose (file);
517
518         err = rename (lock_filename, filename);
519         if (err < 0) {
520             fprintf (stderr, "Error: Failes to rename %s to %s: %s\n",
521                      lock_filename, filename, strerror (errno));
522             continue;
523         }
524
525         free (filename);
526         free (lock_filename);
527     }
528 }
529
530 int
531 main (int argc, char *argv[])
532 {
533     mnemon_t mnemon;
534
535     mnemon_init (&mnemon);
536
537     mnemon_load (&mnemon);
538
539     mnemon_save (&mnemon);
540
541     mnemon_fini (&mnemon);
542
543     return 0;
544 }