1 /*
2  * libcsync -- a library to sync a directory with another
3  *
4  * Copyright (c) 2015-2013 by Klaas Freitag <freitag@owncloud.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  */
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <fcntl.h>
23 #include <cstring>
24 #include <cerrno>
25 #include <cstdio>
26 
27 #include "csync.h"
28 #include "vio/csync_vio_local.h"
29 
30 #include <QDir>
31 
__anon79c329380102null32 static const auto CSYNC_TEST_DIR = []{ return QStringLiteral("%1/csync_test").arg(QDir::tempPath());}();
33 
34 #include "torture.h"
35 
36 namespace {
oc_mkdir(const QString & path)37 int oc_mkdir(const QString &path)
38 {
39     return QDir(path).mkpath(path) ? 0 : -1;
40 }
41 
42 }
43 #define WD_BUFFER_SIZE 255
44 
45 static mbchar_t wd_buffer[WD_BUFFER_SIZE];
46 
47 typedef struct {
48     QByteArray result;
49     QByteArray ignored_dir;
50 } statevar;
51 
52 /* remove the complete test dir */
wipe_testdir()53 static int wipe_testdir()
54 {
55   QDir tmp(CSYNC_TEST_DIR);
56   if (tmp.exists())
57   {
58       return tmp.removeRecursively() ? 0 : 1;
59   }
60   return 0;
61 }
62 
setup_testenv(void ** state)63 static int setup_testenv(void **state) {
64     int rc = 0;
65 
66     rc = wipe_testdir();
67     assert_int_equal(rc, 0);
68 
69     auto dir = CSYNC_TEST_DIR;
70     rc = oc_mkdir(dir);
71     assert_int_equal(rc, 0);
72 
73     assert_non_null(_tgetcwd(wd_buffer, WD_BUFFER_SIZE));
74 
75 #ifdef Q_OS_WIN
76     rc  = _tchdir(dir.toStdWString().data());
77 #else
78     rc  = _tchdir(dir.toLocal8Bit().constData());
79 #endif
80     assert_int_equal(rc, 0);
81 
82     /* --- initialize csync */
83     auto mystate = new statevar;
84     *state = mystate;
85     return 0;
86 }
87 
output(const char * text)88 static void output( const char *text )
89 {
90     printf("%s\n", text);
91 }
92 
teardown(void ** state)93 static int teardown(void **state) {
94     int rc = -1;
95 
96     output("================== Tearing down!\n");
97 
98     rc = _tchdir(wd_buffer);
99     assert_int_equal(rc, 0);
100 
101     rc = wipe_testdir();
102     assert_int_equal(rc, 0);
103 
104     delete reinterpret_cast<statevar*>(*state);
105     return 0;
106 }
107 
108 /* This function takes a relative path, prepends it with the CSYNC_TEST_DIR
109  * and creates each sub directory.
110  */
create_dirs(const char * path)111 static void create_dirs( const char *path )
112 {
113   int rc = -1;
114   auto _mypath = QStringLiteral("%1/%2").arg(CSYNC_TEST_DIR, QString::fromUtf8(path)).toUtf8();
115   char *mypath = _mypath.data();
116 
117   char *p = mypath + CSYNC_TEST_DIR.size() + 1; /* start behind the offset */
118   int i = 0;
119 
120   assert_non_null(path);
121 
122   while( *(p+i) ) {
123     if( *(p+i) == '/' ) {
124       p[i] = '\0';
125 
126       auto mb_dir = QString::fromUtf8(mypath);
127       rc = oc_mkdir(mb_dir);
128       if(rc)
129       {
130           rc = errno;
131       }
132       assert_int_equal(rc, 0);
133       p[i] = '/';
134     }
135     i++;
136   }
137 }
138 
139 /*
140  * This function uses the vio_opendir, vio_readdir and vio_closedir functions
141  * to traverse a file tree that was created before by the create_dir function.
142  *
143  * It appends a listing to the result member of the incoming struct in *state
144  * that can be compared later to what was expected in the calling functions.
145  *
146  * The int parameter cnt contains the number of seen files (not dirs) in the
147  * whole tree.
148  *
149  */
traverse_dir(void ** state,const QString & dir,int * cnt)150 static void traverse_dir(void **state, const QString &dir, int *cnt)
151 {
152     csync_vio_handle_t *dh = nullptr;
153     std::unique_ptr<csync_file_stat_t> dirent;
154     auto sv = (statevar*) *state;
155     QByteArray subdir;
156     QByteArray subdir_out;
157     int rc = -1;
158     int is_dir = 0;
159 
160     dh = csync_vio_local_opendir(dir);
161     assert_non_null(dh);
162 
163     OCC::Vfs *vfs = nullptr;
164     while( (dirent = csync_vio_local_readdir(dh, vfs)) ) {
165         assert_non_null(dirent.get());
166         if (!dirent->original_path.isEmpty()) {
167             sv->ignored_dir = dirent->original_path;
168             continue;
169         }
170 
171         assert_false(dirent->path.isEmpty());
172 
173         if( dirent->path == ".." || dirent->path == "." ) {
174           continue;
175         }
176 
177         is_dir = (dirent->type == ItemTypeDirectory) ? 1:0;
178 
179         subdir = dir.toUtf8() + "/" + dirent->path;
180         subdir_out = (is_dir ? "<DIR> ":"      ") + subdir;
181 
182         if( is_dir ) {
183             if( sv->result.isNull() ) {
184                sv->result = subdir_out;
185             } else {
186                sv->result += subdir_out;
187             }
188         } else {
189             *cnt = *cnt +1;
190         }
191         output(subdir_out.constData());
192         if( is_dir ) {
193             traverse_dir(state, QString::fromUtf8(subdir), cnt);
194         }
195     }
196 
197     rc = csync_vio_local_closedir(dh);
198     assert_int_equal(rc, 0);
199 
200 }
201 
create_file(const char * path,const char * name,const char * content)202 static void create_file( const char *path, const char *name, const char *content)
203 {
204     QFile file(QStringLiteral("%1/%2%3").arg(CSYNC_TEST_DIR, QString::fromUtf8(path), QString::fromUtf8(name)));
205     assert_int_equal(1, file.open(QIODevice::WriteOnly | QIODevice::NewOnly));
206     file.write(content);
207 }
208 
check_readdir_shorttree(void ** state)209 static void check_readdir_shorttree(void **state)
210 {
211     auto sv = (statevar*) *state;
212 
213     const char *t1 = "alibaba/und/die/vierzig/räuber/";
214     create_dirs( t1 );
215     int files_cnt = 0;
216 
217     traverse_dir(state, CSYNC_TEST_DIR, &files_cnt);
218 
219     assert_string_equal(sv->result.constData(),
220         QString::fromUtf8("<DIR> %1/alibaba"
221                           "<DIR> %1/alibaba/und"
222                           "<DIR> %1/alibaba/und/die"
223                           "<DIR> %1/alibaba/und/die/vierzig"
224                           "<DIR> %1/alibaba/und/die/vierzig/räuber")
225             .arg(CSYNC_TEST_DIR)
226             .toUtf8()
227             .constData());
228     assert_int_equal(files_cnt, 0);
229 }
230 
check_readdir_with_content(void ** state)231 static void check_readdir_with_content(void **state)
232 {
233     auto sv = (statevar*) *state;
234     int files_cnt = 0;
235 
236     const char *t1 = "warum/nur/40/Räuber/";
237     create_dirs( t1 );
238 
239     create_file( t1, "Räuber Max.txt", "Der Max ist ein schlimmer finger");
240     create_file( t1, "пя́тница.txt", "Am Freitag tanzt der Ürk");
241 
242 
243     traverse_dir(state, CSYNC_TEST_DIR, &files_cnt);
244 
245     assert_string_equal(sv->result.constData(),
246         QString::fromUtf8("<DIR> %1/warum"
247                           "<DIR> %1/warum/nur"
248                           "<DIR> %1/warum/nur/40"
249                           "<DIR> %1/warum/nur/40/Räuber")
250             .arg(CSYNC_TEST_DIR)
251             .toUtf8()
252             .constData());
253     /*                   "      %1/warum/nur/40/Räuber/Räuber Max.txt"
254                          "      %1/warum/nur/40/Räuber/пя́тница.txt"; */
255     assert_int_equal(files_cnt, 2); /* Two files in the sub dir */
256 }
257 
check_readdir_longtree(void ** state)258 static void check_readdir_longtree(void **state)
259 {
260     auto sv = (statevar*) *state;
261 
262     /* Strange things here: Compilers only support strings with length of 4k max.
263      * The expected result string is longer, so it needs to be split up in r1, r2 and r3
264      */
265 
266     /* create the test tree */
267     const char *t1 = "vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND/NE/BOTTLE/VOLL/RUM/undnochmalallezusammen/VierZig/MannaufDesTotenManns/KISTE/ooooooooooooooooooooooooooohhhhhh/und/BESSER/ZWEI/Butteln/VOLL RUM/";
268     create_dirs( t1 );
269 
270     const auto r1 = QString::fromUtf8(
271 "<DIR> %1/vierzig"
272 "<DIR> %1/vierzig/mann"
273 "<DIR> %1/vierzig/mann/auf"
274 "<DIR> %1/vierzig/mann/auf/des"
275 "<DIR> %1/vierzig/mann/auf/des/toten"
276 "<DIR> %1/vierzig/mann/auf/des/toten/Mann"
277 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste"
278 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh"
279 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and"
280 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne"
281 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle"
282 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll"
283 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum").arg(CSYNC_TEST_DIR);
284 
285 
286     const auto r2 = QString::fromUtf8(
287 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und"
288 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so"
289 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen"
290 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir"
291 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG"
292 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN"
293 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF"
294 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES"
295 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN"
296 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS"
297 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE"
298 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH").arg(CSYNC_TEST_DIR);
299 
300 
301     const auto r3 = QString::fromUtf8(
302 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND"
303 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND/NE"
304 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND/NE/BOTTLE"
305 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND/NE/BOTTLE/VOLL"
306 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND/NE/BOTTLE/VOLL/RUM"
307 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND/NE/BOTTLE/VOLL/RUM/undnochmalallezusammen"
308 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND/NE/BOTTLE/VOLL/RUM/undnochmalallezusammen/VierZig"
309 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND/NE/BOTTLE/VOLL/RUM/undnochmalallezusammen/VierZig/MannaufDesTotenManns"
310 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND/NE/BOTTLE/VOLL/RUM/undnochmalallezusammen/VierZig/MannaufDesTotenManns/KISTE"
311 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND/NE/BOTTLE/VOLL/RUM/undnochmalallezusammen/VierZig/MannaufDesTotenManns/KISTE/ooooooooooooooooooooooooooohhhhhh"
312 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND/NE/BOTTLE/VOLL/RUM/undnochmalallezusammen/VierZig/MannaufDesTotenManns/KISTE/ooooooooooooooooooooooooooohhhhhh/und"
313 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND/NE/BOTTLE/VOLL/RUM/undnochmalallezusammen/VierZig/MannaufDesTotenManns/KISTE/ooooooooooooooooooooooooooohhhhhh/und/BESSER"
314 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND/NE/BOTTLE/VOLL/RUM/undnochmalallezusammen/VierZig/MannaufDesTotenManns/KISTE/ooooooooooooooooooooooooooohhhhhh/und/BESSER/ZWEI"
315 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND/NE/BOTTLE/VOLL/RUM/undnochmalallezusammen/VierZig/MannaufDesTotenManns/KISTE/ooooooooooooooooooooooooooohhhhhh/und/BESSER/ZWEI/Butteln"
316 "<DIR> %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND/NE/BOTTLE/VOLL/RUM/undnochmalallezusammen/VierZig/MannaufDesTotenManns/KISTE/ooooooooooooooooooooooooooohhhhhh/und/BESSER/ZWEI/Butteln/VOLL RUM").arg(CSYNC_TEST_DIR);
317 
318     /* assemble the result string ... */
319     const auto result = (r1 + r2 + r3).toUtf8();
320     int files_cnt = 0;
321     traverse_dir(state, CSYNC_TEST_DIR, &files_cnt);
322     assert_int_equal(files_cnt, 0);
323     /* and compare. */
324     assert_string_equal(sv->result.constData(), result.constData());
325 }
326 
327 // https://github.com/owncloud/client/issues/3128 https://github.com/owncloud/client/issues/2777
check_readdir_bigunicode(void ** state)328 static void check_readdir_bigunicode(void **state)
329 {
330     auto sv = (statevar*) *state;
331 //    1: ? ASCII: 239 - EF
332 //    2: ? ASCII: 187 - BB
333 //    3: ? ASCII: 191 - BF
334 //    4: ASCII: 32    - 20
335 
336     QString p = QStringLiteral("%1/%2").arg(CSYNC_TEST_DIR, QStringLiteral("goodone/"));
337     int rc = oc_mkdir(p);
338     assert_int_equal(rc, 0);
339 
340     p = QStringLiteral("%1/goodone/ugly\xEF\xBB\xBF\x32.txt").arg(CSYNC_TEST_DIR); // file with encoding error
341 
342     rc = oc_mkdir(p);
343 
344     assert_int_equal(rc, 0);
345 
346     int files_cnt = 0;
347     traverse_dir(state, CSYNC_TEST_DIR, &files_cnt);
348     const auto expected_result = QStringLiteral("<DIR> %1/goodone"
349                                                 "<DIR> %1/goodone/ugly\xEF\xBB\xBF\x32.txt")
350                                      .arg(CSYNC_TEST_DIR);
351     assert_string_equal(sv->result.constData(), expected_result.toUtf8().constData());
352 
353     assert_int_equal(files_cnt, 0);
354 }
355 
torture_run_tests(void)356 int torture_run_tests(void)
357 {
358     const struct CMUnitTest tests[] = {
359         cmocka_unit_test_setup_teardown(check_readdir_shorttree, setup_testenv, teardown),
360         cmocka_unit_test_setup_teardown(check_readdir_with_content, setup_testenv, teardown),
361         cmocka_unit_test_setup_teardown(check_readdir_longtree, setup_testenv, teardown),
362         cmocka_unit_test_setup_teardown(check_readdir_bigunicode, setup_testenv, teardown),
363     };
364 
365     return cmocka_run_group_tests(tests, nullptr, nullptr);
366 }
367