gettimeofday is susceptible to system time adjustments, skewing or
altogether breaking progress output.  For the sake of platforms that
lack clock_gettime support, gettimeofday is retained as a fallback.

Fixes FS#36983

Signed-off-by: Andrew Gregory <[email protected]>
---
 src/pacman/callback.c | 61 ++++++++++++++++++++++++++++++++-------------------
 1 file changed, 38 insertions(+), 23 deletions(-)

diff --git a/src/pacman/callback.c b/src/pacman/callback.c
index 9cde1de..dafaeaf 100644
--- a/src/pacman/callback.c
+++ b/src/pacman/callback.c
@@ -21,7 +21,6 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <sys/time.h>
 #include <sys/types.h> /* off_t */
 #include <time.h>
 #include <unistd.h>
@@ -47,6 +46,33 @@ static alpm_list_t *output = NULL;
 /* update speed for the fill_progress based functions */
 #define UPDATE_SPEED_MS 200
 
+#if defined(CLOCK_MONOTONIC) && defined(_POSIX_MONOTONIC_CLOCK)
+
+static void get_time(struct timespec *ts)
+{
+       clock_gettime(CLOCK_MONOTONIC, ts);
+}
+
+#else
+
+/* darwin doesn't support clock_gettime, fallback to gettimeofday */
+#include <sys/time.h>
+static void get_time(struct timespec *ts)
+{
+       struct timeval tv;
+       gettimeofday(&tv, NULL);
+       ts->tv_sec = tv->tv_sec;
+       ts->tv_nsec = tv->tv_usec * 1000;
+}
+
+#endif
+
+static long diff_timespec_ms(struct timespec *t1, struct timespec *t0) {
+               time_t diff_sec = t1->tv_sec - t0->tv_sec;
+               long diff_nsec = t1->tv_nsec - t0->tv_nsec;
+               return (diff_sec * 1000) + (diff_nsec / 1000000);
+}
+
 /**
  * Silly little helper function, determines if the caller needs a visual update
  * since the last time this function was called.
@@ -57,24 +83,18 @@ static alpm_list_t *output = NULL;
 static long get_update_timediff(int first_call)
 {
        long retval = 0;
-       static struct timeval last_time = {0, 0};
+       static struct timespec last_time = {0, 0};
 
        /* on first call, simply set the last time and return */
        if(first_call) {
-               gettimeofday(&last_time, NULL);
+               get_time(&last_time);
        } else {
-               struct timeval this_time;
-               time_t diff_sec;
-               suseconds_t diff_usec;
-
-               gettimeofday(&this_time, NULL);
-               diff_sec = this_time.tv_sec - last_time.tv_sec;
-               diff_usec = this_time.tv_usec - last_time.tv_usec;
-
-               retval = (diff_sec * 1000) + (diff_usec / 1000);
+               struct timespec this_time;
+               get_time(&this_time);
+               retval = diff_timespec_ms(&this_time, &last_time);
 
                /* do not update last_time if interval was too short */
-               if(retval >= UPDATE_SPEED_MS) {
+               if(retval < 0 || retval >= UPDATE_SPEED_MS) {
                        last_time = this_time;
                }
        }
@@ -647,7 +667,7 @@ void cb_dl_progress(const char *filename, off_t 
file_xfered, off_t file_total)
 {
        static double rate_last;
        static off_t xfered_last;
-       static struct timeval initial_time;
+       static struct timespec initial_time;
        int infolen;
        int filenamelen;
        char *fname, *p;
@@ -709,21 +729,16 @@ void cb_dl_progress(const char *filename, off_t 
file_xfered, off_t file_total)
                /* set default starting values, ensure we only call this once
                 * if TotalDownload is enabled */
                if(!totaldownload || (totaldownload && list_xfered == 0)) {
-                       gettimeofday(&initial_time, NULL);
+                       get_time(&initial_time);
                        xfered_last = (off_t)0;
                        rate_last = 0.0;
                        get_update_timediff(1);
                }
        } else if(file_xfered == file_total) {
                /* compute final values */
-               struct timeval current_time;
-               time_t diff_sec;
-               suseconds_t diff_usec;
-
-               gettimeofday(&current_time, NULL);
-               diff_sec = current_time.tv_sec - initial_time.tv_sec;
-               diff_usec = current_time.tv_usec - initial_time.tv_usec;
-               timediff = (diff_sec * 1000) + (diff_usec / 1000);
+               struct timespec current_time;
+               get_time(&current_time);
+               timediff = diff_timespec_ms(&current_time, &initial_time);
                if(timediff > 0) {
                        rate = (double)xfered / (timediff / 1000.0);
                        /* round elapsed time (in ms) to the nearest second */
-- 
2.0.1


Reply via email to