/* * Calcurse - text-based organizer * * Copyright (c) 2004-2016 calcurse Development Team * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Send your feedback or comments to : misc@calcurse.org * Calcurse home page : http://calcurse.org * */ #include #include #include #include #include #include #include #include #include #include #include #include #include "calcurse.h" #include "sha1.h" #define ISLEAP(y) ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0) #define FS_EXT_MAXLEN 64 enum format_specifier { FS_STARTDATE, FS_DURATION, FS_ENDDATE, FS_REMAINING, FS_MESSAGE, FS_NOTE, FS_NOTEFILE, FS_PRIORITY, FS_RAW, FS_HASH, FS_PSIGN, FS_EOF, FS_UNKNOWN }; /* General routine to exit calcurse properly. */ void exit_calcurse(int status) { int was_interactive; if (ui_mode == UI_CURSES) { notify_stop_main_thread(); clear(); wins_refresh(); endwin(); ui_mode = UI_CMDLINE; was_interactive = 1; } else { was_interactive = 0; } ui_calendar_stop_date_thread(); io_stop_psave_thread(); free_user_data(); keys_free(); mem_stats(); if (was_interactive) { if (unlink(path_cpid) != 0) EXIT(_("Could not remove calcurse lock file: %s\n"), strerror(errno)); if (dmon.enable) dmon_start(status); } exit(status); } void free_user_data(void) { unsigned i; day_free_vector(); event_llist_free(); apoint_llist_free(); recur_apoint_llist_free(); recur_event_llist_free(); for (i = 0; i <= 37; i++) ui_day_item_cut_free(i); todo_free_list(); notify_free_app(); } /* Function to exit on internal error. */ void fatalbox(const char *errmsg) { WINDOW *errwin; const char *label = _("/!\\ INTERNAL ERROR /!\\"); const char *reportmsg = _("Please report the following bug:"); const int WINROW = 10; const int WINCOL = col - 2; const int MSGLEN = WINCOL - 2; char msg[MSGLEN]; if (errmsg == NULL) return; strncpy(msg, errmsg, MSGLEN); errwin = newwin(WINROW, WINCOL, (row - WINROW) / 2, (col - WINCOL) / 2); custom_apply_attr(errwin, ATTR_HIGHEST); box(errwin, 0, 0); wins_show(errwin, label); mvwaddstr(errwin, 3, 1, reportmsg); mvwaddstr(errwin, 5, (WINCOL - strlen(msg)) / 2, msg); custom_remove_attr(errwin, ATTR_HIGHEST); wins_wrefresh(errwin); wgetch(errwin); delwin(errwin); wins_doupdate(); } void warnbox(const char *msg) { WINDOW *warnwin; const char *label = "/!\\"; const int WINROW = 10; const int WINCOL = col - 2; const int MSGLEN = WINCOL - 2; char displmsg[MSGLEN]; if (msg == NULL) return; strncpy(displmsg, msg, MSGLEN); warnwin = newwin(WINROW, WINCOL, (row - WINROW) / 2, (col - WINCOL) / 2); custom_apply_attr(warnwin, ATTR_HIGHEST); box(warnwin, 0, 0); wins_show(warnwin, label); mvwaddstr(warnwin, 5, (WINCOL - strlen(displmsg)) / 2, displmsg); custom_remove_attr(warnwin, ATTR_HIGHEST); wins_wrefresh(warnwin); wgetch(warnwin); delwin(warnwin); wins_doupdate(); } /* * Print a message in the status bar. * Message texts for first line and second line are to be provided. */ void status_mesg(const char *msg1, const char *msg2) { wins_erase_status_bar(); custom_apply_attr(win[STA].p, ATTR_HIGHEST); mvwaddstr(win[STA].p, 0, 0, msg1); mvwaddstr(win[STA].p, 1, 0, msg2); custom_remove_attr(win[STA].p, ATTR_HIGHEST); wins_wrefresh(win[STA].p); } /* * Prompts the user to make a choice between named alternatives. * * The available choices are described by a string of the form * "[ynp]". The first and last char are ignored (they are only here to * make the translators' life easier), and every other char indicates * a key the user is allowed to press. * * Returns the index of the key pressed by the user (starting from 1), * or -1 if the user doesn't want to answer (e.g. by escaping). */ int status_ask_choice(const char *message, const char choice[], int nb_choice) { /* "[4/2/f/t/w/.../Z] " */ char avail_choice[2 * nb_choice + 3]; int i, ch; avail_choice[0] = '['; for (i = 1; i <= nb_choice; i++) { avail_choice[i * 2 - 1] = choice[i]; avail_choice[i * 2] = '/'; } avail_choice[nb_choice * 2] = ']'; avail_choice[nb_choice * 2 + 1] = '\0'; status_mesg(message, avail_choice); for (;;) { ch = wgetch(win[KEY].p); for (i = 1; i <= nb_choice; i++) if (ch == choice[i]) return i; if (ch == ESCAPE) return (-1); if (resize) { resize = 0; wins_reset(); status_mesg(message, avail_choice); } } } /* * Prompts the user with a boolean question. * * Returns 1 if yes, 2 if no, and -1 otherwise */ int status_ask_bool(const char *msg) { return (status_ask_choice(msg, _("[yn]"), 2)); } /* * Prompts the user to make a choice between a number of alternatives. * * Returns the option chosen by the user (starting from 1), or -1 if * the user doesn't want to answer. */ int status_ask_simplechoice(const char *prefix, const char *choice[], int nb_choice) { int i; char *tmp; /* "(1) Choice1, (2) Choice2, (3) Choice3?" */ char choicestr[BUFSIZ]; /* Holds the characters to choose from ('1', '2', etc) */ char char_choice[nb_choice + 2]; /* No need to initialize first and last char. */ for (i = 1; i <= nb_choice; i++) char_choice[i] = '0' + i; strcpy(choicestr, prefix); for (i = 0; i < nb_choice; i++) { asprintf(&tmp, ((i + 1) == nb_choice) ? "(%d) %s?" : "(%d) %s, ", (i + 1), choice[i]); strcat(choicestr, tmp); mem_free(tmp); } return (status_ask_choice(choicestr, char_choice, nb_choice)); } /* Erase part of a window. */ void erase_window_part(WINDOW * win, int first_col, int first_row, int last_col, int last_row) { int c, r; for (r = first_row; r <= last_row; r++) { for (c = first_col; c <= last_col; c++) mvwaddstr(win, r, c, " "); } } /* draws a popup window */ WINDOW *popup(int pop_row, int pop_col, int pop_y, int pop_x, const char *title, const char *msg, int hint) { const char *any_key = _("Press any key to continue..."); WINDOW *popup_win; const int MSGXPOS = 5; popup_win = newwin(pop_row, pop_col, pop_y, pop_x); keypad(popup_win, TRUE); if (msg) mvwaddstr(popup_win, MSGXPOS, (pop_col - strlen(msg)) / 2, msg); custom_apply_attr(popup_win, ATTR_HIGHEST); box(popup_win, 0, 0); wins_show(popup_win, title); if (hint) mvwaddstr(popup_win, pop_row - 2, pop_col - (strlen(any_key) + 1), any_key); custom_remove_attr(popup_win, ATTR_HIGHEST); wins_wrefresh(popup_win); return popup_win; } /* prints in middle of a panel */ void print_in_middle(WINDOW * win, int starty, int startx, int width, const char *string) { int len = strlen(string); int x, y; win = win ? win : stdscr; getyx(win, y, x); x = startx ? startx : x; y = starty ? starty : y; width = width ? width : 80; x += (width - len) / 2; custom_apply_attr(win, ATTR_HIGHEST); mvwaddstr(win, y, x, string); custom_remove_attr(win, ATTR_HIGHEST); } /* checks if a string is only made of digits */ int is_all_digit(const char *string) { for (; *string; string++) { if (!isdigit((int)*string)) return 0; } return 1; } /* Given an item date expressed in seconds, return its start time in seconds. */ long get_item_time(long date) { return (long)(get_item_hour(date) * HOURINSEC + get_item_min(date) * MININSEC); } int get_item_hour(long date) { struct tm lt; localtime_r((time_t *) & date, <); return lt.tm_hour; } int get_item_min(long date) { struct tm lt; localtime_r((time_t *) & date, <); return lt.tm_min; } struct tm date2tm(struct date day, unsigned hour, unsigned min) { time_t t = now(); struct tm start; localtime_r(&t, &start); start.tm_mon = day.mm - 1; start.tm_mday = day.dd; start.tm_year = day.yyyy - 1900; start.tm_hour = hour; start.tm_min = min; start.tm_sec = 0; start.tm_isdst = -1; return start; } time_t date2sec(struct date day, unsigned hour, unsigned min) { struct tm start = date2tm(day, hour, min); time_t t = mktime(&start); EXIT_IF(t == -1, _("failure in mktime")); return t; } time_t utcdate2sec(struct date day, unsigned hour, unsigned min) { char *tz; time_t t; tz = getenv("TZ"); if (tz) tz = mem_strdup(tz); setenv("TZ", "", 1); tzset(); t = date2sec(day, hour, min); if (tz) { setenv("TZ", tz, 1); mem_free(tz); } else { unsetenv("TZ"); } tzset(); return t; } /* Return a string containing the date, given a date in seconds. */ char *date_sec2date_str(long sec, const char *datefmt) { struct tm lt; char *datestr = (char *)mem_calloc(BUFSIZ, sizeof(char)); if (sec == 0) { strncpy(datestr, "0", BUFSIZ); } else { localtime_r((time_t *) & sec, <); strftime(datestr, BUFSIZ, datefmt, <); } return datestr; } /* Generic function to format date. */ void date_sec2date_fmt(long sec, const char *fmt, char *datef) { #if ENABLE_NLS /* TODO: Find a better way to deal with localization and strftime(). */ char *locale_old = mem_strdup(setlocale(LC_ALL, NULL)); setlocale(LC_ALL, "C"); #endif struct tm lt; localtime_r((time_t *) & sec, <); strftime(datef, BUFSIZ, fmt, <); #if ENABLE_NLS setlocale(LC_ALL, locale_old); mem_free(locale_old); #endif } /* * Used to change date by adding a certain amount of days or weeks. */ long date_sec_change(long date, int delta_month, int delta_day) { struct tm lt; time_t t; t = date; localtime_r(&t, <); lt.tm_mon += delta_month; lt.tm_mday += delta_day; lt.tm_isdst = -1; t = mktime(<); EXIT_IF(t == -1, _("failure in mktime")); return t; } /* * Return a long containing the date which is updated taking into account * the new time and date entered by the user. */ long update_time_in_date(long date, unsigned hr, unsigned mn) { struct tm lt; time_t t, new_date; t = date; localtime_r(&t, <); lt.tm_hour = hr; lt.tm_min = mn; new_date = mktime(<); EXIT_IF(new_date == -1, _("error in mktime")); return new_date; } /* * Returns the date in seconds from year 1900. * If no date is entered, current date is chosen. */ time_t get_sec_date(struct date date) { struct tm ptrtime; time_t timer; char current_day[] = "dd "; char current_month[] = "mm "; char current_year[] = "yyyy "; if (date.yyyy == 0 && date.mm == 0 && date.dd == 0) { timer = time(NULL); localtime_r(&timer, &ptrtime); strftime(current_day, strlen(current_day), "%d", &ptrtime); strftime(current_month, strlen(current_month), "%m", &ptrtime); strftime(current_year, strlen(current_year), "%Y", &ptrtime); date.mm = atoi(current_month); date.dd = atoi(current_day); date.yyyy = atoi(current_year); } return date2sec(date, 0, 0); } long min2sec(unsigned minutes) { return minutes * MININSEC; } /* * Display a scroll bar when there are so many items that they * can not be displayed inside the corresponding panel. */ void draw_scrollbar(WINDOW * win, int y, int x, int length, int bar_top, int bar_bottom, unsigned hilt) { mvwvline(win, bar_top, x, ACS_VLINE, bar_bottom - bar_top + 1); if (hilt) custom_apply_attr(win, ATTR_HIGHEST); wattron(win, A_REVERSE); mvwvline(win, y, x, ' ', length); wattroff(win, A_REVERSE); if (hilt) custom_remove_attr(win, ATTR_HIGHEST); } /* * Print an item (either an appointment, event, or todo) in a * popup window. This is useful if an item description is too * long to fit in its corresponding panel window. */ void item_in_popup(const char *a_start, const char *a_end, const char *msg, const char *pop_title) { WINDOW *popup_win, *pad; const int margin_left = 4, margin_top = 4; const int winl = row - 5, winw = col - margin_left; const int padl = winl - 2, padw = winw - margin_left; pad = newpad(padl, padw); popup_win = popup(winl, winw, 1, 2, pop_title, NULL, 1); if (a_start && a_end) { mvwprintw(popup_win, margin_top, margin_left, "- %s -> %s", a_start, a_end); } mvwaddstr(pad, 0, margin_left, msg); wmove(win[STA].p, 0, 0); pnoutrefresh(pad, 0, 0, margin_top + 2, margin_left, padl, winw); wins_doupdate(); wgetch(popup_win); delwin(pad); delwin(popup_win); } /* Returns the beginning of current day in seconds from 1900. */ time_t get_today(void) { struct tm lt; time_t current_time; struct date day; current_time = time(NULL); localtime_r(¤t_time, <); day.mm = lt.tm_mon + 1; day.dd = lt.tm_mday; day.yyyy = lt.tm_year + 1900; return date2sec(day, 0, 0); } /* Returns the current time in seconds. */ long now(void) { return (long)time(NULL); } char *nowstr(void) { struct tm lt; static char buf[BUFSIZ]; time_t t = now(); localtime_r(&t, <); strftime(buf, sizeof buf, "%a %b %d %T %Y", <); return buf; } /* Print the given option value with appropriate color. */ void print_bool_option_incolor(WINDOW * win, unsigned option, int pos_y, int pos_x) { int color = 0; const char *option_value; if (option == 1) { color = ATTR_TRUE; option_value = _("yes"); } else if (option == 0) { color = ATTR_FALSE; option_value = _("no"); } else { EXIT(_("option not defined")); } custom_apply_attr(win, color); mvwaddstr(win, pos_y, pos_x, option_value); custom_remove_attr(win, color); wnoutrefresh(win); wins_doupdate(); } /* * Get the name of the default directory for temporary files. */ const char *get_tempdir(void) { if (getenv("TMPDIR")) return getenv("TMPDIR"); #ifdef P_tmpdir else if (P_tmpdir) return P_tmpdir; #endif else return "/tmp"; } /* * Create a new unique file, and return a newly allocated string which contains * the random part of the file name. */ char *new_tempfile(const char *prefix) { char *fullname; int fd; FILE *file; if (prefix == NULL) return NULL; asprintf(&fullname, "%s.XXXXXX", prefix); if ((fd = mkstemp(fullname)) == -1 || (file = fdopen(fd, "w+")) == NULL) { if (fd != -1) { unlink(fullname); close(fd); } ERROR_MSG(_("temporary file \"%s\" could not be created"), fullname); mem_free(fullname); return NULL; } fclose(file); return fullname; } static void get_ymd(int *year, int *month, int *day, time_t t) { struct tm tm; localtime_r(&t, &tm); *day = tm.tm_mday; *month = tm.tm_mon + 1; *year = tm.tm_year + 1900; } static void get_weekday_ymd(int *year, int *month, int *day, int weekday) { time_t t = get_today(); struct tm tm; int delta; localtime_r(&t, &tm); delta = weekday - tm.tm_wday; t = date_sec_change(t, 0, delta > 0 ? delta : 7); localtime_r(&t, &tm); *day = tm.tm_mday; *month = tm.tm_mon + 1; *year = tm.tm_year + 1900; } /* * Check if a date is valid. */ int check_date(unsigned year, unsigned month, unsigned day) { return (year >= 1902 && month >= 1 && month <= 12 && day >= 1 && day <= days[month - 1] + (month == 2 && ISLEAP(year)) ? 1 : 0); } /* * Convert a string containing a date into three integers containing the year, * month and day. * * If a pointer to a date structure containing the current date is passed as * last parameter ("slctd_date"), the function will accept several short forms, * e.g. "26" for the 26th of the current month/year or "3/1" for Mar 01 (or Jan * 03, depending on the date format) of the current year. If a null pointer is * passed, short forms won't be accepted at all. * * Returns 1 if sucessfully converted or 0 if the string is an invalid date. */ int parse_date(const char *date_string, enum datefmt datefmt, int *year, int *month, int *day, struct date *slctd_date) { const char sep = (datefmt == DATEFMT_ISO) ? '-' : '/'; const char *p; int in[3] = { 0, 0, 0 }, n = 0; int d, m, y; if (!date_string) return 0; if (!strcasecmp(date_string, "today")) { get_ymd(year, month, day, get_today()); return 1; } else if (!strcasecmp(date_string, "yesterday")) { get_ymd(year, month, day, date_sec_change(get_today(), 0, -1)); return 1; } else if (!strcasecmp(date_string, "tomorrow")) { get_ymd(year, month, day, date_sec_change(get_today(), 0, 1)); return 1; } else if (!strcasecmp(date_string, "now")) { get_ymd(year, month, day, now()); return 1; } else if (!strcasecmp(date_string, "sunday") || !strcasecmp(date_string, "sun")) { get_weekday_ymd(year, month, day, 0); return 1; } else if (!strcasecmp(date_string, "monday") || !strcasecmp(date_string, "mon")) { get_weekday_ymd(year, month, day, 1); return 1; } else if (!strcasecmp(date_string, "tuesday") || !strcasecmp(date_string, "tue")) { get_weekday_ymd(year, month, day, 2); return 1; } else if (!strcasecmp(date_string, "wednesday") || !strcasecmp(date_string, "wed")) { get_weekday_ymd(year, month, day, 3); return 1; } else if (!strcasecmp(date_string, "thursday") || !strcasecmp(date_string, "thu")) { get_weekday_ymd(year, month, day, 4); return 1; } else if (!strcasecmp(date_string, "friday") || !strcasecmp(date_string, "fri")) { get_weekday_ymd(year, month, day, 5); return 1; } else if (!strcasecmp(date_string, "saturday") || !strcasecmp(date_string, "sat")) { get_weekday_ymd(year, month, day, 6); return 1; } /* parse string into in[], read up to three integers */ for (p = date_string; *p; p++) { if (*p == sep) { if ((++n) > 2) return 0; } else if ((*p >= '0') && (*p <= '9')) { in[n] = in[n] * 10 + (int)(*p - '0'); } else { return 0; } } if ((!slctd_date && n < 2) || in[n] == 0) return 0; /* convert into day, month and year, depending on the date format */ switch (datefmt) { case DATEFMT_MMDDYYYY: m = (n >= 1) ? in[0] : 0; d = (n >= 1) ? in[1] : in[0]; y = in[2]; break; case DATEFMT_DDMMYYYY: d = in[0]; m = in[1]; y = in[2]; break; case DATEFMT_YYYYMMDD: case DATEFMT_ISO: y = (n >= 2) ? in[n - 2] : 0; m = (n >= 1) ? in[n - 1] : 0; d = in[n]; break; default: return 0; } if (slctd_date) { if (y > 0 && y < 100) { /* convert "YY" format into "YYYY" */ y += slctd_date->yyyy - slctd_date->yyyy % 100; } else if (n < 2) { /* set year and, optionally, month if short from is used */ y = slctd_date->yyyy; if (n < 1) m = slctd_date->mm; } } /* check if date is valid, take leap years into account */ if (!check_date(y, m, d)) return 0; if (year) *year = y; if (month) *month = m; if (day) *day = d; return 1; } /* * Check if time is valid. */ int check_time(unsigned hours, unsigned minutes) { return (hours < DAYINHOURS && minutes < HOURINMIN); } /* * Converts a time string into hours and minutes. Short forms like "23:" * (23:00) or ":45" (0:45) are allowed. * * Returns 1 on success and 0 on failure. */ int parse_time(const char *string, unsigned *hour, unsigned *minute) { const char *p; unsigned in[2] = { 0, 0 }, n = 0; if (!string) return 0; /* parse string into in[], read up to two integers */ for (p = string; *p; p++) { if (*p == ':') { if ((++n) > 1) return 0; } else if ((*p >= '0') && (*p <= '9')) { if ((n == 0) && (p == (string + 2)) && *(p + 1)) n++; in[n] = in[n] * 10 + (int)(*p - '0'); } else { return 0; } } if (n != 1 || !check_time(in[0], in[1])) return 0; *hour = in[0]; *minute = in[1]; return 1; } /* * Converts a duration string into minutes. * * Allowed formats (noted as regular expressions): * * - \d*:\d* * - (\d*m|\d*h(|\d*m)|\d*d(|\d*m|\d*h(|\d*m))) * - \d+ * * "\d" is used as a placeholder for "(0|1|2|3|4|5|6|7|8|9)". * * Returns 1 on success and 0 on failure. */ int parse_duration(const char *string, unsigned *duration) { enum { STATE_INITIAL, STATE_HHMM_MM, STATE_DDHHMM_HH, STATE_DDHHMM_MM, STATE_DONE } state = STATE_INITIAL; const char *p; unsigned in = 0; unsigned dur = 0; if (!string || *string == '\0') return 0; /* parse string using a simple state machine */ for (p = string; *p; p++) { if (state == STATE_DONE) { return 0; } else if ((*p >= '0') && (*p <= '9')) { in = in * 10 + (int)(*p - '0'); } else { switch (state) { case STATE_INITIAL: if (*p == ':') { dur += in * HOURINMIN; state = STATE_HHMM_MM; } else if (*p == 'd') { dur += in * DAYINMIN; state = STATE_DDHHMM_HH; } else if (*p == 'h') { dur += in * HOURINMIN; state = STATE_DDHHMM_MM; } else if (*p == 'm') { dur += in; state = STATE_DONE; } else { return 0; } break; case STATE_DDHHMM_HH: if (*p == 'h') { dur += in * HOURINMIN; state = STATE_DDHHMM_MM; } else if (*p == 'm') { dur += in; state = STATE_DONE; } else { return 0; } break; case STATE_DDHHMM_MM: if (*p == 'm') { dur += in; state = STATE_DONE; } else { return 0; } break; case STATE_HHMM_MM: return 0; break; default: break; } in = 0; } } if ((state == STATE_HHMM_MM && in >= HOURINMIN) || ((state == STATE_DDHHMM_HH || state == STATE_DDHHMM_MM) && in > 0)) return 0; dur += in; *duration = dur; return 1; } void file_close(FILE * f, const char *pos) { EXIT_IF((fclose(f)) != 0, _("Error when closing file at %s"), pos); } /* * Sleep the given number of seconds, but make it more 'precise' than sleep(3) * (hence the 'p') in a way that even if a signal is caught during the sleep * process, this function will return to sleep afterwards. */ void psleep(unsigned secs) { unsigned unslept; for (unslept = sleep(secs); unslept; unslept = sleep(unslept)) ; } /* * Fork and execute an external process. * * If pfdin and/or pfdout point to a valid address, a pipe is created and the * appropriate file descriptors are written to pfdin/pfdout. */ int fork_exec(int *pfdin, int *pfdout, const char *path, const char *const *arg) { int pin[2], pout[2]; int pid; if (pfdin && (pipe(pin) == -1)) return 0; if (pfdout && (pipe(pout) == -1)) return 0; if ((pid = fork()) == 0) { if (pfdout) { if (dup2(pout[0], STDIN_FILENO) < 0) _exit(127); close(pout[0]); close(pout[1]); } if (pfdin) { if (dup2(pin[1], STDOUT_FILENO) < 0) _exit(127); close(pin[0]); close(pin[1]); } execvp(path, (char *const *)arg); _exit(127); } else { if (pfdin) close(pin[1]); if (pfdout) close(pout[0]); if (pid > 0) { if (pfdin) { fcntl(pin[0], F_SETFD, FD_CLOEXEC); *pfdin = pin[0]; } if (pfdout) { fcntl(pout[1], F_SETFD, FD_CLOEXEC); *pfdout = pout[1]; } } else { if (pfdin) close(pin[0]); if (pfdout) close(pout[1]); return 0; } } return pid; } /* Execute an external program in a shell. */ int shell_exec(int *pfdin, int *pfdout, const char *path, const char *const *arg) { int argc, i; const char **narg; char *arg0 = NULL; int ret; for (argc = 0; arg[argc]; argc++) ; if (argc < 1) return -1; narg = mem_calloc(argc + 4, sizeof(const char *)); narg[0] = "sh"; narg[1] = "-c"; if (argc > 1) { asprintf(&arg0, "%s \"$@\"", path); narg[2] = arg0; for (i = 0; i < argc; i++) narg[i + 3] = arg[i]; narg[argc + 3] = NULL; } else { narg[2] = path; narg[3] = NULL; } ret = fork_exec(pfdin, pfdout, *narg, narg); if (arg0) mem_free(arg0); mem_free(narg); return ret; } /* Wait for a child process to terminate. */ int child_wait(int *pfdin, int *pfdout, int pid) { int stat; if (pfdin) close(*pfdin); if (pfdout) close(*pfdout); waitpid(pid, &stat, 0); return stat; } /* Display "Press any key to continue..." and wait for a key press. */ void press_any_key(void) { struct termios t_attr_old, t_attr; tcgetattr(STDIN_FILENO, &t_attr_old); memcpy(&t_attr, &t_attr_old, sizeof(struct termios)); t_attr.c_lflag &= ~(ICANON | ECHO | ECHONL); tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_attr); fflush(stdout); fputs(_("Press any key to continue..."), stdout); fflush(stdout); fgetc(stdin); fflush(stdin); fputs("\r\n", stdout); tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_attr_old); } /* * Display note contents if one is asociated with the currently displayed item * (to be used together with the '-a' or '-t' flag in non-interactive mode). * Each line begins with nbtab tabs. * Print "No note file found", if the notefile does not exists. * * (patch submitted by Erik Saule). */ static void print_notefile(FILE * out, const char *filename, int nbtab) { char *path_to_notefile; FILE *notefile; char linestarter[BUFSIZ]; char buffer[BUFSIZ]; int i; int printlinestarter = 1; if (nbtab < BUFSIZ) { for (i = 0; i < nbtab; i++) linestarter[i] = '\t'; linestarter[nbtab] = '\0'; } else { linestarter[0] = '\0'; } asprintf(&path_to_notefile, "%s/%s", path_notes, filename); notefile = fopen(path_to_notefile, "r"); mem_free(path_to_notefile); if (notefile) { while (fgets(buffer, BUFSIZ, notefile) != 0) { if (printlinestarter) { fputs(linestarter, out); printlinestarter = 0; } fputs(buffer, out); if (buffer[strlen(buffer) - 1] == '\n') printlinestarter = 1; } fputs("\n", out); file_close(notefile, __FILE_POS__); } else { fputs(linestarter, out); fputs(_("No note file found\n"), out); } } /* Print an escape sequence and return its length. */ static int print_escape(const char *s) { switch (*(s + 1)) { case 'a': putchar('\a'); return 1; case 'b': putchar('\b'); return 1; case 'f': putchar('\f'); return 1; case 'n': putchar('\n'); return 1; case 'r': putchar('\r'); return 1; case 't': putchar('\t'); return 1; case 'v': putchar('\v'); return 1; case '0': putchar('\0'); return 1; case '\'': putchar('\''); return 1; case '"': putchar('"'); return 1; case '\?': putchar('?'); return 1; case '\\': putchar('\\'); return 1; case '\0': return 0; default: return 1; } } /* Parse a format specifier. */ static enum format_specifier parse_fs(const char **s, char *extformat) { char buf[FS_EXT_MAXLEN]; int i; extformat[0] = '\0'; switch (**s) { case 's': strcpy(extformat, "epoch"); return FS_STARTDATE; case 'S': return FS_STARTDATE; case 'd': return FS_DURATION; case 'e': strcpy(extformat, "epoch"); return FS_ENDDATE; case 'E': return FS_ENDDATE; case 'm': return FS_MESSAGE; case 'n': return FS_NOTE; case 'N': return FS_NOTEFILE; case 'p': return FS_PRIORITY; case 'r': return FS_REMAINING; case '(': /* Long format specifier. */ for ((*s)++, i = 0; **s != ':' && **s != ')'; (*s)++, i++) { if (**s == '\0') return FS_EOF; if (i < FS_EXT_MAXLEN) buf[i] = **s; } buf[(i < FS_EXT_MAXLEN) ? i : FS_EXT_MAXLEN - 1] = '\0'; if (**s == ':') { for ((*s)++, i = 0; **s != ')'; (*s)++, i++) { if (**s == '\0') return FS_EOF; if (i < FS_EXT_MAXLEN) extformat[i] = **s; } extformat[(i < FS_EXT_MAXLEN) ? i : FS_EXT_MAXLEN - 1] = '\0'; } if (!strcmp(buf, "start")) return FS_STARTDATE; else if (!strcmp(buf, "duration")) return FS_DURATION; else if (!strcmp(buf, "end")) return FS_ENDDATE; else if (!strcmp(buf, "remaining")) return FS_REMAINING; else if (!strcmp(buf, "message")) return FS_MESSAGE; else if (!strcmp(buf, "noteid")) return FS_NOTE; else if (!strcmp(buf, "note")) return FS_NOTEFILE; else if (!strcmp(buf, "priority")) return FS_PRIORITY; else if (!strcmp(buf, "raw")) return FS_RAW; else if (!strcmp(buf, "hash")) return FS_HASH; else return FS_UNKNOWN; case '%': return FS_PSIGN; case '\0': return FS_EOF; default: return FS_UNKNOWN; } } /* Print a formatted date to stdout. */ static void print_date(long date, long day, const char *extformat) { char buf[BUFSIZ]; if (!strcmp(extformat, "epoch")) { printf("%ld", date); } else { time_t day_end = date_sec_change(day, 0, 1); time_t t = date; struct tm lt; localtime_r((time_t *) & t, <); if (extformat[0] == '\0' || !strcmp(extformat, "default")) { if (date >= day && date <= day_end) strftime(buf, BUFSIZ, "%H:%M", <); else strftime(buf, BUFSIZ, "..:..", <); } else { strftime(buf, BUFSIZ, extformat, <); } printf("%s", buf); } } /* Print a time difference to stdout. */ static void print_datediff(long difference, const char *extformat) { const char *p; const char *numfmt; bool usetotal; long value; if (!strcmp(extformat, "epoch")) { printf("%ld", difference); } else { if (extformat[0] == '\0' || !strcmp(extformat, "default")) { /* Set a default format if none specified. */ p = "%EH:%M"; } else { p = extformat; } while (*p) { if (*p == '%') { p++; /* Default is to zero-pad, and assume * the user wants the time unit modulo * the next biggest time unit. */ numfmt = "%02d"; usetotal = FALSE; if (*p == '-') { numfmt = "%d"; p++; } if (*p == 'E') { usetotal = TRUE; p++; } switch (*p) { case '\0': return; case 'd': value = difference / DAYINSEC; printf(numfmt, value); break; case 'H': value = difference / HOURINSEC; if (!usetotal) value %= DAYINHOURS; printf(numfmt, value); break; case 'M': value = difference / MININSEC; if (!usetotal) value %= HOURINMIN; printf(numfmt, value); break; case 'S': value = difference; if (!usetotal) value %= MININSEC; printf(numfmt, value); break; case '%': putchar('%'); break; default: putchar('?'); break; } } else { putchar(*p); } p++; } } } /* Print a formatted appointment to stdout. */ static void print_apoint_helper(const char *format, long day, struct apoint *apt, struct recur_apoint *rapt) { const char *p; char extformat[FS_EXT_MAXLEN]; for (p = format; *p; p++) { if (*p == '%') { p++; switch (parse_fs(&p, extformat)) { case FS_STARTDATE: print_date(apt->start, day, extformat); break; case FS_DURATION: /* Backwards compatibility: Use epoch by * default. */ if (*extformat == '\0') strcpy(extformat, "epoch"); print_datediff(apt->dur, extformat); break; case FS_ENDDATE: print_date(apt->start + apt->dur, day, extformat); break; case FS_REMAINING: print_datediff(difftime(apt->start, now()), extformat); break; case FS_MESSAGE: printf("%s", apt->mesg); break; case FS_NOTE: printf("%s", apt->note); break; case FS_NOTEFILE: print_notefile(stdout, apt->note, 1); break; case FS_RAW: if (rapt) recur_apoint_write(rapt, stdout); else apoint_write(apt, stdout); break; case FS_HASH: if (rapt) printf("%s", recur_apoint_hash(rapt)); else printf("%s", apoint_hash(apt)); break; case FS_PSIGN: putchar('%'); break; case FS_EOF: return; break; default: putchar('?'); break; } } else if (*p == '\\') { p += print_escape(p); } else { putchar(*p); } } } /* Print a formatted event to stdout. */ static void print_event_helper(const char *format, long day, struct event *ev, struct recur_event *rev) { const char *p; char extformat[FS_EXT_MAXLEN]; for (p = format; *p; p++) { if (*p == '%') { p++; switch (parse_fs(&p, extformat)) { case FS_MESSAGE: printf("%s", ev->mesg); break; case FS_NOTE: printf("%s", ev->note); break; case FS_NOTEFILE: print_notefile(stdout, ev->note, 1); break; case FS_PSIGN: putchar('%'); break; case FS_RAW: if (rev) recur_event_write(rev, stdout); else event_write(ev, stdout); break; case FS_HASH: if (rev) printf("%s", recur_event_tostr(rev)); else printf("%s", event_tostr(ev)); break; case FS_EOF: return; break; default: putchar('?'); break; } } else if (*p == '\\') { p += print_escape(p); } else { putchar(*p); } } } /* Print a formatted appointment to stdout. */ void print_apoint(const char *format, long day, struct apoint *apt) { print_apoint_helper(format, day, apt, NULL); } /* Print a formatted event to stdout. */ void print_event(const char *format, long day, struct event *ev) { print_event_helper(format, day, ev, NULL); } /* Print a formatted recurrent appointment to stdout. */ void print_recur_apoint(const char *format, long day, time_t occurrence, struct recur_apoint *rapt) { struct apoint apt; apt.start = occurrence; apt.dur = rapt->dur; apt.mesg = rapt->mesg; apt.note = rapt->note; print_apoint_helper(format, day, &apt, rapt); } /* Print a formatted recurrent event to stdout. */ void print_recur_event(const char *format, long day, struct recur_event *rev) { struct event ev; ev.mesg = rev->mesg; ev.note = rev->note; print_event_helper(format, day, &ev, rev); } /* Print a formatted todo item to stdout. */ void print_todo(const char *format, struct todo *todo) { const char *p; char extformat[FS_EXT_MAXLEN]; for (p = format; *p; p++) { if (*p == '%') { p++; switch (parse_fs(&p, extformat)) { case FS_PRIORITY: printf("%d", abs(todo->id)); break; case FS_MESSAGE: printf("%s", todo->mesg); break; case FS_NOTE: printf("%s", todo->note); break; case FS_NOTEFILE: print_notefile(stdout, todo->note, 1); break; case FS_RAW: todo_write(todo, stdout); break; case FS_HASH: printf("%s", todo_hash(todo)); break; case FS_PSIGN: putchar('%'); break; case FS_EOF: return; break; default: putchar('?'); break; } } else if (*p == '\\') { p += print_escape(p); } else { putchar(*p); } } } int asprintf(char **str, const char *format, ...) { struct string s; va_list ap; int n; va_start(ap, format); string_init(&s); n = string_vcatf(&s, format, ap); *str = string_buf(&s); va_end(ap); return n; } int starts_with(const char *s, const char *p) { for (; *p && *p == *s; s++, p++); return (*p == '\0'); } int starts_with_ci(const char *s, const char *p) { for (; *p && tolower(*p) == tolower(*s); s++, p++); return (*p == '\0'); } int hash_matches(const char *pattern, const char *hash) { int invert = 0; if (pattern[0] == '!') { invert = 1; pattern++; } return (starts_with(hash, pattern) != invert); } int show_dialogs(void) { return (!quiet) && conf.system_dialogs; }