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