/* * libcsync -- a library to sync a directory with another * * Copyright (c) 2015-2013 by Klaas Freitag * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include "csync.h" #include "std/c_alloc.h" #include "std/c_string.h" #include "vio/csync_vio_local.h" #include static const auto CSYNC_TEST_DIR = []{ return QStringLiteral("%1/csync_test").arg(QDir::tempPath());}(); #include "torture.h" namespace { int oc_mkdir(const QString &path) { return QDir(path).mkpath(path) ? 0 : -1; } } #define WD_BUFFER_SIZE 255 static mbchar_t wd_buffer[WD_BUFFER_SIZE]; typedef struct { char *result; char *ignored_dir; } statevar; /* remove the complete test dir */ static int wipe_testdir() { QDir tmp(CSYNC_TEST_DIR); if (tmp.exists()) { return tmp.removeRecursively() ? 0 : 1; } return 0; } static int setup_testenv(void **state) { int rc; rc = wipe_testdir(); assert_int_equal(rc, 0); auto dir = CSYNC_TEST_DIR; rc = oc_mkdir(dir); assert_int_equal(rc, 0); assert_non_null(_tgetcwd(wd_buffer, WD_BUFFER_SIZE)); #ifdef Q_OS_WIN rc = _tchdir(dir.toStdWString().data()); #else rc = _tchdir(dir.toLocal8Bit().constData()); #endif assert_int_equal(rc, 0); /* --- initialize csync */ statevar *mystate = (statevar*)malloc( sizeof(statevar) ); mystate->result = NULL; *state = mystate; return 0; } static void output( const char *text ) { printf("%s\n", text); } static int teardown(void **state) { int rc; output("================== Tearing down!\n"); rc = _tchdir(wd_buffer); assert_int_equal(rc, 0); rc = wipe_testdir(); assert_int_equal(rc, 0); SAFE_FREE(((statevar*)*state)->result); SAFE_FREE(*state); return 0; } /* This function takes a relative path, prepends it with the CSYNC_TEST_DIR * and creates each sub directory. */ static void create_dirs( const char *path ) { int rc; auto _mypath = QStringLiteral("%1/%2").arg(CSYNC_TEST_DIR, path).toUtf8(); char *mypath = _mypath.data(); char *p = mypath + CSYNC_TEST_DIR.size() + 1; /* start behind the offset */ int i = 0; assert_non_null(path); while( *(p+i) ) { if( *(p+i) == '/' ) { p[i] = '\0'; auto mb_dir = QString::fromUtf8(mypath); rc = oc_mkdir(mb_dir); if(rc) { rc = errno; } assert_int_equal(rc, 0); p[i] = '/'; } i++; } } /* * This function uses the vio_opendir, vio_readdir and vio_closedir functions * to traverse a file tree that was created before by the create_dir function. * * It appends a listing to the result member of the incoming struct in *state * that can be compared later to what was expected in the calling functions. * * The int parameter cnt contains the number of seen files (not dirs) in the * whole tree. * */ static void traverse_dir(void **state, const QString &dir, int *cnt) { csync_vio_handle_t *dh; std::unique_ptr dirent; statevar *sv = (statevar*) *state; char *subdir; char *subdir_out; int rc; int is_dir; const char *format_str = "%s %s"; dh = csync_vio_local_opendir(dir); assert_non_null(dh); OCC::Vfs *vfs = nullptr; while( (dirent = csync_vio_local_readdir(dh, vfs)) ) { assert_non_null(dirent.get()); if (!dirent->original_path.isEmpty()) { sv->ignored_dir = c_strdup(dirent->original_path); continue; } assert_false(dirent->path.isEmpty()); if( c_streq( dirent->path, "..") || c_streq( dirent->path, "." )) { continue; } is_dir = (dirent->type == ItemTypeDirectory) ? 1:0; assert_int_not_equal( asprintf( &subdir, "%s/%s", dir.toUtf8().constData(), dirent->path.constData() ), -1 ); assert_int_not_equal( asprintf( &subdir_out, format_str, is_dir ? "":" ", subdir), -1 ); if( is_dir ) { if( !sv->result ) { sv->result = c_strdup( subdir_out); } else { int newlen = 1+strlen(sv->result)+strlen(subdir_out); char *tmp = sv->result; sv->result = (char*)c_malloc(newlen); strcpy( sv->result, tmp); SAFE_FREE(tmp); strcat( sv->result, subdir_out ); } } else { *cnt = *cnt +1; } output(subdir_out); if( is_dir ) { traverse_dir( state, subdir, cnt); } SAFE_FREE(subdir); SAFE_FREE(subdir_out); } rc = csync_vio_local_closedir(dh); assert_int_equal(rc, 0); } static void create_file( const char *path, const char *name, const char *content) { QFile file(QStringLiteral("%1/%2%3").arg(CSYNC_TEST_DIR, path, name)); assert_int_equal(1, file.open(QIODevice::WriteOnly | QIODevice::NewOnly)); file.write(content); } static void check_readdir_shorttree(void **state) { statevar *sv = (statevar*) *state; const char *t1 = "alibaba/und/die/vierzig/räuber/"; create_dirs( t1 ); int files_cnt = 0; traverse_dir(state, CSYNC_TEST_DIR, &files_cnt); assert_string_equal( sv->result, QString::fromUtf8(" %1/alibaba" " %1/alibaba/und" " %1/alibaba/und/die" " %1/alibaba/und/die/vierzig" " %1/alibaba/und/die/vierzig/räuber").arg(CSYNC_TEST_DIR).toUtf8().constData() ); assert_int_equal(files_cnt, 0); } static void check_readdir_with_content(void **state) { statevar *sv = (statevar*) *state; int files_cnt = 0; const char *t1 = "warum/nur/40/Räuber/"; create_dirs( t1 ); create_file( t1, "Räuber Max.txt", "Der Max ist ein schlimmer finger"); create_file( t1, "пя́тница.txt", "Am Freitag tanzt der Ürk"); traverse_dir(state, CSYNC_TEST_DIR, &files_cnt); assert_string_equal( sv->result, QString::fromUtf8(" %1/warum" " %1/warum/nur" " %1/warum/nur/40" " %1/warum/nur/40/Räuber").arg(CSYNC_TEST_DIR).toUtf8().constData()); /* " %1/warum/nur/40/Räuber/Räuber Max.txt" " %1/warum/nur/40/Räuber/пя́тница.txt"; */ assert_int_equal(files_cnt, 2); /* Two files in the sub dir */ } static void check_readdir_longtree(void **state) { statevar *sv = (statevar*) *state; /* Strange things here: Compilers only support strings with length of 4k max. * The expected result string is longer, so it needs to be split up in r1, r2 and r3 */ /* create the test tree */ 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/"; create_dirs( t1 ); const auto r1 = QString::fromUtf8( " %1/vierzig" " %1/vierzig/mann" " %1/vierzig/mann/auf" " %1/vierzig/mann/auf/des" " %1/vierzig/mann/auf/des/toten" " %1/vierzig/mann/auf/des/toten/Mann" " %1/vierzig/mann/auf/des/toten/Mann/kiste" " %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh" " %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and" " %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne" " %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle" " %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll" " %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum").arg(CSYNC_TEST_DIR); const auto r2 = QString::fromUtf8( " %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und" " %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so" " %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen" " %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir" " %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG" " %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN" " %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF" " %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES" " %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN" " %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS" " %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" " %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); const auto r3 = QString::fromUtf8( " %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" " %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" " %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" " %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" " %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" " %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" " %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" " %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" " %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" " %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" " %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" " %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" " %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" " %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" " %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); /* assemble the result string ... */ const auto result = (r1 + r2 + r3).toUtf8(); int files_cnt = 0; traverse_dir(state, CSYNC_TEST_DIR, &files_cnt); assert_int_equal(files_cnt, 0); /* and compare. */ assert_string_equal( sv->result, result); } // https://github.com/owncloud/client/issues/3128 https://github.com/owncloud/client/issues/2777 static void check_readdir_bigunicode(void **state) { statevar *sv = (statevar*) *state; // 1: ? ASCII: 239 - EF // 2: ? ASCII: 187 - BB // 3: ? ASCII: 191 - BF // 4: ASCII: 32 - 20 QString p = QStringLiteral("%1/%2").arg(CSYNC_TEST_DIR, "goodone/" ); int rc = oc_mkdir(p); assert_int_equal(rc, 0); p = QStringLiteral("%1/%2").arg(CSYNC_TEST_DIR, "goodone/ugly\xEF\xBB\xBF\x32" ".txt" ); // file with encoding error rc = oc_mkdir(p); assert_int_equal(rc, 0); int files_cnt = 0; traverse_dir(state, CSYNC_TEST_DIR, &files_cnt); const auto expected_result = QString::fromUtf8(" %1/goodone" " %1/goodone/ugly\xEF\xBB\xBF\x32" ".txt").arg(CSYNC_TEST_DIR) ; assert_string_equal( sv->result, expected_result.toUtf8().constData()); assert_int_equal(files_cnt, 0); } int torture_run_tests(void) { const struct CMUnitTest tests[] = { cmocka_unit_test_setup_teardown(check_readdir_shorttree, setup_testenv, teardown), cmocka_unit_test_setup_teardown(check_readdir_with_content, setup_testenv, teardown), cmocka_unit_test_setup_teardown(check_readdir_longtree, setup_testenv, teardown), cmocka_unit_test_setup_teardown(check_readdir_bigunicode, setup_testenv, teardown), }; return cmocka_run_group_tests(tests, NULL, NULL); }