]> Joshua Wise's Git repositories - tdl.git/blame - main.c
get 'make install' to work right on case-insensitive systems
[tdl.git] / main.c
CommitLineData
7024e37b
JW
1/*
2 $Header: /cvs/src/tdl/main.c,v 1.39.2.6 2004/02/03 22:17:22 richard Exp $
3
4 tdl - A console program for managing to-do lists
5 Copyright (C) 2001,2002,2003,2004,2005 Richard P. Curnow
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
20 */
21
22#include "tdl.h"
23#include "version.h"
24#include <assert.h>
25#include <string.h>
26#include <errno.h>
27#include <ctype.h>
28#include <fcntl.h>
29#include <sys/types.h>
30#include <sys/stat.h>
31#include <unistd.h>
32#include <signal.h>
33
34#ifdef USE_DOTLOCK
35#include <sys/utsname.h>
36#include <pwd.h>
37#endif
38
39/* The name of the database file (in whichever directory it may be) */
40#define DBNAME ".tdldb"
41
42/* Set if db doesn't exist in this directory */
43static char *current_database_path = NULL;
44
45/* The currently loaded database */
46struct links top;
47
48/* Flag for whether data is actually loaded yet */
49static int is_loaded = 0;
50
51#ifdef USE_DOTLOCK
52static char *lock_file_name = NULL;
53#endif
54
55/* Flag if currently loaded database has been changed and needs writing back to
56 * the filesystem */
57static int currently_dirty = 0;
58
59/* Flag indicating whether to load databases read only */
60static int read_only = 0;
61
62/* Whether to complain about problems with file operations */
63static int is_noisy = 1;
64
65/* Whether to forcibly unlock the database before the next lock attempt (e.g.
66 * if the program crashed for some reason before.) */
67static int forced_unlock = 0;
68
69static int is_interactive = 0;
70
71static void set_descendent_priority(struct node *x, enum Priority priority)/*{{{*/
72{
73 struct node *y;
74 for (y = x->kids.next; y != (struct node *) &x->kids; y = y->chain.next) {
75 y->priority = priority;
76 set_descendent_priority(y, priority);
77 }
78}
79/*}}}*/
80
81/* This will be variable eventually */
82static char default_database_path[] = "./" DBNAME;
83
84#ifdef USE_DOTLOCK
85static void unlock_database(void)/*{{{*/
86{
87 if (lock_file_name) unlink(lock_file_name);
88 return;
89}
90/*}}}*/
91static volatile void unlock_and_exit(int code)/*{{{*/
92{
93 unlock_database();
94 exit(code);
95}
96/*}}}*/
97static void lock_database(char *path)/*{{{*/
98{
99 struct utsname uu;
100 struct passwd *pw;
101 int pid;
102 int len;
103 char *tname;
104 struct stat sb;
105 FILE *out;
106
107 if (uname(&uu) < 0) {
108 perror("uname");
109 exit(1);
110 }
111 pw = getpwuid(getuid());
112 if (!pw) {
113 perror("getpwuid");
114 exit(1);
115 }
116 pid = getpid();
117 len = 1 + strlen(path) + 5;
118 lock_file_name = new_array(char, len);
119 sprintf(lock_file_name, "%s.lock", path);
120
121 if (forced_unlock) {
122 unlock_database();
123 forced_unlock = 0;
124 }
125
126 len += strlen(uu.nodename);
127 /* add on max width of pid field (allow up to 32 bit pid_t) + 2 '.' chars */
128 len += (10 + 2);
129 tname = new_array(char, len);
130 sprintf(tname, "%s.%d.%s", lock_file_name, pid, uu.nodename);
131 out = fopen(tname, "w");
132 if (!out) {
133 fprintf(stderr, "Cannot open lock file %s for writing\n", tname);
134 exit(1);
135 }
136 fprintf(out, "%d,%s,%s\n", pid, uu.nodename, pw->pw_name);
137 fclose(out);
138
139 if (link(tname, lock_file_name) < 0) {
140 /* check if link count==2 */
141 if (stat(tname, &sb) < 0) {
142 fprintf(stderr, "Could not stat the lock file\n");
143 unlink(tname);
144 exit(1);
145 } else {
146 if (sb.st_nlink != 2) {
147 FILE *in;
148 in = fopen(lock_file_name, "r");
149 if (in) {
150 char line[2048];
151 fgets(line, sizeof(line), in);
152 line[strlen(line)-1] = 0; /* strip trailing newline */
153 fprintf(stderr, "Database %s appears to be locked by (pid,node,user)=(%s)\n", path, line);
154 unlink(tname);
155 exit(1);
156 }
157 } else {
158 /* lock succeeded apparently */
159 }
160 }
161 } else {
162 /* lock succeeded apparently */
163 }
164 unlink(tname);
165 free(tname);
166 return;
167}
168/*}}}*/
169#else
170static volatile void unlock_and_exit(int code)/*{{{*/
171{
172 exit(code);
173}
174/*}}}*/
175#endif /* USE_DOTLOCK */
176
177static char *get_database_path(int traverse_up)/*{{{*/
178{
179 char *env_var;
180 env_var = getenv("TDL_DATABASE");
181 if (env_var) {
182 return env_var;
183 } else {
184 int at_root, orig_size, size, dbname_len, found, stat_result;
185 char *orig_cwd, *cwd, *result, *filename;
186 struct stat statbuf;
187
188 dbname_len = strlen(DBNAME);
189 size = 16;
190 orig_size = 16;
191 found = 0;
192 at_root = 0;
193 cwd = new_array(char, size);
194 orig_cwd = new_array(char, orig_size);
195 do {
196 result = getcwd(orig_cwd, orig_size);
197 if (!result) {
198 if (errno == ERANGE) {
199 orig_size <<= 1;
200 orig_cwd = grow_array(char, orig_size, orig_cwd);
201 } else {
202 fprintf(stderr, "Unexpected error reading current directory\n");
203 unlock_and_exit(1);
204 }
205 }
206 } while (!result);
207 filename = new_array(char, size + dbname_len + 2);
208 filename[0] = 0;
209 do {
210 result = getcwd(cwd, size);
211 if (!result && (errno == ERANGE)) {
212 size <<= 1;
213 cwd = grow_array(char, size, cwd);
214 filename = grow_array(char, size + dbname_len + 2, filename);
215 } else {
216 if (!strcmp(cwd, "/")) {
217 at_root = 1;
218 }
219 strcpy(filename, cwd);
220 strcat(filename, "/");
221 strcat(filename, DBNAME);
222 stat_result = stat(filename, &statbuf);
223 if ((stat_result >= 0) && (statbuf.st_mode & 0600)) {
224 found = 1;
225 break;
226 }
227
228 if (!traverse_up) break;
229
230 /* Otherwise, go up a level */
231 chdir ("..");
232 }
233 } while (!at_root);
234
235 free(cwd);
236
237 /* Reason for this : if using create in a subdirectory of a directory
238 * already containing a .tdldb, the cwd after the call here from main would
239 * get left pointing at the directory containing the .tdldb that already
240 * exists, making the call here from process_create() fail. So go back to
241 * the directory where we started. */
242 chdir(orig_cwd);
243 free(orig_cwd);
244
245 if (found) {
246 return filename;
247 } else {
248 return default_database_path;
249 }
250 }
251
252}
253/*}}}*/
254static void rename_database(char *path)/*{{{*/
255{
256 int len;
257 char *pathbak;
258
259 len = strlen(path);
260 pathbak = new_array(char, len + 5);
261 strcpy(pathbak, path);
262 strcat(pathbak, ".bak");
263 if (rename(path, pathbak) < 0) {
264 if (is_noisy) {
265 perror("warning, couldn't save backup database:");
266 }
267 }
268 free(pathbak);
269 return;
270}
271/*}}}*/
272static char *executable_name(char *argv0)/*{{{*/
273{
274 char *p;
275 for (p=argv0; *p; p++) ;
276 for (; p>=argv0; p--) {
277 if (*p == '/') return (p+1);
278 }
279 return argv0;
280}
281/*}}}*/
282static void load_database(char *path) /*{{{*/
283 /* Return 1 if successful, 0 if no database was found */
284{
285 FILE *in;
286 currently_dirty = 0;
287#ifdef USE_DOTLOCK
288 if (!read_only) {
289 lock_database(path);
290 }
291#endif
292 in = fopen(path, "rb");
293 if (in) {
294 /* Database may not exist, e.g. if the program has never been run before.
295 */
296 read_database(in, &top);
297 fclose(in);
298 is_loaded = 1;
299 } else {
300 if (is_noisy) {
301 fprintf(stderr, "warning: no database found above this directory\n");
302 }
303 }
304}
305/*}}}*/
306void load_database_if_not_loaded(void)/*{{{*/
307{
308 if (!is_loaded) {
309 load_database(current_database_path);
310 is_loaded = 1;
311 }
312}
313/*}}}*/
314static mode_t get_mode(const char *path)/*{{{*/
315{
316 mode_t result;
317 const mode_t default_result = 0600; /* access to user only. */
318 struct stat sb;
319 if (stat(path, &sb) < 0) {
320 result = default_result;
321 } else {
322 if (!S_ISREG(sb.st_mode)) {
323 fprintf(stderr, "Warning : existing database is not a regular file!\n");
324 result = default_result;
325 } else {
326 result = sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO);
327 }
328 }
329 return result;
330}
331/*}}}*/
332static void save_database(char *path)/*{{{*/
333{
334 FILE *out = NULL;
335 int out_fd;
336 mode_t database_mode;
337 if (read_only) {
338 fprintf(stderr, "Warning : database opened read-only. Not saving.\n");
339 return;
340 }
341 if (is_loaded && currently_dirty) {
342 database_mode = get_mode(path);
343
344 /* The next line only used to happen if the command wasn't 'create'.
345 * However, it should quietly fail for create, where the existing database
346 * doesn't exist */
347 rename_database(path);
348
349 /* Open database this way so that the permissions from the existing
350 database can be duplicated onto the new one in way free of race
351 conditions. */
352 out_fd = open(path, O_WRONLY | O_CREAT | O_EXCL, database_mode);
353 if (out_fd < 0) {
354 fprintf(stderr, "Could not open new database %s for writing : %s\n",
355 path, strerror(errno));
356 unlock_and_exit(1);
357 } else {
358 /* Normal case */
359 out = fdopen(out_fd, "wb");
360 }
361 if (!out) {
362 fprintf(stderr, "Cannot open database %s for writing\n", path);
363 unlock_and_exit(1);
364 }
365 write_database(out, &top);
366 fclose(out);
367 }
368 currently_dirty = 0;
369 return;
370}
371/*}}}*/
372void free_database(struct links *x)/*{{{*/
373{
374 struct node *y;
375 struct node *next;
376 for (y=x->next; y != (struct node *) x; y = next) {
377 free_database(&y->kids);
378 free(y->text);
379 next = y->chain.next;
380 free(y);
381 }
382 x->next = x->prev = (struct node *) x;
383}
384/*}}}*/
385
386/* {{{ One line descriptions of the subcommands */
387static char desc_above[] = "Move entries above (before) another entry";
388static char desc_add[] = "Add a new entry to the database";
389static char desc_after[] = "Move entries after (below) another entry";
390static char desc_before[] = "Move entries before (above) another entry";
391static char desc_below[] = "Move entries below (after) another entry";
392static char desc_clone[] = "Make deep copy of one or more entries";
393static char desc_copyto[] = "Insert deep copy of one or more entries under another entry";
394static char desc_create[] = "Create a new database in the current directory";
395static char desc_defer[] = "Put off starting some tasks until a given time";
396static char desc_delete[] = "Remove 1 or more entries from the database";
397static char desc_done[] = "Mark 1 or more entries as done";
398static char desc_edit[] = "Change the text of an entry";
399static char desc_exit[] = "Exit program, saving database";
400static char desc_export[] = "Export entries to another database";
401static char desc_help[] = "Display help information";
402static char desc_import[] = "Import entries from another database";
403static char desc_ignore[] = "Postpone or partially remove 1 or more entries";
404static char desc_into[] = "Move entries to end of new parent";
405static char desc_list[] = "List entries in database (default from top node)";
406static char desc_log[] = "Add a new entry to the database, mark it done as well";
407static char desc_moveto[] = "Move entries to end of new parent";
408static char desc_narrow[] = "Restrict actions to part of the database";
409static char desc_open[] = "Move one or more entries out of postponed/deferred state";
410static char desc_postpone[] = "Make one or more entries postponed indefinitely";
411static char desc_priority[] = "Change the priority of 1 or more entries";
412static char desc_purge[] = "Remove old done entries in subtrees";
413static char desc_quit[] = "Exit program, NOT saving database";
414static char desc_remove[] = "Remove 1 or more entries from the database";
415static char desc_report[] = "Report completed tasks in interval";
416static char desc_revert[] = "Discard changes and reload previous database from disc";
417static char desc_save[] = "Save the database back to disc and keep working";
418static char desc_undo[] = "Mark 1 or more entries as not done (cancel effect of 'done')";
419static char desc_usage[] = "Display help information";
420static char desc_version[] = "Display program version";
421static char desc_which[] = "Display filename of database being used";
422static char desc_widen[] = "Widen the part of the database to which actions apply";
423
424/* }}} */
425/* {{{ Synopsis of each subcommand */
426static char synop_above[] = "<index_to_insert_above> <index_to_move> ...";
427static char synop_add[] = "[@<datespec>] [<parent_index>] [<priority>] <entry_text>";
428static char synop_after[] = "<index_to_insert_below> <index_to_move> ...";
429static char synop_before[] = "<index_to_insert_above> <index_to_move> ...";
430static char synop_below[] = "<index_to_insert_below> <index_to_move> ...";
431static char synop_clone[] = "<index_to_clone> ...";
432static char synop_copyto[] = "<parent_index> <index_to_clone> ...";
433static char synop_create[] = "";
434static char synop_defer[] = "[@]<datespec> <entry_index>{...] ...";
435static char synop_delete[] = "<entry_index>[...] ...";
436static char synop_done[] = "[@<datespec>] <entry_index>[...] ...";
437static char synop_edit[] = "<entry_index> [<new_text>]";
438static char synop_exit[] = "";
439static char synop_export[] = "<filename> <entry_index> ...";
440static char synop_help[] = "[<command-name>]";
441static char synop_ignore[] = "<entry_index>[...] ...";
442static char synop_import[] = "<filename>";
443static char synop_into[] = "<new_parent_index> <index_to_move> ...";
444static char synop_list[] = "[-v] [-a] [-p] [-m] [-1..9] [<min-priority>] [<parent_index>|/<search_condition>...]\n"
445 "-v : verbose (show dates, priorities etc)\n"
446 "-a : show all entries, including 'done' ones\n"
447 "-p : show deferred and postponed entries\n"
448 "-m : don't use colours (monochrome)\n"
449 "-1,-2,..,-9 : summarise (and don't show) entries below this depth\n"
450 "<search_condition> : word to match on";
451static char synop_log[] = "[@<datespec>] [<parent_index>] [<priority>] <entry_text>";
452static char synop_moveto[] = "<new_parent_index> <index_to_move> ...";
453static char synop_narrow[] = "<entry_index>";
454static char synop_open[] = "<entry_index>[...] ...";
455static char synop_postpone[] = "<entry_index>[...] ...";
456static char synop_priority[] = "<new_priority> <entry_index>[...] ...";
457static char synop_purge[] = "<since_datespec> [<ancestor_index> ...]";
458static char synop_quit[] = "";
459static char synop_remove[] = "<entry_index>[...] ...";
460static char synop_report[] = "<start_datespec> [<end_datespec>]\n"
461 "(end defaults to now)";
462static char synop_revert[] = "";
463static char synop_save[] = "";
464static char synop_undo[] = "<entry_index>[...] ...";
465static char synop_usage[] = "[<command-name>]";
466static char synop_version[] = "";
467static char synop_which[] = "";
468static char synop_widen[] = "[<levels>]";
469/* }}} */
470
471static int process_create(char **x)/*{{{*/
472{
473 char *dbpath = get_database_path(0);
474 struct stat sb;
475 int result;
476
477 result = stat(dbpath, &sb);
478 if (result >= 0) {
479 fprintf(stderr, "Can't create database <%s>, it already exists!\n", dbpath);
480 return -1;
481 } else {
482 if (read_only) {
483 fprintf(stderr, "Can't create database <%s> in read-only mode!\n", dbpath);
484 return -1;
485 }
486 /* Should have an empty database, and the dirty flag will be set */
487 current_database_path = dbpath;
488 /* don't emit complaint about not being able to move database to its backup */
489 is_noisy = 0;
490 /* Force empty database to be written out */
491 is_loaded = currently_dirty = 1;
492 return 0;
493 }
494}
495/*}}}*/
496static int process_priority(char **x)/*{{{*/
497{
498 int error;
499 enum Priority priority;
500 struct node *n;
501 int do_descendents;
502
503 priority = parse_priority(*x, &error);
504 if (error < 0) {
505 fprintf(stderr, "usage: priority %s\n", synop_priority);
506 return error;
507 }
508
509 while (*++x) {
510 do_descendents = include_descendents(*x); /* May modify *x */
511 n = lookup_node(*x, 0, NULL);
512 if (!n) return -1;
513 n->priority = priority;
514 if (do_descendents) {
515 set_descendent_priority(n, priority);
516 }
517 }
518
519 return 0;
520}/*}}}*/
521static int process_which(char **argv)/*{{{*/
522{
523 printf("%s\n", current_database_path);
524 return 0;
525}
526/*}}}*/
527static int process_version(char **x)/*{{{*/
528{
529 fprintf(stderr, "tdl %s\n", PROGRAM_VERSION);
530 return 0;
531}
532/*}}}*/
533static int process_exit(char **x)/*{{{*/
534{
535 save_database(current_database_path);
536 free_database(&top);
537 unlock_and_exit(0);
538 return 0; /* moot */
539}
540/*}}}*/
541static int process_quit(char **x)/*{{{*/
542{
543 /* Just get out quick, don't write the database back */
544 char ans[4];
545 if (currently_dirty) {
546 printf(" WARNING: if you quit, all changes to database will be lost!\n"
547 " Use command 'exit' instead of 'quit' if you wish to save data.\n"
548 " Really quit [y/N]? ");
549 fgets (ans, 4, stdin);
550 if (strcasecmp(ans,"y\n") != 0) {
551 printf(" Quit canceled.\n");
552 return 0;
553 }
554 }
555 free_database(&top);
556 unlock_and_exit(0);
557 return 0; /* moot */
558}
559/*}}}*/
560static int process_save(char **x)/*{{{*/
561{
562 /* FIXME: I'm not sure whether the behaviour here should include renaming the
563 * existing disc database to become the backup file. I think the precedent
564 * would be how vi or emacs handle backup files when multiple saves are done
565 * within a session. */
566 save_database(current_database_path);
567 return 0;
568}
569/*}}}*/
570static int process_revert(char **x)/*{{{*/
571{
572 if (is_loaded) {
573 free_database(&top);
574 }
575 is_loaded = currently_dirty = 0;
576 return 0;
577}
578/*}}}*/
579/* Forward prototype */
580static int usage(char **x);
581
582struct command cmds[] = {/*{{{*/
583 {"--help", NULL, usage, desc_help, NULL, NULL, 0, 0, 3, 0, 1},
584 {"-h", NULL, usage, desc_help, NULL, NULL, 0, 0, 2, 0, 1},
585 {"-V", NULL, process_version, desc_version, NULL, NULL, 0, 0, 2, 0, 1},
586 {"above", NULL, process_above, desc_above, synop_above, NULL, 1, 1, 2, 1, 1},
587 {"add", "tdla", process_add, desc_add, synop_add, NULL, 1, 1, 2, 1, 1},
588 {"after", NULL, process_below, desc_after, synop_after, NULL, 1, 1, 2, 1, 1},
589 {"before", NULL, process_above, desc_before, synop_before, NULL, 1, 1, 3, 1, 1},
590 {"below", NULL, process_below, desc_below, synop_below, NULL, 1, 1, 3, 1, 1},
591 {"clone", NULL, process_clone, desc_clone, synop_clone, NULL, 1, 1, 2, 1, 1},
592 {"copyto", NULL, process_copyto, desc_copyto, synop_copyto, NULL, 1, 1, 2, 1, 1},
593 {"create", NULL, process_create, desc_create, synop_create, NULL, 1, 0, 2, 0, 1},
594 {"defer", NULL, process_defer, desc_defer, synop_defer, NULL, 1, 1, 3, 1, 1},
595 {"delete", NULL, process_remove, desc_delete, synop_delete, NULL, 1, 1, 3, 1, 1},
596 {"done", "tdld", process_done, desc_done, synop_done, complete_done, 1, 1, 2, 1, 1},
597 {"edit", NULL, process_edit, desc_edit, synop_edit, NULL, 1, 1, 2, 1, 1},
598 {"exit", NULL, process_exit, desc_exit, synop_exit, NULL, 0, 0, 3, 1, 0},
599 {"export", NULL, process_export, desc_export, synop_export, NULL, 0, 1, 3, 1, 1},
600 {"help", NULL, usage, desc_help, synop_help, complete_help, 0, 0, 1, 1, 1},
601 {"ignore", NULL, process_ignore, desc_ignore, synop_ignore, complete_done, 1, 1, 2, 1, 1},
602 {"import", NULL, process_import, desc_import, synop_import, NULL, 1, 1, 2, 1, 1},
603 {"into", NULL, process_into, desc_into, synop_into, NULL, 1, 1, 2, 1, 1},
604 {"list", "tdll", process_list, desc_list, synop_list, complete_list, 0, 1, 2, 1, 1},
605 {"ls", "tdls", process_list, desc_list, synop_list, complete_list, 0, 1, 2, 1, 1},
606 {"log", "tdlg", process_log, desc_log, synop_log, NULL, 1, 1, 2, 1, 1},
607 {"moveto", NULL, process_into, desc_moveto, synop_moveto, NULL, 1, 1, 1, 1, 1},
608 {"narrow", NULL, process_narrow, desc_narrow, synop_narrow, NULL, 0, 1, 1, 1, 0},
609 {"open", NULL, process_open, desc_open, synop_open, complete_open, 1, 1, 1, 1, 1},
610 {"postpone", NULL, process_postpone, desc_postpone,synop_postpone,complete_postpone, 1, 1, 2, 1, 1},
611 {"priority", NULL, process_priority, desc_priority,synop_priority,complete_priority, 1, 1, 2, 1, 1},
612 {"purge", NULL, process_purge, desc_purge, synop_purge, NULL, 1, 1, 2, 1, 1},
613 {"quit", NULL, process_quit, desc_quit, synop_quit, NULL, 0, 0, 1, 1, 0},
614 {"remove", NULL, process_remove, desc_remove, synop_remove, NULL, 1, 1, 3, 1, 1},
615 {"report", NULL, process_report, desc_report, synop_report, NULL, 0, 1, 3, 1, 1},
616 {"revert", NULL, process_revert, desc_revert, synop_revert, NULL, 0, 0, 3, 1, 0},
617 {"save", NULL, process_save, desc_save, synop_save, NULL, 0, 1, 1, 1, 0},
618 {"undo", NULL, process_undo, desc_undo, synop_undo, NULL, 1, 1, 2, 1, 1},
619 {"usage", NULL, usage, desc_usage, synop_usage, complete_help, 0, 0, 2, 1, 1},
620 {"version", NULL, process_version, desc_version, synop_version, NULL, 0, 0, 1, 1, 1},
621 {"which", NULL, process_which, desc_which, synop_which, NULL, 0, 0, 2, 1, 1},
622 {"widen", NULL, process_widen, desc_widen, synop_widen, NULL, 0, 1, 2, 1, 0}
623};/*}}}*/
624int n_cmds = 0;
625
626#define N(x) (sizeof(x) / sizeof(x[0]))
627
628static int is_processing = 0;
629static int signal_count = 0;
630
631static void handle_signal(int a)/*{{{*/
632{
633 signal_count++;
634 /* And close stdin, which should cause readline() in inter.c to return
635 * immediately if it was active when the signal arrived. */
636 close(0);
637
638 if (signal_count == 3) {
639 /* User is desperately hitting ^C to stop the program. Bail out without tidying up, and give a warning. */
640 static char msg[] = "About to force exit due to repeated termination signals.\n"
641 "Database changes since the last save will be LOST.\n";
642 write(2, msg, strlen(msg));
643 }
644 if (signal_count == 4) {
645 static char msg[] = "The database may be left locked.\n"
646 "You will need to run 'tdl -u' next time to unlock it.\n";
647 write(2, msg, strlen(msg));
648 exit(1);
649 }
650}
651/*}}}*/
652static void guarded_sigaction(int signum, struct sigaction *sa)/*{{{*/
653{
654 if (sigaction(signum, sa, NULL) < 0) {
655 perror("sigaction");
656 unlock_and_exit(1);
657 }
658}
659/*}}}*/
660static void setup_signals(void)/*{{{*/
661{
662 struct sigaction sa;
663 if (sigemptyset(&sa.sa_mask) < 0) {
664 perror("sigemptyset");
665 unlock_and_exit(1);
666 }
667 sa.sa_handler = handle_signal;
668 sa.sa_flags = 0;
669#if 0
670 guarded_sigaction(SIGHUP, &sa);
671 guarded_sigaction(SIGINT, &sa);
672 guarded_sigaction(SIGQUIT, &sa);
673 guarded_sigaction(SIGTERM, &sa);
674#endif
675
676 return;
677}
678/*}}}*/
679static void print_copyright(void)/*{{{*/
680{
681 fprintf(stderr,
682 "tdl %s, Copyright (C) 2001,2002,2003,2004,2005 Richard P. Curnow\n"
683 "tdl comes with ABSOLUTELY NO WARRANTY.\n"
684 "This is free software, and you are welcome to redistribute it\n"
685 "under certain conditions; see the GNU General Public License for details.\n\n",
686 PROGRAM_VERSION);
687}
688/*}}}*/
689void dispatch(char **argv) /* and other args *//*{{{*/
690{
691 int i, p_len, matchlen, index=-1;
692 char *executable;
693 int is_tdl;
694 char **p, **pp;
695
696 if (signal_count > 0) {
697 save_database(current_database_path);
698 unlock_and_exit(0);
699 }
700
701 executable = executable_name(argv[0]);
702 is_tdl = (!strcmp(executable, "tdl"));
703
704 p = argv + 1;
705 while (*p && *p[0] == '-') p++;
706
707 /* Parse command line */
708 if (is_tdl && !*p) {
709 /* If no arguments, go into interactive mode, but only if we didn't come from there (!) */
710 if (!is_interactive) {
711 setup_signals();
712 print_copyright();
713 is_interactive = 1;
714 interactive();
715 }
716 return;
717 }
718
719 if (is_tdl) {
720 p_len = strlen(*p);
721 for (i=0; i<n_cmds; i++) {
722 matchlen = p_len < cmds[i].matchlen ? cmds[i].matchlen : p_len;
723 if ((is_interactive ? cmds[i].interactive_ok : cmds[i].non_interactive_ok) &&
724 !strncmp(cmds[i].name, *p, matchlen)) {
725 index = i;
726 break;
727 }
728 }
729 } else {
730 for (i=0; i<n_cmds; i++) {
731 if (cmds[i].shortcut && !strcmp(cmds[i].shortcut, executable)) {
732 index = i;
733 break;
734 }
735 }
736 }
737
738 /* Skip commands that dirty the database, if it was opened read-only. */
739 if (index >= 0 && cmds[index].dirty && read_only) {
740 fprintf(stderr, "Can't use command <%s> in read-only mode\n",
741 cmds[index].name);
742 if (!is_interactive) {
743 unlock_and_exit(-1);
744 }
745 } else if (index >= 0) {
746 int result;
747
748 is_processing = 1;
749
750 if (!is_loaded && cmds[index].load_db) {
751 load_database(current_database_path);
752 }
753
754 pp = is_tdl ? (p + 1) : p;
755 result = (cmds[index].func)(pp);
756
757 /* Check for failure */
758 if (result < 0) {
759 if (!is_interactive) {
760 unlock_and_exit(-result);
761 }
762
763 /* If interactive, the handling function has emitted its error message.
764 * Just 'abort' this command and go back to the prompt */
765
766 } else {
767
768 if (cmds[index].dirty) {
769 currently_dirty = 1;
770 }
771 }
772
773 is_processing = 0;
774 if (signal_count > 0) {
775 save_database(current_database_path);
776 unlock_and_exit(0);
777 }
778
779 } else {
780 if (is_tdl && *p) {
781 fprintf(stderr, "tdl: Unknown command <%s>\n", *p);
782 } else {
783 fprintf(stderr, "tdl: Unknown command\n");
784 }
785 if (!is_interactive) {
786 unlock_and_exit(1);
787 }
788 }
789
790}
791/*}}}*/
792static int usage(char **x)/*{{{*/
793{
794 int i, index;
795 char *cmd = *x;
796
797 if (cmd) {
798 /* Detailed help for the one command */
799 index = -1;
800 for (i=0; i<n_cmds; i++) {
801 if (!strncmp(cmds[i].name, cmd, 3) &&
802 (is_interactive ? cmds[i].interactive_ok : cmds[i].non_interactive_ok)) {
803 index = i;
804 break;
805 }
806 }
807 if (index >= 0) {
808 fprintf(stdout, "Description\n %s\n\n", cmds[i].descrip);
809 fprintf(stdout, "Synopsis\n");
810
811 if (is_interactive) {
812 fprintf(stdout, " %s %s\n", cmds[i].name, cmds[i].synopsis ? cmds[i].synopsis : "");
813 } else {
814 fprintf(stdout, " tdl [-qR] %s %s\n", cmds[i].name, cmds[i].synopsis ? cmds[i].synopsis : "");
815 if (cmds[i].shortcut) {
816 fprintf(stdout, " %s [-qR] %s\n", cmds[i].shortcut, cmds[i].synopsis ? cmds[i].synopsis : "");
817 }
818 }
819
820 fprintf(stdout,
821 "\n"
822 "General notes (where they apply to a command):\n"
823 "\n"
824 "<*_index> : 1, 1.1 etc (see output of 'tdl list')\n"
825 "<priority> : urgent|high|normal|low|verylow\n"
826 "<datespec> : [-|+][0-9]+[shdwmy][-hh[mm[ss]]] OR\n"
827 " [-|+](sun|mon|tue|wed|thu|fri|sat)[-hh[mm[ss]]] OR\n"
828 " [[[cc]yy]mm]dd[-hh[mm[ss]]]\n"
829 "<text> : Any text (you'll need to quote it if >1 word)\n"
830 );
831
832 } else {
833 fprintf(stderr, "Unrecognized command <%s>, no help available\n", cmd);
834 }
835
836 } else {
837 print_copyright();
838
839 if (!is_interactive) {
840 fprintf(stdout, "tdl [-qR] : Enter interactive mode\n");
841 }
842 for (i=0; i<n_cmds; i++) {
843 if (is_interactive) {
844 if (cmds[i].interactive_ok) {
845 fprintf(stdout, "%-8s : %s\n", cmds[i].name, cmds[i].descrip);
846 }
847 } else {
848 if (cmds[i].non_interactive_ok) {
849 fprintf(stdout, "tdl [-qR] %-8s : %s\n", cmds[i].name, cmds[i].descrip);
850 if (cmds[i].shortcut) {
851 fprintf(stdout, "%s [-qR] : %s\n", cmds[i].shortcut, cmds[i].descrip);
852 }
853 }
854 }
855 }
856 if (is_interactive) {
857 fprintf(stdout, "\nEnter 'help <command-name>' for more help on a particular command\n");
858 } else {
859 fprintf(stdout, "\nEnter 'tdl help <command-name>' for more help on a particular command\n");
860 }
861 }
862
863 fprintf(stdout, "\n");
864
865 return 0;
866
867}
868/*}}}*/
869
870/*{{{ int main (int argc, char **argv)*/
871int main (int argc, char **argv)
872{
873 int i = 0;
874 n_cmds = N(cmds);
875 is_interactive = 0;
876
877 /* Initialise database */
878 top.prev = (struct node *) &top;
879 top.next = (struct node *) &top;
880
881 if (argc > 1) {
882 for (i=1; i<argc && argv[i][0] == '-'; i++) {
883 if (strspn(argv[i]+1, "qRu")+1 != strlen(argv[i])) {
884 fprintf(stderr, "Unknown flag <%s>\n", argv[i]);
885 return 1;
886 }
887
888 if (strchr(argv[i], 'q')) {
889 is_noisy = 0;
890 }
891 if (strchr(argv[i], 'R')) {
892 read_only = 1;
893 }
894 if (strchr(argv[i], 'u')) {
895 forced_unlock = 1;
896 }
897 }
898 }
899
900 current_database_path = get_database_path(1);
901
902 dispatch(argv);
903
904 if (! read_only) {
905 save_database(current_database_path);
906 }
907 free_database(&top);
908 unlock_and_exit(0);
909 return 0; /* moot */
910}
911/*}}}*/
912
This page took 0.117579 seconds and 4 git commands to generate.