Bläddra i källkod

* The binary is only rewritten now if CHANGES are made in the editor.

svn: 2072
Bryan Drewery 21 år sedan
förälder
incheckning
4230ec0f5b
7 ändrade filer med 493 tillägg och 10 borttagningar
  1. 24 1
      autotools/configure.ac
  2. 30 0
      config.h.in
  3. 354 1
      configure
  4. 1 0
      doc/UPDATES
  5. 1 0
      src/compat/compat.h
  6. 33 0
      src/compat/timespec.h
  7. 50 8
      src/conf.c

+ 24 - 1
autotools/configure.ac

@@ -128,12 +128,13 @@ AC_FUNC_MKTIME
 AC_FUNC_MMAP
 AC_FUNC_REALLOC
 AC_FUNC_STAT
+EGG_FUNC_TIMESPEC
 AC_FUNC_STRFTIME
 
 #checkpoint
 AC_CACHE_SAVE
 AC_CHECK_FUNCS([clock fsync getrusage isascii inet_aton gethostbyname2])
-AC_CHECK_FUNCS([getipnodebyname memcpy memset random rename snprintf srandom])
+AC_CHECK_FUNCS([getipnodebyname memcpy memset random rename snprintf srandom gettime])
 #checkpoint
 AC_CACHE_SAVE
 AC_CHECK_FUNCS([strcasecmp strncasecmp vsnprintf])
@@ -159,3 +160,25 @@ DO_DEPS
 
 AC_CONFIG_FILES([Makefile src/Makefile src/compat/Makefile src/crypto/Makefile src/mod/Makefile])
 AC_OUTPUT
+
+AH_TEMPLATE(HAVE_ST_MTIM, [Define to 1 if your struct stat has an st_mtim member])
+AH_TEMPLATE(HAVE_ST_MTIMESPEC, [Define to 1 if your struct stat has an st_mtimespec member])
+AH_TEMPLATE(HAVE_TIMESPEC, [Define to 1 if you have struct timespec in sys/time.h])
+AH_TEMPLATE(HAVE_TIMESPECSUB2, [Define to 1 if you have a timespecsub macro or function that takes two arguments (not three)])
+AH_BOTTOM([/*
+ * Macros to pull sec and nsec parts of mtime from struct stat.
+ */
+#ifdef HAVE_ST_MTIM
+# define mtim_getsec(_x)        ((_x).st_mtim.tv_sec)
+# define mtim_getnsec(_x)       ((_x).st_mtim.tv_nsec)
+#else
+# ifdef HAVE_ST_MTIMESPEC
+#  define mtim_getsec(_x)       ((_x).st_mtimespec.tv_sec)
+#  define mtim_getnsec(_x)      ((_x).st_mtimespec.tv_nsec)
+# else
+#  define mtim_getsec(_x)       ((_x).st_mtime)
+#  define mtim_getnsec(_x)      (0)
+# endif /* HAVE_ST_MTIMESPEC */
+#endif /* HAVE_ST_MTIM */
+])
+

+ 30 - 0
config.h.in

@@ -156,6 +156,12 @@
 /* Define to 1 if `tm_zone' is member of `struct tm'. */
 #undef HAVE_STRUCT_TM_TM_ZONE
 
+/* Define to 1 if your struct stat has an st_mtim member */
+#undef HAVE_ST_MTIM
+
+/* Define to 1 if your struct stat has an st_mtimespec member */
+#undef HAVE_ST_MTIMESPEC
+
 /* Define to 1 if you have the <sys/dir.h> header file, and it defines `DIR'.
    */
 #undef HAVE_SYS_DIR_H
@@ -191,6 +197,13 @@
 /* Define to 1 if you have <sys/wait.h> that is POSIX.1 compatible. */
 #undef HAVE_SYS_WAIT_H
 
+/* Define to 1 if you have struct timespec in sys/time.h */
+#undef HAVE_TIMESPEC
+
+/* Define to 1 if you have a timespecsub macro or function that takes two
+   arguments (not three) */
+#undef HAVE_TIMESPECSUB2
+
 /* Define to 1 if your `struct tm' has `tm_zone'. Deprecated, use
    `HAVE_STRUCT_TM_TM_ZONE' instead. */
 #undef HAVE_TM_ZONE
@@ -337,4 +350,21 @@
    code using `volatile' can become incorrect without. Disable with care. */
 #undef volatile
 
+/*
+ * Macros to pull sec and nsec parts of mtime from struct stat.
+ */
+#ifdef HAVE_ST_MTIM
+# define mtim_getsec(_x)        ((_x).st_mtim.tv_sec)
+# define mtim_getnsec(_x)       ((_x).st_mtim.tv_nsec)
+#else
+# ifdef HAVE_ST_MTIMESPEC
+#  define mtim_getsec(_x)       ((_x).st_mtimespec.tv_sec)
+#  define mtim_getnsec(_x)      ((_x).st_mtimespec.tv_nsec)
+# else
+#  define mtim_getsec(_x)       ((_x).st_mtime)
+#  define mtim_getnsec(_x)      (0)
+# endif /* HAVE_ST_MTIMESPEC */
+#endif /* HAVE_ST_MTIM */
+
+
 /* Defines for pack features */

+ 354 - 1
configure

@@ -1,5 +1,5 @@
 #! /bin/sh
-# From configure.ac Revision: 1.43 .
+# From configure.ac Revision: 1.44 .
 # Guess values for system-dependent variables and create Makefiles.
 # Generated by GNU Autoconf 2.59.
 #
@@ -9720,6 +9720,347 @@ _ACEOF
 fi
 
 
+echo "$as_me:$LINENO: checking for struct timespec" >&5
+echo $ECHO_N "checking for struct timespec... $ECHO_C" >&6
+if test "${ac_cv_type_struct_timespec+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+#include <sys/types.h>
+#include <sys/time.h>
+#include <time.h>
+
+int
+main ()
+{
+if ((struct timespec *) 0)
+  return 0;
+if (sizeof (struct timespec))
+  return 0;
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+  (eval $ac_compile) 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } &&
+	 { ac_try='test -z "$ac_cxx_werror_flag"			 || test ! -s conftest.err'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; } &&
+	 { ac_try='test -s conftest.$ac_objext'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; }; then
+  ac_cv_type_struct_timespec=yes
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ac_cv_type_struct_timespec=no
+fi
+rm -f conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+echo "$as_me:$LINENO: result: $ac_cv_type_struct_timespec" >&5
+echo "${ECHO_T}$ac_cv_type_struct_timespec" >&6
+if test $ac_cv_type_struct_timespec = yes; then
+  cat >>confdefs.h <<\_ACEOF
+#define HAVE_TIMESPEC 1
+_ACEOF
+
+fi
+
+
+if test X"$ac_cv_type_struct_timespec" != X"no"; then
+ echo "$as_me:$LINENO: checking for struct stat.st_mtim" >&5
+echo $ECHO_N "checking for struct stat.st_mtim... $ECHO_C" >&6
+if test "${ac_cv_member_struct_stat_st_mtim+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+int
+main ()
+{
+static struct stat ac_aggr;
+if (ac_aggr.st_mtim)
+return 0;
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+  (eval $ac_compile) 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } &&
+	 { ac_try='test -z "$ac_cxx_werror_flag"			 || test ! -s conftest.err'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; } &&
+	 { ac_try='test -s conftest.$ac_objext'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; }; then
+  ac_cv_member_struct_stat_st_mtim=yes
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+int
+main ()
+{
+static struct stat ac_aggr;
+if (sizeof ac_aggr.st_mtim)
+return 0;
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+  (eval $ac_compile) 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } &&
+	 { ac_try='test -z "$ac_cxx_werror_flag"			 || test ! -s conftest.err'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; } &&
+	 { ac_try='test -s conftest.$ac_objext'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; }; then
+  ac_cv_member_struct_stat_st_mtim=yes
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ac_cv_member_struct_stat_st_mtim=no
+fi
+rm -f conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+echo "$as_me:$LINENO: result: $ac_cv_member_struct_stat_st_mtim" >&5
+echo "${ECHO_T}$ac_cv_member_struct_stat_st_mtim" >&6
+if test $ac_cv_member_struct_stat_st_mtim = yes; then
+  cat >>confdefs.h <<\_ACEOF
+#define HAVE_ST_MTIM 1
+_ACEOF
+
+else
+  echo "$as_me:$LINENO: checking for struct stat.st_mtimespec" >&5
+echo $ECHO_N "checking for struct stat.st_mtimespec... $ECHO_C" >&6
+if test "${ac_cv_member_struct_stat_st_mtimespec+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+int
+main ()
+{
+static struct stat ac_aggr;
+if (ac_aggr.st_mtimespec)
+return 0;
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+  (eval $ac_compile) 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } &&
+	 { ac_try='test -z "$ac_cxx_werror_flag"			 || test ! -s conftest.err'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; } &&
+	 { ac_try='test -s conftest.$ac_objext'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; }; then
+  ac_cv_member_struct_stat_st_mtimespec=yes
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+int
+main ()
+{
+static struct stat ac_aggr;
+if (sizeof ac_aggr.st_mtimespec)
+return 0;
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+  (eval $ac_compile) 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } &&
+	 { ac_try='test -z "$ac_cxx_werror_flag"			 || test ! -s conftest.err'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; } &&
+	 { ac_try='test -s conftest.$ac_objext'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; }; then
+  ac_cv_member_struct_stat_st_mtimespec=yes
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ac_cv_member_struct_stat_st_mtimespec=no
+fi
+rm -f conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+echo "$as_me:$LINENO: result: $ac_cv_member_struct_stat_st_mtimespec" >&5
+echo "${ECHO_T}$ac_cv_member_struct_stat_st_mtimespec" >&6
+if test $ac_cv_member_struct_stat_st_mtimespec = yes; then
+  cat >>confdefs.h <<\_ACEOF
+#define HAVE_ST_MTIMESPEC 1
+_ACEOF
+
+fi
+
+fi
+
+ echo "$as_me:$LINENO: checking for two-parameter timespecsub" >&5
+echo $ECHO_N "checking for two-parameter timespecsub... $ECHO_C" >&6
+ cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+#include <sys/types.h>
+#include <sys/time.h>
+int
+main ()
+{
+struct timespec ts1, ts2;
+ts1.tv_sec = 1; ts1.tv_nsec = 0; ts2.tv_sec = 0; ts2.tv_nsec = 0;
+#ifndef timespecsub
+#error missing timespecsub
+#endif
+timespecsub(&ts1, &ts2);
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+  (eval $ac_compile) 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } &&
+	 { ac_try='test -z "$ac_cxx_werror_flag"			 || test ! -s conftest.err'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; } &&
+	 { ac_try='test -s conftest.$ac_objext'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; }; then
+  cat >>confdefs.h <<\_ACEOF
+#define HAVE_TIMESPECSUB2 1
+_ACEOF
+
+        echo "$as_me:$LINENO: result: yes" >&5
+echo "${ECHO_T}yes" >&6
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6
+fi
+rm -f conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+
+
 for ac_func in strftime
 do
 as_ac_var=`echo "ac_cv_func_$ac_func" | $as_tr_sh`
@@ -11826,3 +12167,15 @@ if test "$no_create" != yes; then
   $ac_cs_success || { (exit 1); exit 1; }
 fi
 
+
+
+
+
+
+
+
+
+
+
+
+

+ 1 - 0
doc/UPDATES

@@ -80,6 +80,7 @@ Lines prefixxed with '-' were disabled before release and are not finishsed, or
   -Default binpath is now the directory the binary is first ran in
   -Running the bot with -C will automatically update uid/uname/homedir/username
   -Added cmd line param '-c' to avoid auto updating uid/uname/homedir/username
+  -The binary is only rewritten now if CHANGES are made in the editor.
   * Things done after the config is saved:
    -New bots are spawned and added (if linked)
    -Changed (NEW) ip/host for bots are added to the botnet (if linked)

+ 1 - 0
src/compat/compat.h

@@ -20,6 +20,7 @@
 #include "makepath.h"
 #include "strlcpy.h"
 #include "strsep.h"
+#include "timespec.h"
 
 /* These apparently are unsafe without recasting. */
 #define egg_isdigit(x)  isdigit((int)  (unsigned char) (x))

+ 33 - 0
src/compat/timespec.h

@@ -0,0 +1,33 @@
+#ifndef _TIMESPEC_H
+#define _TIMESPEC_H
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifndef HAVE_TIMESPEC
+struct timespec {
+    time_t      tv_sec;
+    long        tv_nsec;
+};
+#endif /* !HAVE_TIMESPEC */
+
+#ifndef timespecclear
+# define timespecclear(ts)      (ts)->tv_sec = (ts)->tv_nsec = 0
+#endif
+#ifndef timespecisset
+# define timespecisset(ts)      ((ts)->tv_sec || (ts)->tv_nsec)
+#endif
+#ifndef timespecsub
+# define timespecsub(minuend, subrahend, difference)                           \
+    do {                                                                       \
+            (difference)->tv_sec = (minuend)->tv_sec - (subrahend)->tv_sec;    \
+            (difference)->tv_nsec = (minuend)->tv_nsec - (subrahend)->tv_nsec; \
+            if ((difference)->tv_nsec < 0) {                                   \
+                    (difference)->tv_nsec += 1000000000L;                      \
+                    (difference)->tv_sec--;                                    \
+            }                                                                  \
+    } while (0)
+#endif
+
+#endif /* !_TIMESPEC_H */

+ 50 - 8
src/conf.c

@@ -27,6 +27,8 @@
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <sys/stat.h>
+#include <time.h>
+#include <sys/time.h>
 #include <signal.h>
 
 #ifdef CYGWIN_HACKS
@@ -166,6 +168,24 @@ swap_uids_back()
   return (setegid(save_egid) || seteuid(save_euid)) ? -1 : 0;
 }
 
+static int
+my_gettime(struct timespec *ts)
+{
+    int rval;
+#if defined(HAVE_GETTIMEOFDAY) && (defined(HAVE_ST_MTIM) || defined(HAVE_ST_MTIMESPEC))
+    struct timeval tv;
+
+    rval = gettimeofday(&tv, NULL);
+    ts->tv_sec = tv.tv_sec;
+    ts->tv_nsec = tv.tv_usec * 1000;
+#else
+    rval = (int)time(&ts->tv_sec);
+    ts->tv_nsec = 0;
+#endif
+    return (rval);
+}
+
+
 void
 confedit()
 {
@@ -174,13 +194,16 @@ confedit()
   mode_t um;
   int waiter;
   pid_t pid, xpid, localhub_pid = 0;
-
+  struct stat st, sn;
+  struct timespec ts1, ts2;           /* time before and after edit */
 
   um = umask(077);
 
   writeconf(NULL, tmpconf.f, CONF_COMMENT);
-  tmpconf.my_close();
-  (void) umask(um);
+  fstat(tmpconf.fd, &st);		/* for file modification compares */
+//  tmpconf.my_close();
+
+  umask(um);
 
   if (!can_stat(tmpconf.file))
     fatal("Cannot stat tempfile", 0);
@@ -208,6 +231,7 @@ confedit()
 
   swap_uids();
 
+  my_gettime(&ts1);
   switch (pid = fork()) {
     case -1:
       fatal("Cannot fork", 0);
@@ -233,6 +257,7 @@ confedit()
   /* parent */
   while (1) {
     xpid = waitpid(pid, &waiter, WUNTRACED);
+    my_gettime(&ts2);
     if (xpid == -1) {
       fprintf(stderr, "waitpid() failed waiting for PID %d from \"%s\": %s\n", pid, editor, strerror(errno));
     } else if (xpid != pid) {
@@ -258,19 +283,36 @@ confedit()
     }
   }
 
-  (void) signal(SIGINT, SIG_DFL);
-  (void) signal(SIGQUIT, SIG_DFL);
 
-  swap_uids_back();
+  signal(SIGINT, SIG_DFL);
+  signal(SIGQUIT, SIG_DFL);
 
-  if (!can_stat(tmpconf.file))
+  swap_uids_back();
+  if (fstat(tmpconf.fd, &sn))
     fatal("Error reading new config file", 0);
 
+  if (st.st_size == sn.st_size &&
+      mtim_getsec(st) == mtim_getsec(sn) &&
+      mtim_getnsec(st) == mtim_getnsec(sn)) {
+    /*
+     * If mtime and size match but the user spent no measurable
+     * time in the editor we can't tell if the file was changed.
+     */
+#ifdef HAVE_TIMESPECSUB2
+    timespecsub(&ts1, &ts2);
+#else
+    timespecsub(&ts1, &ts2, &ts2);
+#endif
+    if (timespecisset(&ts2)) {
+      printf("* Config unchanged.\n");
+      exit(0);            
+    }
+  }
+
   if (conf.bots && conf.bots->pid)
     localhub_pid = conf.bots->pid;
 
   tmpconf.my_close();
-
   readconf((const char *) tmpconf.file, 0);               /* read cleartext conf tmp into &settings */
   fix_tilde(&conf.binpath);
   unlink(tmpconf.file);