From: Kevin McCarthy Date: Thu, 14 Jun 2018 08:17:56 +0000 (+0800) Subject: Convert context and buffy to use nanosecond timestamps. X-Git-Tag: 2019-10-25~668^2~4 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=42b436392846f06ebbc01dba7642361f163a8aad;p=neomutt Convert context and buffy to use nanosecond timestamps. The inotify interface has an unfortunate side effect of making Mutt react too quickly to new mail. Sometimes, the mail is only half-delivered when the mailbox is checked. Because Mutt is using the stat mtime - seconds resolution - this means it won't realize there are more messages delivered during the same second. Nanosecond resolution fields were standardized in POSIX.1-2008, so check for and use those if they are available. --- diff --git a/auto.def b/auto.def index 1c5b2bc4c..ebd129bb3 100644 --- a/auto.def +++ b/auto.def @@ -270,6 +270,7 @@ if {1} { sysexits.h cc-check-functions \ + clock_gettime \ fgetc_unlocked \ futimens \ getaddrinfo \ @@ -277,12 +278,21 @@ if {1} { iswblank \ mkdtemp \ strsep \ + utimesnsat \ vasprintf \ wcscasecmp cc-check-function-in-lib gethostent nsl cc-check-function-in-lib setsockopt socket cc-check-function-in-lib getaddrinfo_a anl + + cc-with {-includes time.h} { + cc-check-types "struct timespec" + } + + cc-with {-includes sys/stat.h} { + cc-check-members "struct stat.st_atim.tv_nsec" + } } ############################################################################### diff --git a/context.h b/context.h index 6b02c2b84..6353f95e6 100644 --- a/context.h +++ b/context.h @@ -56,8 +56,8 @@ struct Context char *path; char *realpath; /**< used for mailbox comparison and the sidebar */ FILE *fp; - time_t atime; - time_t mtime; + struct timespec atime; + struct timespec mtime; off_t size; off_t vsize; char *pattern; /**< limit pattern string */ diff --git a/mailbox.c b/mailbox.c index 16a9e6f1c..7471fa4ed 100644 --- a/mailbox.c +++ b/mailbox.c @@ -234,7 +234,8 @@ static int mailbox_maildir_check_dir(struct Mailbox *mailbox, const char *dir_na */ if (check_new && MailCheckRecent) { - if (stat(path, &sb) == 0 && sb.st_mtime < mailbox->last_visited) + if (stat(path, &sb) == 0 && + mutt_stat_timespec_compare(&sb, MUTT_STAT_MTIME, &mailbox->last_visited) < 0) { rc = 0; check_new = false; @@ -276,8 +277,11 @@ static int mailbox_maildir_check_dir(struct Mailbox *mailbox, const char *dir_na { snprintf(msgpath, sizeof(msgpath), "%s/%s", path, de->d_name); /* ensure this message was received since leaving this mailbox */ - if (stat(msgpath, &sb) == 0 && (sb.st_ctime <= mailbox->last_visited)) + if (stat(msgpath, &sb) == 0 && + (mutt_stat_timespec_compare(&sb, MUTT_STAT_CTIME, &mailbox->last_visited) <= 0)) + { continue; + } } mailbox->new = true; rc = 1; @@ -337,14 +341,17 @@ static int mailbox_mbox_check(struct Mailbox *mailbox, struct stat *sb, bool che new_or_changed = (sb->st_size > mailbox->size); else { - new_or_changed = (sb->st_mtime > sb->st_atime) || - (mailbox->newly_created && (sb->st_ctime == sb->st_mtime) && - (sb->st_ctime == sb->st_atime)); + new_or_changed = + (mutt_stat_compare(sb, MUTT_STAT_MTIME, sb, MUTT_STAT_ATIME) > 0) || + (mailbox->newly_created && + (mutt_stat_compare(sb, MUTT_STAT_CTIME, sb, MUTT_STAT_MTIME) == 0) && + (mutt_stat_compare(sb, MUTT_STAT_CTIME, sb, MUTT_STAT_ATIME) == 0)); } if (new_or_changed) { - if (!MailCheckRecent || sb->st_mtime > mailbox->last_visited) + if (!MailCheckRecent || + (mutt_stat_timespec_compare(sb, MUTT_STAT_MTIME, &mailbox->last_visited) > 0)) { rc = 1; mailbox->new = true; @@ -359,7 +366,8 @@ static int mailbox_mbox_check(struct Mailbox *mailbox, struct stat *sb, bool che if (mailbox->newly_created && (sb->st_ctime != sb->st_mtime || sb->st_ctime != sb->st_atime)) mailbox->newly_created = false; - if (check_stats && (mailbox->stats_last_checked < sb->st_mtime)) + if (check_stats && + (mutt_stat_timespec_compare(sb, MUTT_STAT_MTIME, &mailbox->stats_last_checked) > 0)) { struct Context *ctx = mx_mbox_open(mailbox->path, MUTT_READONLY | MUTT_QUIET | MUTT_NOSORT | MUTT_PEEK); @@ -531,7 +539,11 @@ static struct Mailbox *mailbox_get(const char *path) */ void mutt_mailbox_cleanup(const char *path, struct stat *st) { +#ifdef HAVE_UTIMENSAT + struct timespec ts[2]; +#else struct utimbuf ut; +#endif if (CheckMboxSize) { @@ -544,12 +556,30 @@ void mutt_mailbox_cleanup(const char *path, struct stat *st) /* fix up the times so mailbox won't get confused */ if (st->st_mtime > st->st_atime) { +#ifdef HAVE_UTIMENSAT + ts[0].tv_sec = 0; + ts[0].tv_nsec = UTIME_OMIT; + ts[1].tv_sec = 0; + ts[1].tv_nsec = UTIME_NOW; + utimensat(0, buf, ts, 0); +#else ut.actime = st->st_atime; ut.modtime = time(NULL); utime(path, &ut); +#endif } else + { +#ifdef HAVE_UTIMENSAT + ts[0].tv_sec = 0; + ts[0].tv_nsec = UTIME_NOW; + ts[1].tv_sec = 0; + ts[1].tv_nsec = UTIME_NOW; + utimensat(0, buf, ts, 0); +#else utime(path, NULL); +#endif + } } } @@ -917,7 +947,12 @@ void mutt_mailbox_setnotified(const char *path) return; mailbox->notified = true; - time(&mailbox->last_visited); +#if HAVE_CLOCK_GETTIME + clock_gettime(CLOCK_REALTIME, &mailbox->last_visited); +#else + mailbox->last_visited.tv_nsec = 0; + time(&mailbox->last_visited.tv_sec); +#endif } /** diff --git a/mailbox.h b/mailbox.h index 437a182c2..64bc43c3f 100644 --- a/mailbox.h +++ b/mailbox.h @@ -56,15 +56,15 @@ struct Mailbox bool new; /**< mailbox has new mail */ /* These next three are only set when MailCheckStats is set */ - int msg_count; /**< total number of messages */ - int msg_unread; /**< number of unread messages */ - int msg_flagged; /**< number of flagged messages */ + int msg_count; /**< total number of messages */ + int msg_unread; /**< number of unread messages */ + int msg_flagged; /**< number of flagged messages */ bool notified; /**< user has been notified */ enum MailboxType magic; /**< mailbox type */ bool newly_created; /**< mbox or mmdf just popped into existence */ - time_t last_visited; /**< time of last exit from this mailbox */ - time_t stats_last_checked; /**< mtime of mailbox the last time stats where checked. */ + struct timespec last_visited; /**< time of last exit from this mailbox */ + struct timespec stats_last_checked; /**< mtime of mailbox the last time stats where checked. */ }; /** diff --git a/maildir/mh.c b/maildir/mh.c index 1e6109ce4..b106ba172 100644 --- a/maildir/mh.c +++ b/maildir/mh.c @@ -105,7 +105,7 @@ struct MhSequences */ struct MhData { - time_t mtime_cur; + struct timespec mtime_cur; mode_t mh_umask; }; @@ -300,7 +300,7 @@ static int mh_sequences_changed(struct Mailbox *b) if ((snprintf(path, sizeof(path), "%s/.mh_sequences", b->path) < sizeof(path)) && (stat(path, &sb) == 0)) { - return sb.st_mtime > b->last_visited; + return (mutt_stat_timespec_compare(&sb, MUTT_STAT_MTIME, &b->last_visited) > 0); } return -1; } @@ -321,7 +321,7 @@ static int mh_already_notified(struct Mailbox *b, int msgno) if ((snprintf(path, sizeof(path), "%s/%d", b->path, msgno) < sizeof(path)) && (stat(path, &sb) == 0)) { - return sb.st_mtime <= b->last_visited; + return (mutt_stat_timespec_compare(&sb, MUTT_STAT_MTIME, &b->last_visited) <= 0); } return -1; } @@ -853,20 +853,20 @@ static void maildir_update_mtime(struct Context *ctx) { snprintf(buf, sizeof(buf), "%s/%s", ctx->path, "cur"); if (stat(buf, &st) == 0) - data->mtime_cur = st.st_mtime; + mutt_get_stat_timespec(&data->mtime_cur, &st, MUTT_STAT_MTIME); snprintf(buf, sizeof(buf), "%s/%s", ctx->path, "new"); } else { snprintf(buf, sizeof(buf), "%s/.mh_sequences", ctx->path); if (stat(buf, &st) == 0) - data->mtime_cur = st.st_mtime; + mutt_get_stat_timespec(&data->mtime_cur, &st, MUTT_STAT_MTIME); mutt_str_strfcpy(buf, ctx->path, sizeof(buf)); } if (stat(buf, &st) == 0) - ctx->mtime = st.st_mtime; + mutt_get_stat_timespec(&ctx->mtime, &st, MUTT_STAT_MTIME); } /** @@ -2340,17 +2340,17 @@ static int maildir_mbox_check(struct Context *ctx, int *index_hint) return -1; /* determine which subdirectories need to be scanned */ - if (st_new.st_mtime > ctx->mtime) + if (mutt_stat_timespec_compare(&st_new, MUTT_STAT_MTIME, &ctx->mtime) > 0) changed = 1; - if (st_cur.st_mtime > data->mtime_cur) + if (mutt_stat_timespec_compare(&st_cur, MUTT_STAT_MTIME, &data->mtime_cur) > 0) changed |= 2; if (!changed) return 0; /* nothing to do */ /* update the modification times on the mailbox */ - data->mtime_cur = st_cur.st_mtime; - ctx->mtime = st_new.st_mtime; + mutt_get_stat_timespec(&data->mtime_cur, &st_cur, MUTT_STAT_MTIME); + mutt_get_stat_timespec(&ctx->mtime, &st_new, MUTT_STAT_MTIME); /* do a fast scan of just the filenames in * the subdirectories that have changed. @@ -2507,14 +2507,17 @@ static int mh_mbox_check(struct Context *ctx, int *index_hint) if (i == -1 && stat(buf, &st_cur) == -1) modified = true; - if (st.st_mtime > ctx->mtime || st_cur.st_mtime > data->mtime_cur) + if ((mutt_stat_timespec_compare(&st, MUTT_STAT_MTIME, &ctx->mtime) > 0) || + (mutt_stat_timespec_compare(&st_cur, MUTT_STAT_MTIME, &data->mtime_cur) > 0)) + { modified = true; + } if (!modified) return 0; - data->mtime_cur = st_cur.st_mtime; - ctx->mtime = st.st_mtime; + mutt_get_stat_timespec(&data->mtime_cur, &st_cur, MUTT_STAT_MTIME); + mutt_get_stat_timespec(&ctx->mtime, &st, MUTT_STAT_MTIME); md = NULL; last = &md; diff --git a/mbox/mbox.c b/mbox/mbox.c index 71a6e3d36..9edfbc943 100644 --- a/mbox/mbox.c +++ b/mbox/mbox.c @@ -129,8 +129,8 @@ static int mmdf_parse_mailbox(struct Context *ctx) mutt_perror(ctx->path); return -1; } - ctx->atime = sb.st_atime; - ctx->mtime = sb.st_mtime; + mutt_get_stat_timespec(&ctx->atime, &sb, MUTT_STAT_ATIME); + mutt_get_stat_timespec(&ctx->mtime, &sb, MUTT_STAT_MTIME); ctx->size = sb.st_size; buf[sizeof(buf) - 1] = '\0'; @@ -291,8 +291,8 @@ static int mbox_parse_mailbox(struct Context *ctx) } ctx->size = sb.st_size; - ctx->mtime = sb.st_mtime; - ctx->atime = sb.st_atime; + mutt_get_stat_timespec(&ctx->mtime, &sb, MUTT_STAT_MTIME); + mutt_get_stat_timespec(&ctx->atime, &sb, MUTT_STAT_ATIME); if (!ctx->readonly) ctx->readonly = access(ctx->path, W_OK) ? true : false; @@ -828,13 +828,14 @@ static int mbox_mbox_check(struct Context *ctx, int *index_hint) if (stat(ctx->path, &st) == 0) { - if (st.st_mtime == ctx->mtime && st.st_size == ctx->size) + if ((mutt_stat_timespec_compare(&st, MUTT_STAT_MTIME, &ctx->mtime) == 0) && + st.st_size == ctx->size) return 0; if (st.st_size == ctx->size) { /* the file was touched, but it is still the same length, so just exit */ - ctx->mtime = st.st_mtime; + mutt_get_stat_timespec(&ctx->mtime, &st, MUTT_STAT_MTIME); return 0; } @@ -1386,10 +1387,17 @@ int mbox_path_probe(const char *path, const struct stat *st) * only the type was accessed. This is important, because detection * of "new mail" depends on those times set correctly. */ +#ifdef HAVE_UTIMENSAT + struct timespec ts[2]; + mutt_get_stat_timespec(&ts[0], &st, MUTT_STAT_ATIME); + mutt_get_stat_timespec(&ts[1], &st, MUTT_STAT_MTIME); + utimensat(0, path, ts, 0); +#else struct utimbuf times; times.actime = st->st_atime; times.modtime = st->st_mtime; utime(path, ×); +#endif } return magic; diff --git a/mutt.h b/mutt.h index 5acc5beba..21e7175a3 100644 --- a/mutt.h +++ b/mutt.h @@ -54,6 +54,22 @@ struct Mapping; #define fgetc fgetc_unlocked #endif +#ifndef HAVE_STRUCT_TIMESPEC +struct timespec +{ + time_t tv_sec; + long tv_nsec; +}; +#endif + +/* flags for mutt_get_stat_timespec */ +enum MuttStatType +{ + MUTT_STAT_ATIME, + MUTT_STAT_MTIME, + MUTT_STAT_CTIME +}; + /* flags for mutt_enter_string_full() */ #define MUTT_ALIAS (1 << 0) /**< do alias "completion" by calling up the alias-menu */ #define MUTT_FILE (1 << 1) /**< do file completion */ diff --git a/muttlib.c b/muttlib.c index 045308daa..cd01c7763 100644 --- a/muttlib.c +++ b/muttlib.c @@ -1434,6 +1434,65 @@ void mutt_sleep(short s) sleep(s); } +int mutt_timespec_compare(struct timespec *a, struct timespec *b) +{ + if (a->tv_sec < b->tv_sec) + return -1; + if (a->tv_sec > b->tv_sec) + return 1; + + if (a->tv_nsec < b->tv_nsec) + return -1; + if (a->tv_nsec > b->tv_nsec) + return 1; + return 0; +} + +void mutt_get_stat_timespec(struct timespec *dest, struct stat *sb, enum MuttStatType type) +{ + dest->tv_nsec = 0; + + switch (type) + { + case MUTT_STAT_ATIME: + dest->tv_sec = sb->st_atime; +#ifdef HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC + dest->tv_nsec = sb->st_atim.tv_nsec; +#endif + break; + case MUTT_STAT_MTIME: + dest->tv_sec = sb->st_mtime; +#ifdef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC + dest->tv_nsec = sb->st_mtim.tv_nsec; +#endif + break; + case MUTT_STAT_CTIME: + dest->tv_sec = sb->st_ctime; +#ifdef HAVE_STRUCT_STAT_ST_CTIM_TV_NSEC + dest->tv_nsec = sb->st_ctim.tv_nsec; +#endif + break; + } +} + +int mutt_stat_timespec_compare(struct stat *sba, enum MuttStatType type, struct timespec *b) +{ + struct timespec a; + + mutt_get_stat_timespec(&a, sba, type); + return mutt_timespec_compare(&a, b); +} + +int mutt_stat_compare(struct stat *sba, enum MuttStatType sba_type, + struct stat *sbb, enum MuttStatType sbb_type) +{ + struct timespec a, b; + + mutt_get_stat_timespec(&a, sba, sba_type); + mutt_get_stat_timespec(&b, sbb, sbb_type); + return mutt_timespec_compare(&a, &b); +} + /** * mutt_make_version - Generate the NeoMutt version string * @retval ptr Version string diff --git a/muttlib.h b/muttlib.h index ad5b17a12..4fed3c50f 100644 --- a/muttlib.h +++ b/muttlib.h @@ -27,6 +27,7 @@ #include #include #include +#include "mutt.h" #include "format_flags.h" struct Address; @@ -65,6 +66,11 @@ int mutt_save_confirm(const char *s, struct stat *st); void mutt_save_path(char *d, size_t dsize, struct Address *a); void mutt_sleep(short s); +int mutt_timespec_compare(struct timespec *a, struct timespec *b); +void mutt_get_stat_timespec(struct timespec *dest, struct stat *sb, enum MuttStatType type); +int mutt_stat_timespec_compare(struct stat *sba, enum MuttStatType type, struct timespec *b); +int mutt_stat_compare(struct stat *sba, enum MuttStatType sba_type, struct stat *sbb, enum MuttStatType sbb_type); + #define mutt_mktemp(a, b) mutt_mktemp_pfx_sfx(a, b, "neomutt", NULL) #define mutt_mktemp_pfx_sfx(a, b, c, d) mutt_mktemp_full(a, b, c, d, __FILE__, __LINE__) diff --git a/mx.c b/mx.c index b6e17c655..57599e08a 100644 --- a/mx.c +++ b/mx.c @@ -341,17 +341,27 @@ struct Context *mx_mbox_open(const char *path, int flags) */ void mx_fastclose_mailbox(struct Context *ctx) { +#ifdef HAVE_UTIMENSAT + struct timespec ts[2]; +#else struct utimbuf ut; +#endif /* HAVE_UTIMENSAT */ if (!ctx) return; /* fix up the times so mailbox won't get confused */ - if (ctx->peekonly && ctx->path && (ctx->mtime > ctx->atime)) + if (ctx->peekonly && ctx->path && (mutt_timespec_compare(&ctx->mtime, &ctx->atime) > 0)) { - ut.actime = ctx->atime; - ut.modtime = ctx->mtime; +#ifdef HAVE_UTIMENSAT + ts[0] = ctx->atime; + ts[1] = ctx->mtime; + utimensat(0, ctx->path, ts, 0); +#else + ut.actime = ctx->atime.tv_sec; + ut.modtime = ctx->mtime.tv_sec; utime(ctx->path, &ut); +#endif /* HAVE_UTIMENSAT */ } /* never announce that a mailbox we've just left has new mail. #3290 diff --git a/notmuch/mutt_notmuch.c b/notmuch/mutt_notmuch.c index 135a1ecae..c749464c6 100644 --- a/notmuch/mutt_notmuch.c +++ b/notmuch/mutt_notmuch.c @@ -1866,7 +1866,8 @@ int nm_read_entire_thread(struct Context *ctx, struct Header *h) notmuch_query_set_sort(q, NOTMUCH_SORT_NEWEST_FIRST); read_threads_query(ctx, q, true, 0); - ctx->mtime = time(NULL); + ctx->mtime.tv_sec = time(NULL); + ctx->mtime.tv_nsec = 0; rc = 0; if (ctx->msgcount > data->oldmsgcount) @@ -2076,7 +2077,10 @@ done: if (!is_longrun(data)) release_db(data); if (hdr->changed) - ctx->mtime = time(NULL); + { + ctx->mtime.tv_sec = time(NULL); + ctx->mtime.tv_nsec = 0; + } mutt_debug(1, "nm: tags modify done [rc=%d]\n", rc); return rc; } @@ -2179,7 +2183,8 @@ int nm_update_filename(struct Context *ctx, const char *old, const char *new, if (!is_longrun(data)) release_db(data); - ctx->mtime = time(NULL); + ctx->mtime.tv_sec = time(NULL); + ctx->mtime.tv_nsec = 0; return rc; } @@ -2472,7 +2477,8 @@ static int nm_mbox_open(struct Context *ctx) if (!is_longrun(data)) release_db(data); - ctx->mtime = time(NULL); + ctx->mtime.tv_sec = time(NULL); + ctx->mtime.tv_nsec = 0; mx_update_context(ctx, ctx->msgcount); data->oldmsgcount = 0; @@ -2526,7 +2532,7 @@ static int nm_mbox_check(struct Context *ctx, int *index_hint) if (!data || (get_database_mtime(data, &mtime) != 0)) return -1; - if (ctx->mtime >= mtime) + if (ctx->mtime.tv_sec >= mtime) { mutt_debug(2, "nm: check unnecessary (db=%lu ctx=%lu)\n", mtime, ctx->mtime); return 0; @@ -2621,7 +2627,8 @@ done: if (!is_longrun(data)) release_db(data); - ctx->mtime = time(NULL); + ctx->mtime.tv_sec = time(NULL); + ctx->mtime.tv_nsec = 0; mutt_debug(1, "nm: ... check done [count=%d, new_flags=%d, occult=%d]\n", ctx->msgcount, new_flags, occult); @@ -2709,7 +2716,10 @@ static int nm_mbox_sync(struct Context *ctx, int *index_hint) if (!is_longrun(data)) release_db(data); if (changed) - ctx->mtime = time(NULL); + { + ctx->mtime.tv_sec = time(NULL); + ctx->mtime.tv_nsec = 0; + } mutt_debug(1, "nm: .... sync done [rc=%d]\n", rc); return rc;