2 * Copyright (C) 2008-2009 Patrick Ohly <patrick.ohly@gmx.de>
3 * Copyright (C) 2009 Intel Corporation
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) version 3.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 #include <syncevo/util.h>
23 #include <syncevo/SyncContext.h>
24 #include <syncevo/TransportAgent.h>
25 #include <syncevo/SynthesisEngine.h>
26 #include <syncevo/Logging.h>
27 #include <syncevo/LogRedirect.h>
29 #include <synthesis/syerror.h>
31 #include <boost/scoped_array.hpp>
32 #include <boost/foreach.hpp>
40 #include <sys/types.h>
49 # include <nss/sechash.h>
50 # include <nss/hasht.h>
54 #ifdef ENABLE_UNIT_TESTS
56 CPPUNIT_REGISTRY_ADD_TO_DEFAULT("SyncEvolution");
59 #include <syncevo/declarations.h>
62 string normalizePath(const string &path)
66 res.reserve(path.size());
68 while (index < path.size()) {
69 char curr = path[index];
73 while (index < path.size() &&
74 (path[index] == '/' ||
75 (path[index] == '.' &&
76 index + 1 < path.size() &&
77 (path[index + 1] == '.' ||
78 path[index + 1] == '/')))) {
83 if (!res.empty() && res[res.size() - 1] == '/') {
84 res.resize(res.size() - 1);
89 bool relToAbs(string &path)
91 char buffer[PATH_MAX+1];
92 if (realpath(path.c_str(), buffer)) {
100 void mkdir_p(const string &path)
102 boost::scoped_array<char> dirs(new char[path.size() + 1]);
103 char *curr = dirs.get();
104 strcpy(curr, path.c_str());
106 char *nextdir = strchr(curr, '/');
112 if (access(dirs.get(),
113 nextdir ? (R_OK|X_OK) : (R_OK|X_OK|W_OK)) &&
115 mkdir(dirs.get(), 0700))) {
116 SyncContext::throwError(string(dirs.get()), errno);
126 void rm_r(const string &path, boost::function<bool (const string &,
130 if (lstat(path.c_str(), &buffer)) {
131 if (errno == ENOENT) {
134 SyncContext::throwError(path, errno);
138 if (!S_ISDIR(buffer.st_mode)) {
139 if (!filter(path, false) ||
140 !unlink(path.c_str())) {
143 SyncContext::throwError(path, errno);
148 BOOST_FOREACH(const string &entry, dir) {
149 rm_r(path + "/" + entry, filter);
151 if (filter(path, true) &&
152 rmdir(path.c_str())) {
153 SyncContext::throwError(path, errno);
157 void cp_r(const string &from, const string &to)
162 BOOST_FOREACH(const string &entry, dir) {
163 cp_r(from + "/" + entry, to + "/" + entry);
168 out.open(to.c_str());
169 in.open(from.c_str());
172 in.read(buf, sizeof(buf));
173 out.write(buf, in.gcount());
177 if (out.bad() || in.bad()) {
178 SE_THROW(string("failed copying ") + from + " to " + to);
183 bool isDir(const string &path)
185 DIR *dir = opendir(path.c_str());
189 } else if (errno != ENOTDIR && errno != ENOENT) {
190 SyncContext::throwError(path, errno);
196 int Execute(const std::string &cmd, ExecuteFlags flags) throw()
201 // use simpler system() calls whenever we don't want to capture
202 // output, because it means that output is sent to the user
204 if (((flags & EXECUTE_NO_STDERR) || !LogRedirect::redirectingStderr()) &&
205 ((flags & EXECUTE_NO_STDOUT) || !LogRedirect::redirectingStdout())) {
206 string fullcmd = cmd;
207 if (flags & EXECUTE_NO_STDERR) {
208 fullcmd += " 2>/dev/null";
210 if (flags & EXECUTE_NO_STDOUT) {
211 fullcmd += " >/dev/null";
213 ret = system(fullcmd.c_str());
215 // Need to catch at least one of stdout or stderr. A
216 // low-tech solution would be to use temporary files which
217 // are read after system() returns. But we want true
218 // streaming of the output, so use fork()/exec() plus
219 // reliable output redirection.
220 LogRedirect io(flags);
221 pid_t child = fork();
225 // - close unused end of the pipes
226 if (io.getStdout().m_read >= 0) {
227 close(io.getStdout().m_read);
229 if (io.getStderr().m_read >= 0) {
230 close(io.getStderr().m_read);
232 // - replace file descriptors 1 and 2 with the ones
233 // prepared for us or /dev/null
235 int fd_null = open("/dev/null", O_WRONLY);
236 fd = io.getStdout().m_write;
240 dup2(fd, STDOUT_FILENO);
241 fd = io.getStderr().m_write;
245 dup2(fd, STDERR_FILENO);
247 execl("/bin/sh", "sh", "-c", cmd.c_str(), NULL);
248 // - error handling if execl() failed (= returned)
249 std::cerr << cmd << ": execl() failed: " << strerror(errno);
254 // error handling in parent when fork() fails
255 SE_LOG_ERROR(NULL, NULL, "%s: fork() failed: %s",
256 cmd.c_str(), strerror(errno));
260 // - close write side so that we can detect "end of data"
261 if (io.getStdout().m_write >= 0) {
262 close(io.getStdout().m_write);
264 if (io.getStderr().m_write >= 0) {
265 close(io.getStderr().m_write);
267 // - read until no more data or error triggers exception
269 // - wait for child, without caring about errors
270 waitpid(child, &ret, 0);
283 static class InitSRand {
286 ifstream seedsource("/dev/urandom");
288 if (!seedsource.get((char *)&seed, sizeof(seed))) {
295 char buffer[16 * 4 + 5];
296 sprintf(buffer, "%08x-%04x-%04x-%02x%02x-%08x%04x",
299 (rand() & 0x0FFF) | 0x4000 /* RFC 4122 time_hi_and_version */,
300 (rand() & 0xBF) | 0x80 /* clock_seq_hi_and_reserved */,
305 this->assign(buffer);
309 ReadDir::ReadDir(const string &path, bool throwError) : m_path(path)
314 dir = opendir(path.c_str());
316 SyncContext::throwError(path, errno);
319 struct dirent *entry = readdir(dir);
321 if (strcmp(entry->d_name, ".") &&
322 strcmp(entry->d_name, "..")) {
323 m_entries.push_back(entry->d_name);
325 entry = readdir(dir);
328 SyncContext::throwError(path, errno);
344 std::string ReadDir::find(const string &entry, bool caseSensitive)
346 BOOST_FOREACH(const string &e, *this) {
347 if (caseSensitive ? e == entry : boost::iequals(e, entry)) {
348 return m_path + "/" + e;
354 bool ReadFile(const string &filename, string &content)
357 in.open(filename.c_str());
361 in.read(buf, sizeof(buf));
362 out.write(buf, in.gcount());
369 unsigned long Hash(const char *str)
371 unsigned long hashval = 5381;
374 while ((c = *str++) != 0) {
375 hashval = ((hashval << 5) + hashval) + c;
381 unsigned long Hash(const std::string &str)
383 unsigned long hashval = 5381;
385 BOOST_FOREACH(int c, str) {
386 hashval = ((hashval << 5) + hashval) + c;
392 std::string SHA_256(const std::string &data)
395 GString hash(g_compute_checksum_for_data(G_CHECKSUM_SHA256, (guchar *)data.c_str(), data.size()),
396 "g_compute_checksum_for_data() failed");
397 return std::string(hash.get());
398 #elif USE_SHA256 == 2
400 unsigned char hash[SHA256_LENGTH];
401 static bool initialized;
403 // https://wiki.mozilla.org/NSS_Shared_DB_And_LINUX has
404 // some comments which indicate that calling init multiple
405 // times works, but http://www.mozilla.org/projects/security/pki/nss/ref/ssl/sslfnc.html#1234224
406 // says it must only be called once. How that is supposed
407 // to work when multiple, independent libraries have to
408 // use NSS is beyond me. Bad design. At least let's do the
414 if (HASH_HashBuf(HASH_AlgSHA256, hash, (unsigned char *)data.c_str(), data.size()) != SECSuccess) {
415 SE_THROW("NSS HASH_HashBuf() failed");
417 res.reserve(SHA256_LENGTH * 2);
418 BOOST_FOREACH(unsigned char value, hash) {
419 res += StringPrintf("%02x", value);
423 SE_THROW("Hash256() not implemented");
429 std::string StringPrintf(const char *format, ...)
432 va_start(ap, format);
433 std::string res = StringPrintfV(format, ap);
438 std::string StringPrintfV(const char *format, va_list ap)
442 char *buffer = NULL, *nbuffer = NULL;
444 ssize_t realsize = 255;
446 // vsnprintf() destroys ap, so make a copy first
449 if (size < realsize) {
450 nbuffer = (char *)realloc(buffer, realsize + 1);
461 realsize = vsnprintf(buffer, size + 1, format, aq);
462 if (realsize == -1) {
463 // old-style vnsprintf: exact len unknown, try again with doubled size
467 } while(realsize > size);
469 std::string res = buffer;
474 SyncMLStatus Exception::handle(SyncMLStatus *status, Logger *logger)
476 // any problem here is a fatal local problem, unless set otherwise
477 // by the specific exception
478 SyncMLStatus new_status = SyncMLStatus(STATUS_FATAL + sysync::LOCAL_STATUS_CODE);
482 } catch (const TransportException &ex) {
483 SE_LOG_DEBUG(logger, NULL, "TransportException thrown at %s:%d",
484 ex.m_file.c_str(), ex.m_line);
485 SE_LOG_ERROR(logger, NULL, "%s", ex.what());
486 new_status = SyncMLStatus(sysync::LOCERR_TRANSPFAIL);
487 } catch (const BadSynthesisResult &ex) {
488 new_status = SyncMLStatus(ex.result());
489 SE_LOG_DEBUG(logger, NULL, "error code from Synthesis engine %s",
490 Status2String(new_status).c_str());
491 } catch (const StatusException &ex) {
492 new_status = ex.syncMLStatus();
493 SE_LOG_DEBUG(logger, NULL, "error code from SyncEvolution %s and exception thrown at %s:%d",
494 Status2String(new_status).c_str(), ex.m_file.c_str(), ex.m_line);
495 } catch (const Exception &ex) {
496 SE_LOG_DEBUG(logger, NULL, "exception thrown at %s:%d",
497 ex.m_file.c_str(), ex.m_line);
498 SE_LOG_ERROR(logger, NULL, "%s", ex.what());
499 } catch (const std::exception &ex) {
500 SE_LOG_ERROR(logger, NULL, "%s", ex.what());
502 SE_LOG_ERROR(logger, NULL, "unknown error");
505 if (status && *status == STATUS_OK) {
506 *status = new_status;
508 return status ? *status : new_status;
511 std::string SubstEnvironment(const std::string &str)
513 std::stringstream res;
514 size_t envstart = std::string::npos;
517 for(off = 0; off < str.size(); off++) {
518 if (envstart != std::string::npos) {
519 if (str[off] == '}') {
520 std::string envname = str.substr(envstart, off - envstart);
521 envstart = std::string::npos;
523 const char *val = getenv(envname.c_str());
526 } else if (envname == "XDG_CONFIG_HOME") {
527 res << getHome() << "/.config";
528 } else if (envname == "XDG_DATA_HOME") {
529 res << getHome() << "/.local/share";
530 } else if (envname == "XDG_CACHE_HOME") {
531 res << getHome() << "/.cache";
535 if (str[off] == '$' &&
536 off + 1 < str.size() &&
537 str[off + 1] == '{') {
549 std::vector<std::string> unescapeJoinedString (const std::string& src, char sep)
551 std::vector<std::string> splitStrings;
552 size_t pos1 = 0, pos2 = 0, pos3 = 0;
554 while (pos3 != src.npos) {
555 pos2 = src.find (sep, pos3);
556 s1 = src.substr (pos1,
557 (pos2 == std::string::npos) ? std::string::npos : pos2-pos1);
558 size_t pos = s1.find_last_not_of ("\\");
559 pos3 = (pos2 == std::string::npos) ?pos2 : pos2+1;
560 // A matching delimiter is a comma with even trailing '\'
562 if (!((s1.length() - ((pos == s1.npos) ? 0: pos-1)) &1 )) {
565 for (std::string::iterator i = s1.begin(); i != s1.end(); i++) {
566 //unescape characters
568 if(++i == s1.end()) {
574 splitStrings.push_back (s2);
581 ScopedEnvChange::ScopedEnvChange(const string &var, const string &value) :
584 const char *oldval = getenv(var.c_str());
591 setenv(var.c_str(), value.c_str(), 1);
594 ScopedEnvChange::~ScopedEnvChange()
597 setenv(m_var.c_str(), m_oldval.c_str(), 1);
599 unsetenv(m_var.c_str());
603 std::string getCurrentTime()
605 time_t seconds = time (NULL);
606 tm *data = localtime (&seconds);
607 arrayptr<char> buffer (new char [13]);
608 strftime (buffer.get(), 13, "%y%m%d%H%M%S", data);