source4/torture/util_writetime.c
authorStefan Metzmacher <metze@samba.org>
Wed, 15 Aug 2018 06:06:55 +0000 (08:06 +0200)
committerStefan Metzmacher <metze@samba.org>
Wed, 15 Aug 2018 06:09:58 +0000 (08:09 +0200)
source4/torture/smb2/durable_open.c
source4/torture/util.h
source4/torture/util_writetime.c [new file with mode: 0644]
source4/torture/wscript_build

index 0704fe4af8fdfcdb151c140e328a756680350da9..814f4944152d195b2034e6f5b3bb07698caf385f 100644 (file)
@@ -25,9 +25,9 @@
 #include "libcli/smb2/smb2_calls.h"
 #include "../libcli/smb/smbXcli_base.h"
 #include "torture/torture.h"
+#include "torture/util.h"
 #include "torture/smb2/proto.h"
-#include "../libcli/smb/smbXcli_base.h"
-#include "lib/util/time_basic.h"
+#include "../lib/util/time_basic.h"
 
 #define CHECK_VAL(v, correct) do { \
        if ((v) != (correct)) { \
@@ -2908,191 +2908,6 @@ done:
        } \
 } while (0)
 
-static bool test_delay_writetime(struct torture_context *tctx,
-                                double used_delay,
-                                double normal_delay,
-                                const char *description,
-                                bool (*get_basic_info_cb)(void *private_data,
-                                                          union smb_fileinfo *finfo),
-                                bool (*write_data_cb)(void *private_data),
-                                void *private_data)
-{
-       union smb_fileinfo finfo0, finfo1, finfo2, finfo3, finfoT;
-       struct timeval before_write;
-       struct timeval after_write;
-       struct timeval before_last_write;
-       struct timeval after_last_write;
-       struct timeval start;
-       struct timeval end;
-       double sec = used_delay / normal_delay;
-       double msec = 1000 * sec;
-       double nsec = 1000 * msec;
-       double expected_delay = used_delay / nsec;
-       double min_delay = expected_delay * 0.01;
-       double max_delay = 5 * expected_delay;
-       bool ret = true;
-       bool ok;
-
-       torture_comment(tctx, "START: %s\n", description);
-
-       /* get the initial times */
-       ok = get_basic_info_cb(private_data, &finfo0);
-       torture_assert(tctx, ok, "get_basic_info_cb: finfo0");
-
-       /*
-        * Make sure the time doesn't change during the next 5 seconds
-        */
-       start = timeval_current();
-       end = timeval_add(&start, max_delay * 1.25, 0);
-       while (!timeval_expired(&end)) {
-               smb_msleep(1 * msec);
-               torture_comment(tctx, "Check for no change\n");
-               ok = get_basic_info_cb(private_data, &finfoT);
-               torture_assert(tctx, ok, "get_basic_info_cb: finfoT");
-               COMPARE_ALL_TIMES_EQUAL(finfoT, finfo0);
-       }
-
-       ok = get_basic_info_cb(private_data, &finfoT);
-       torture_assert(tctx, ok, "get_basic_info_cb: finfoT");
-       COMPARE_WRITE_TIME_EQUAL(finfoT, finfo0);
-
-       torture_comment(tctx, "Do a write on the file handle\n");
-       before_write = timeval_current();
-       ok = write_data_cb(private_data);
-       after_write = timeval_current();
-       torture_assert(tctx, ok, "write_data_cb");
-
-       start = timeval_current();
-       end = timeval_add(&start, max_delay * 2, 0);
-       while (!timeval_expired(&end)) {
-               struct timeval before_get;
-               struct timeval after_get;
-
-               torture_comment(tctx, "Wait for change\n");
-               before_get = timeval_current();
-               ok = get_basic_info_cb(private_data, &finfoT);
-               after_get = timeval_current();
-               torture_assert(tctx, ok, "get_basic_info_cb: finfoT");
-
-               if (finfoT.basic_info.out.write_time > finfo0.basic_info.out.write_time) {
-                       double delayS = timeval_elapsed2(&after_write, &before_get);
-                       double delayL = timeval_elapsed2(&before_write, &after_get);
-
-                       torture_comment(tctx, "Server updated write_time after %.2f/%.2f seconds "
-                                       "(min delay == %.2f, max delay == %.2f)\n",
-                                       delayS, delayL, min_delay, max_delay);
-                       torture_assert(tctx, (delayL >= min_delay),
-                                      "Server updated write_time to early!");
-                       torture_assert(tctx, (delayS <= max_delay),
-                                      "Server didn't update write_time!");
-
-                       COMPARE_TIMES_AFTER_WRITE(finfoT, finfo0);
-                       break;
-               }
-
-               COMPARE_ALL_TIMES_EQUAL(finfoT, finfo0);
-               smb_msleep(0.01 * msec);
-       }
-
-       ok = get_basic_info_cb(private_data, &finfo1);
-       torture_assert(tctx, ok, "get_basic_info_cb: finfo1");
-       COMPARE_TIMES_AFTER_WRITE(finfo1, finfo0);
-
-       /*
-        * Make sure the time doesn't change during the next 5 seconds
-        */
-       start = timeval_current();
-       end = timeval_add(&start, max_delay * 1.25, 0);
-       while (!timeval_expired(&end)) {
-               smb_msleep(1 * msec);
-               torture_comment(tctx, "Check for no additional change\n");
-               ok = get_basic_info_cb(private_data, &finfoT);
-               torture_assert(tctx, ok, "get_basic_info_cb: finfoT");
-               COMPARE_ALL_TIMES_EQUAL(finfoT, finfo1);
-       }
-
-       ok = get_basic_info_cb(private_data, &finfoT);
-       torture_assert(tctx, ok, "get_basic_info_cb: finfoT");
-       COMPARE_ALL_TIMES_EQUAL(finfoT, finfo1);
-
-       torture_comment(tctx, "Do a write on the file handle\n");
-       before_write = timeval_current();
-       ok = write_data_cb(private_data);
-       after_write = timeval_current();
-       torture_assert(tctx, ok, "write_data_cb");
-
-       ZERO_STRUCT(finfo2);
-       before_last_write = before_write;
-       after_last_write = after_write;
-       start = timeval_current();
-       end = timeval_add(&start, max_delay * 2, 0);
-       while (!timeval_expired(&end)) {
-               struct timeval before_get;
-               struct timeval after_get;
-
-               smb_msleep(0.01 * msec);
-
-               torture_comment(tctx, "Wait for change\n");
-               before_get = timeval_current();
-               ok = get_basic_info_cb(private_data, &finfoT);
-               after_get = timeval_current();
-               torture_assert(tctx, ok, "get_basic_info_cb: finfoT");
-
-               if (finfoT.basic_info.out.write_time > finfo1.basic_info.out.write_time) {
-                       double delayS = timeval_elapsed2(&after_write, &before_get);
-                       double delayL = timeval_elapsed2(&before_write, &after_get);
-
-                       torture_comment(tctx, "Server updated write_time after %.2f/%.2f seconds "
-                                       "(min delay == %.2f, max delay == %.2f)\n",
-                                       delayS, delayL, min_delay, max_delay);
-                       torture_assert(tctx, (delayL >= min_delay),
-                                      "Server updated write_time to early!");
-                       torture_assert(tctx, (delayS <= max_delay),
-                                      "Server didn't update write_time!");
-
-                       COMPARE_TIMES_AFTER_WRITE(finfoT, finfo1);
-                       before_write = before_last_write;
-                       after_write = after_last_write;
-                       finfo2 = finfoT;
-                       break;
-               }
-
-               COMPARE_ALL_TIMES_EQUAL(finfoT, finfo1);
-
-               torture_comment(tctx, "Write while waiting\n");
-               before_last_write = timeval_current();
-               ok = write_data_cb(private_data);
-               after_last_write = timeval_current();
-               torture_assert(tctx, ok, "write_data_cb");
-       }
-
-       // We may get one additional change...
-
-       ok = get_basic_info_cb(private_data, &finfo3);
-       torture_assert(tctx, ok, "get_basic_info_cb: finfo3");
-       COMPARE_ALL_TIMES_EQUAL(finfo3, finfo2);
-
-       /*
-        * Make sure the time doesn't change during the next 5 seconds
-        */
-       start = timeval_current();
-       end = timeval_add(&start, max_delay * 1.25, 0);
-       while (!timeval_expired(&end)) {
-               smb_msleep(1 * msec);
-               torture_comment(tctx, "Check for no additional change\n");
-               ok = get_basic_info_cb(private_data, &finfoT);
-               torture_assert(tctx, ok, "get_basic_info_cb: finfoT");
-               COMPARE_ALL_TIMES_EQUAL(finfoT, finfo3);
-       }
-
-       ok = get_basic_info_cb(private_data, &finfoT);
-       torture_assert(tctx, ok, "get_basic_info_cb: finfoT");
-       COMPARE_ALL_TIMES_EQUAL(finfoT, finfo3);
-
-done:
-       return ret;
-}
-
 struct test_durable_open_delaywrite1_state {
        struct torture_context *tctx;
        struct smb2_tree *tree1;
@@ -3263,18 +3078,18 @@ static bool test_durable_open_delaywrite1(struct torture_context *tctx,
        state.h1 = h1;
        state.h2 = h2;
 
-       ok = test_delay_writetime(tctx, used_delay, normal_delay,
+       ok = test_delay_writetime1(tctx, used_delay, normal_delay,
                        "run1",
                        test_durable_open_delaywrite1_get_info,
                        test_durable_open_delaywrite1_write_data,
                        &state);
-       torture_assert(tctx, ok, "test_delay_writetime(1)");
-       ok = test_delay_writetime(tctx, used_delay, normal_delay,
+       torture_assert(tctx, ok, "test_delay_writetime1(1)");
+       ok = test_delay_writetime1(tctx, used_delay, normal_delay,
                        "run2",
                        test_durable_open_delaywrite1_get_info,
                        test_durable_open_delaywrite1_write_data,
                        &state);
-       torture_assert(tctx, ok, "test_delay_writetime(2)");
+       torture_assert(tctx, ok, "test_delay_writetime2(2)");
 
        GET_INFO_BOTH(c1finfo1, c2finfo1);
        COMPARE_TIMES_AFTER_WRITE(c1finfo1, c1finfo0);
@@ -3342,7 +3157,6 @@ done:
        return ret;
 }
 
-
 struct torture_suite *torture_smb2_durable_open_init(TALLOC_CTX *ctx)
 {
        struct torture_suite *suite =
index 4695710faece634f9e8b0b88fb993a4dfe1069db..cd0a530be409e33b2ec79c4171e1f2b7d7700a3b 100644 (file)
@@ -107,5 +107,13 @@ NTSTATUS torture_check_privilege(struct smbcli_state *cli,
                                 const char *sid_str,
                                 const char *privilege);
 
+bool test_delay_writetime1(struct torture_context *tctx,
+                          double used_delay,
+                          double normal_delay,
+                          const char *description,
+                          bool (*get_basic_info_cb)(void *private_data,
+                                                    union smb_fileinfo *finfo),
+                          bool (*write_data_cb)(void *private_data),
+                          void *private_data);
 
 #endif /* _TORTURE_UTIL_H_ */
diff --git a/source4/torture/util_writetime.c b/source4/torture/util_writetime.c
new file mode 100644 (file)
index 0000000..7ded582
--- /dev/null
@@ -0,0 +1,300 @@
+/*
+   Unix SMB/CIFS implementation.
+
+   test suite for delayed write time updates
+
+   Copyright (C) Stefan Metzmacher 2018
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "../lib/util/time_basic.h"
+#include "../libcli/smb/smb_common.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "libcli/raw/libcliraw.h"
+#include "torture/torture.h"
+#include "torture/util.h"
+
+#define COMPARE_TIME_CMP(given, gelem, correct, celem, cmp) do { \
+       const uint64_t _r = 10*1000*1000; \
+       NTTIME _g = (given).basic_info.out.gelem; \
+       NTTIME _gr = (_g / _r) * _r; \
+       NTTIME _c = (correct).basic_info.out.celem; \
+       NTTIME _cr = (_c / _r) * _r; \
+       bool _strict = torture_setting_bool(tctx, "strict mode", false); \
+       const char *_err = NULL; \
+       if (_strict && (_g cmp _c)) { \
+               _err = "strict"; \
+       } else if ((_g cmp _c) && (_gr cmp _cr)) { \
+               /* handle filesystem without high resolution timestamps */ \
+               _err = "rounded"; \
+       } \
+       if (_err != NULL) { \
+               struct timeval _gtv; \
+               struct timeval _ctv; \
+               struct timeval_buf _gtvb; \
+               struct timeval_buf _ctvb; \
+               nttime_to_timeval(&_gtv, _g); \
+               nttime_to_timeval(&_ctv, _c); \
+               torture_result(tctx, TORTURE_FAIL, \
+                              __location__": %s wrong (%s.%s)%s %s (%s.%s)%s", \
+                              _err, \
+                              #given, #gelem, \
+                              timeval_str_buf(&_gtv, false, true, &_gtvb), \
+                              #cmp, \
+                              #correct, #celem, \
+                              timeval_str_buf(&_ctv, false, true, &_ctvb)); \
+               ret = false; \
+               goto done; \
+       } \
+} while (0)
+#define COMPARE_WRITE_TIME_CMP(given, correct, cmp) do { \
+       COMPARE_TIME_CMP(given, write_time, correct, write_time, cmp); \
+} while (0)
+#define COMPARE_WRITE_TIME_EQUAL(given,correct) \
+       COMPARE_WRITE_TIME_CMP(given,correct,!=)
+#define COMPARE_WRITE_TIME_GREATER(given,correct) \
+       COMPARE_WRITE_TIME_CMP(given,correct,<=)
+
+#define COMPARE_ACCESS_TIME_CMP(given, correct, cmp) do { \
+       COMPARE_TIME_CMP(given, access_time, correct, access_time, cmp); \
+} while (0)
+#define COMPARE_ACCESS_TIME_EQUAL(given,correct) \
+       COMPARE_ACCESS_TIME_CMP(given,correct,!=)
+#define COMPARE_ACCESS_TIME_GREATER(given,correct) \
+       COMPARE_ACCESS_TIME_CMP(given,correct,<=)
+
+#define COMPARE_CHANGE_TIME_CMP(given, correct, cmp) do { \
+       COMPARE_TIME_CMP(given, change_time, correct, change_time, cmp); \
+} while (0)
+#define COMPARE_CHANGE_TIME_EQUAL(given,correct) \
+       COMPARE_CHANGE_TIME_CMP(given,correct,!=)
+#define COMPARE_CHANGE_TIME_GREATER(given,correct) \
+       COMPARE_CHANGE_TIME_CMP(given,correct,<=)
+
+#define COMPARE_CREATE_TIME_CMP(given, correct, cmp) do { \
+       COMPARE_TIME_CMP(given, create_time, correct, create_time, cmp); \
+} while (0)
+#define COMPARE_CREATE_TIME_EQUAL(given,correct) \
+       COMPARE_CREATE_TIME_CMP(given,correct,!=)
+
+#define COMPARE_ALL_TIMES_EQUAL(given,correct) do { \
+       COMPARE_WRITE_TIME_EQUAL(given,correct); \
+       COMPARE_CHANGE_TIME_EQUAL(given,correct); \
+       COMPARE_ACCESS_TIME_EQUAL(given,correct); \
+       COMPARE_CREATE_TIME_EQUAL(given,correct); \
+} while (0)
+
+#define COMPARE_TIMES_AFTER_WRITE(given,correct) do { \
+       COMPARE_WRITE_TIME_GREATER(given,correct); \
+       COMPARE_CHANGE_TIME_GREATER(given,correct); \
+       COMPARE_ACCESS_TIME_EQUAL(given,correct); \
+       COMPARE_CREATE_TIME_EQUAL(given,correct); \
+       COMPARE_TIME_CMP(given, change_time, given, write_time, !=); \
+} while (0)
+
+#define COMPARE_TIMES_AFTER_CLOSE(given,correct) do { \
+       COMPARE_WRITE_TIME_GREATER(given,correct); \
+       COMPARE_CHANGE_TIME_GREATER(given,correct); \
+       COMPARE_ACCESS_TIME_GREATER(given,correct); \
+       COMPARE_CREATE_TIME_EQUAL(given,correct); \
+       COMPARE_TIME_CMP(given, change_time, given, write_time, !=); \
+       COMPARE_TIME_CMP(given, access_time, given, write_time, !=); \
+} while (0)
+
+bool test_delay_writetime1(struct torture_context *tctx,
+                          double used_delay,
+                          double normal_delay,
+                          const char *description,
+                          bool (*get_basic_info_cb)(void *private_data,
+                                                    union smb_fileinfo *finfo),
+                          bool (*write_data_cb)(void *private_data),
+                          void *private_data)
+{
+       union smb_fileinfo finfo0, finfo1, finfo2, finfo3, finfoT;
+       struct timeval before_write;
+       struct timeval after_write;
+       struct timeval before_last_write;
+       struct timeval after_last_write;
+       struct timeval start;
+       struct timeval end;
+       double sec = used_delay / normal_delay;
+       double msec = 1000 * sec;
+       double nsec = 1000 * msec;
+       double expected_delay = used_delay / nsec;
+       double min_delay = expected_delay * 0.01;
+       double max_delay = 5 * expected_delay;
+       bool ret = true;
+       bool ok;
+
+       torture_comment(tctx, "START: %s\n", description);
+
+       /* get the initial times */
+       ok = get_basic_info_cb(private_data, &finfo0);
+       torture_assert(tctx, ok, "get_basic_info_cb: finfo0");
+
+       /*
+        * Make sure the time doesn't change during the next 5 seconds
+        */
+       start = timeval_current();
+       end = timeval_add(&start, max_delay * 1.25, 0);
+       while (!timeval_expired(&end)) {
+               smb_msleep(1 * msec);
+               torture_comment(tctx, "Check for no change\n");
+               ok = get_basic_info_cb(private_data, &finfoT);
+               torture_assert(tctx, ok, "get_basic_info_cb: finfoT");
+               COMPARE_ALL_TIMES_EQUAL(finfoT, finfo0);
+       }
+
+       ok = get_basic_info_cb(private_data, &finfoT);
+       torture_assert(tctx, ok, "get_basic_info_cb: finfoT");
+       COMPARE_WRITE_TIME_EQUAL(finfoT, finfo0);
+
+       torture_comment(tctx, "Do a write on the file handle\n");
+       before_write = timeval_current();
+       ok = write_data_cb(private_data);
+       after_write = timeval_current();
+       torture_assert(tctx, ok, "write_data_cb");
+
+       start = timeval_current();
+       end = timeval_add(&start, max_delay * 2, 0);
+       while (!timeval_expired(&end)) {
+               struct timeval before_get;
+               struct timeval after_get;
+
+               torture_comment(tctx, "Wait for change\n");
+               before_get = timeval_current();
+               ok = get_basic_info_cb(private_data, &finfoT);
+               after_get = timeval_current();
+               torture_assert(tctx, ok, "get_basic_info_cb: finfoT");
+
+               if (finfoT.basic_info.out.write_time > finfo0.basic_info.out.write_time) {
+                       double delayS = timeval_elapsed2(&after_write, &before_get);
+                       double delayL = timeval_elapsed2(&before_write, &after_get);
+
+                       torture_comment(tctx, "Server updated write_time after %.2f/%.2f seconds "
+                                       "(min delay == %.2f, max delay == %.2f)\n",
+                                       delayS, delayL, min_delay, max_delay);
+                       torture_assert(tctx, (delayL >= min_delay),
+                                      "Server updated write_time to early!");
+                       torture_assert(tctx, (delayS <= max_delay),
+                                      "Server didn't update write_time!");
+
+                       COMPARE_TIMES_AFTER_WRITE(finfoT, finfo0);
+                       break;
+               }
+
+               COMPARE_ALL_TIMES_EQUAL(finfoT, finfo0);
+               smb_msleep(0.01 * msec);
+       }
+
+       ok = get_basic_info_cb(private_data, &finfo1);
+       torture_assert(tctx, ok, "get_basic_info_cb: finfo1");
+       COMPARE_TIMES_AFTER_WRITE(finfo1, finfo0);
+
+       /*
+        * Make sure the time doesn't change during the next 5 seconds
+        */
+       start = timeval_current();
+       end = timeval_add(&start, max_delay * 1.25, 0);
+       while (!timeval_expired(&end)) {
+               smb_msleep(1 * msec);
+               torture_comment(tctx, "Check for no additional change\n");
+               ok = get_basic_info_cb(private_data, &finfoT);
+               torture_assert(tctx, ok, "get_basic_info_cb: finfoT");
+               COMPARE_ALL_TIMES_EQUAL(finfoT, finfo1);
+       }
+
+       ok = get_basic_info_cb(private_data, &finfoT);
+       torture_assert(tctx, ok, "get_basic_info_cb: finfoT");
+       COMPARE_ALL_TIMES_EQUAL(finfoT, finfo1);
+
+       torture_comment(tctx, "Do a write on the file handle\n");
+       before_write = timeval_current();
+       ok = write_data_cb(private_data);
+       after_write = timeval_current();
+       torture_assert(tctx, ok, "write_data_cb");
+
+       ZERO_STRUCT(finfo2);
+       before_last_write = before_write;
+       after_last_write = after_write;
+       start = timeval_current();
+       end = timeval_add(&start, max_delay * 2, 0);
+       while (!timeval_expired(&end)) {
+               struct timeval before_get;
+               struct timeval after_get;
+
+               smb_msleep(0.01 * msec);
+
+               torture_comment(tctx, "Wait for change\n");
+               before_get = timeval_current();
+               ok = get_basic_info_cb(private_data, &finfoT);
+               after_get = timeval_current();
+               torture_assert(tctx, ok, "get_basic_info_cb: finfoT");
+
+               if (finfoT.basic_info.out.write_time > finfo1.basic_info.out.write_time) {
+                       double delayS = timeval_elapsed2(&after_write, &before_get);
+                       double delayL = timeval_elapsed2(&before_write, &after_get);
+
+                       torture_comment(tctx, "Server updated write_time after %.2f/%.2f seconds "
+                                       "(min delay == %.2f, max delay == %.2f)\n",
+                                       delayS, delayL, min_delay, max_delay);
+                       torture_assert(tctx, (delayL >= min_delay),
+                                      "Server updated write_time to early!");
+                       torture_assert(tctx, (delayS <= max_delay),
+                                      "Server didn't update write_time!");
+
+                       COMPARE_TIMES_AFTER_WRITE(finfoT, finfo1);
+                       before_write = before_last_write;
+                       after_write = after_last_write;
+                       finfo2 = finfoT;
+                       break;
+               }
+
+               COMPARE_ALL_TIMES_EQUAL(finfoT, finfo1);
+
+               torture_comment(tctx, "Write while waiting\n");
+               before_last_write = timeval_current();
+               ok = write_data_cb(private_data);
+               after_last_write = timeval_current();
+               torture_assert(tctx, ok, "write_data_cb");
+       }
+
+       // We may get one additional change...
+
+       ok = get_basic_info_cb(private_data, &finfo3);
+       torture_assert(tctx, ok, "get_basic_info_cb: finfo3");
+       COMPARE_ALL_TIMES_EQUAL(finfo3, finfo2);
+
+       /*
+        * Make sure the time doesn't change during the next 5 seconds
+        */
+       start = timeval_current();
+       end = timeval_add(&start, max_delay * 1.25, 0);
+       while (!timeval_expired(&end)) {
+               smb_msleep(1 * msec);
+               torture_comment(tctx, "Check for no additional change\n");
+               ok = get_basic_info_cb(private_data, &finfoT);
+               torture_assert(tctx, ok, "get_basic_info_cb: finfoT");
+               COMPARE_ALL_TIMES_EQUAL(finfoT, finfo3);
+       }
+
+       ok = get_basic_info_cb(private_data, &finfoT);
+       torture_assert(tctx, ok, "get_basic_info_cb: finfoT");
+       COMPARE_ALL_TIMES_EQUAL(finfoT, finfo3);
+
+done:
+       return ret;
+}
index aceededc9d83fdc6398c5f7b8d78d2a6dd58570a..fb9c324da0512040f41d0bc7a99caea23bbee3d8 100644 (file)
@@ -2,7 +2,7 @@
 
 
 bld.SAMBA_SUBSYSTEM('TORTURE_UTIL',
-       source='util_smb.c',
+       source='util_smb.c util_writetime.c',
        public_deps='torture popt POPT_CREDENTIALS',
        deps='smbclient-raw'
        )