Imported Upstream version 1.0beta3
[platform/upstream/syncevolution.git] / src / syncevo / util.cpp
1 /*
2  * Copyright (C) 2008-2009 Patrick Ohly <patrick.ohly@gmx.de>
3  * Copyright (C) 2009 Intel Corporation
4  *
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.
9  *
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.
14  *
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
18  * 02110-1301  USA
19  */
20
21 #include "config.h"
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>
28
29 #include <synthesis/syerror.h>
30
31 #include <boost/scoped_array.hpp>
32 #include <boost/foreach.hpp>
33 #include <fstream>
34 #include <iostream>
35
36 #include <errno.h>
37 #include <unistd.h>
38 #include <sys/wait.h>
39 #include <sys/stat.h>
40 #include <sys/types.h>
41 #include <fcntl.h>
42 #include <dirent.h>
43 #include <limits.h>
44 #include <stdlib.h>
45
46 #if USE_SHA256 == 1
47 # include <glib.h>
48 #elif USE_SHA256 == 2
49 # include <nss/sechash.h>
50 # include <nss/hasht.h>
51 # include <nss.h>
52 #endif
53
54 #ifdef ENABLE_UNIT_TESTS
55 #include "test.h"
56 CPPUNIT_REGISTRY_ADD_TO_DEFAULT("SyncEvolution");
57 #endif
58
59 #include <syncevo/declarations.h>
60 SE_BEGIN_CXX
61
62 string normalizePath(const string &path)
63 {
64     string res;
65
66     res.reserve(path.size());
67     size_t index = 0;
68     while (index < path.size()) {
69         char curr = path[index];
70         res += curr;
71         index++;
72         if (curr == '/') {
73             while (index < path.size() &&
74                    (path[index] == '/' ||
75                     (path[index] == '.' &&
76                      index + 1 < path.size() &&
77                      (path[index + 1] == '.' ||
78                       path[index + 1] == '/')))) {
79                 index++;
80             }
81         }
82     }
83     if (!res.empty() && res[res.size() - 1] == '/') {
84         res.resize(res.size() - 1);
85     }
86     return res;
87 }
88
89 bool relToAbs(string &path)
90 {
91     char buffer[PATH_MAX+1];
92     if (realpath(path.c_str(), buffer)) {
93         path = buffer;
94         return true;
95     } else {
96         return false; 
97     }
98 }
99
100 void mkdir_p(const string &path)
101 {
102     boost::scoped_array<char> dirs(new char[path.size() + 1]);
103     char *curr = dirs.get();
104     strcpy(curr, path.c_str());
105     do {
106         char *nextdir = strchr(curr, '/');
107         if (nextdir) {
108             *nextdir = 0;
109             nextdir++;
110         }
111         if (*curr) {
112             if (access(dirs.get(),
113                        nextdir ? (R_OK|X_OK) : (R_OK|X_OK|W_OK)) &&
114                 (errno != ENOENT ||
115                  mkdir(dirs.get(), 0700))) {
116                 SyncContext::throwError(string(dirs.get()), errno);
117             }
118         }
119         if (nextdir) {
120             nextdir[-1] = '/';
121         }
122         curr = nextdir;
123     } while (curr);
124 }
125
126 void rm_r(const string &path, boost::function<bool (const string &,
127                                                     bool)> filter)
128 {
129     struct stat buffer;
130     if (lstat(path.c_str(), &buffer)) {
131         if (errno == ENOENT) {
132             return;
133         } else {
134             SyncContext::throwError(path, errno);
135         }
136     }
137
138     if (!S_ISDIR(buffer.st_mode)) {
139         if (!filter(path, false) ||
140             !unlink(path.c_str())) {
141             return;
142         } else {
143             SyncContext::throwError(path, errno);
144         }
145     }
146
147     ReadDir dir(path);
148     BOOST_FOREACH(const string &entry, dir) {
149         rm_r(path + "/" + entry, filter);
150     }
151     if (filter(path, true) &&
152         rmdir(path.c_str())) {
153         SyncContext::throwError(path, errno);
154     }
155 }
156
157 void cp_r(const string &from, const string &to)
158 {
159     if (isDir(from)) {
160         mkdir_p(to);
161         ReadDir dir(from);
162         BOOST_FOREACH(const string &entry, dir) {
163             cp_r(from + "/" + entry, to + "/" + entry);
164         }
165     } else {
166         ofstream out;
167         ifstream in;
168         out.open(to.c_str());
169         in.open(from.c_str());
170         char buf[8192];
171         do {
172             in.read(buf, sizeof(buf));
173             out.write(buf, in.gcount());
174         } while(in);
175         in.close();
176         out.close();
177         if (out.bad() || in.bad()) {
178             SE_THROW(string("failed copying ") + from + " to " + to);
179         }
180     }
181 }
182
183 bool isDir(const string &path)
184 {
185     DIR *dir = opendir(path.c_str());
186     if (dir) {
187         closedir(dir);
188         return true;
189     } else if (errno != ENOTDIR && errno != ENOENT) {
190         SyncContext::throwError(path, errno);
191     }
192
193     return false;
194 }
195
196 int Execute(const std::string &cmd, ExecuteFlags flags) throw()
197 {
198     int ret = -1;
199
200     try {
201         // use simpler system() calls whenever we don't want to capture
202         // output, because it means that output is sent to the user
203         // directly
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";
209             }
210             if (flags & EXECUTE_NO_STDOUT) {
211                 fullcmd += " >/dev/null";
212             }
213             ret = system(fullcmd.c_str());
214         } else {
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();
222             switch (child) {
223             case 0: {
224                 // child process:
225                 // - close unused end of the pipes
226                 if (io.getStdout().m_read >= 0) {
227                     close(io.getStdout().m_read);
228                 }
229                 if (io.getStderr().m_read >= 0) {
230                     close(io.getStderr().m_read);
231                 }
232                 // - replace file descriptors 1 and 2 with the ones
233                 //   prepared for us or /dev/null
234                 int fd;
235                 int fd_null = open("/dev/null", O_WRONLY);
236                 fd = io.getStdout().m_write;
237                 if (fd <= 0) {
238                     fd = fd_null;
239                 }
240                 dup2(fd, STDOUT_FILENO);
241                 fd = io.getStderr().m_write;
242                 if (fd <= 0) {
243                     fd = fd_null;
244                 }
245                 dup2(fd, STDERR_FILENO);
246                 // - run command
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);
250                 exit(1);
251                 break;
252             }
253             case -1:
254                 // error handling in parent when fork() fails
255                 SE_LOG_ERROR(NULL, NULL, "%s: fork() failed: %s",
256                              cmd.c_str(), strerror(errno));
257                 break;
258             default:
259                 // parent:
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);
263                 }
264                 if (io.getStderr().m_write >= 0) {
265                     close(io.getStderr().m_write);
266                 }
267                 // - read until no more data or error triggers exception
268                 io.process();
269                 // - wait for child, without caring about errors
270                 waitpid(child, &ret, 0);
271                 break;
272             }
273         }
274     } catch (...) {
275         Exception::handle();
276     }
277
278     return ret;
279 }
280
281 UUID::UUID()
282 {
283     static class InitSRand {
284     public:
285         InitSRand() {
286             ifstream seedsource("/dev/urandom");
287             unsigned int seed;
288             if (!seedsource.get((char *)&seed, sizeof(seed))) {
289                 seed = time(NULL);
290             }
291             srand(seed);
292         }
293     } initSRand;
294
295     char buffer[16 * 4 + 5];
296     sprintf(buffer, "%08x-%04x-%04x-%02x%02x-%08x%04x",
297             rand() & 0xFFFFFFFF,
298             rand() & 0xFFFF,
299             (rand() & 0x0FFF) | 0x4000 /* RFC 4122 time_hi_and_version */,
300             (rand() & 0xBF) | 0x80 /* clock_seq_hi_and_reserved */,
301             rand() & 0xFF,
302             rand() & 0xFFFFFFFF,
303             rand() & 0xFFFF
304             );
305     this->assign(buffer);
306 }
307
308
309 ReadDir::ReadDir(const string &path, bool throwError) : m_path(path)
310 {
311     DIR *dir = NULL;
312
313     try {
314         dir = opendir(path.c_str());
315         if (!dir) {
316             SyncContext::throwError(path, errno);
317         }
318         errno = 0;
319         struct dirent *entry = readdir(dir);
320         while (entry) {
321             if (strcmp(entry->d_name, ".") &&
322                 strcmp(entry->d_name, "..")) {
323                 m_entries.push_back(entry->d_name);
324             }
325             entry = readdir(dir);
326         }
327         if (errno) {
328             SyncContext::throwError(path, errno);
329         }
330     } catch(...) {
331         if (dir) {
332             closedir(dir);
333         }
334         if (throwError) {
335             throw;
336         } else {
337             return;
338         }
339     }
340
341     closedir(dir);
342 }
343
344 std::string ReadDir::find(const string &entry, bool caseSensitive)
345 {
346     BOOST_FOREACH(const string &e, *this) {
347         if (caseSensitive ? e == entry : boost::iequals(e, entry)) {
348             return m_path + "/" + e;
349         }
350     }
351     return "";
352 }
353
354 bool ReadFile(const string &filename, string &content)
355 {
356     ifstream in;
357     in.open(filename.c_str());
358     ostringstream out;
359     char buf[8192];
360     do {
361         in.read(buf, sizeof(buf));
362         out.write(buf, in.gcount());
363     } while(in);
364
365     content = out.str();
366     return in.eof();
367 }
368
369 unsigned long Hash(const char *str)
370 {
371     unsigned long hashval = 5381;
372     int c;
373
374     while ((c = *str++) != 0) {
375         hashval = ((hashval << 5) + hashval) + c;
376     }
377
378     return hashval;
379 }
380
381 unsigned long Hash(const std::string &str)
382 {
383     unsigned long hashval = 5381;
384
385     BOOST_FOREACH(int c, str) {
386         hashval = ((hashval << 5) + hashval) + c;
387     }
388
389     return hashval;
390 }
391
392 std::string SHA_256(const std::string &data)
393 {
394 #if USE_SHA256 == 1
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
399     std::string res;
400     unsigned char hash[SHA256_LENGTH];
401     static bool initialized;
402     if (!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
409         // best we can here.
410         NSS_NoDB_Init(NULL);
411         initialized = true;
412     }
413
414     if (HASH_HashBuf(HASH_AlgSHA256, hash, (unsigned char *)data.c_str(), data.size()) != SECSuccess) {
415         SE_THROW("NSS HASH_HashBuf() failed");
416     }
417     res.reserve(SHA256_LENGTH * 2);
418     BOOST_FOREACH(unsigned char value, hash) {
419         res += StringPrintf("%02x", value);
420     }
421     return res;
422 #else
423     SE_THROW("Hash256() not implemented");
424     return "";
425 #endif
426 }
427
428
429 std::string StringPrintf(const char *format, ...)
430 {
431     va_list ap;
432     va_start(ap, format);
433     std::string res = StringPrintfV(format, ap);
434     va_end(ap);
435     return res;
436 }
437
438 std::string StringPrintfV(const char *format, va_list ap)
439 {
440     va_list aq;
441
442     char *buffer = NULL, *nbuffer = NULL;
443     ssize_t size = 0;
444     ssize_t realsize = 255;
445     do {
446         // vsnprintf() destroys ap, so make a copy first
447         va_copy(aq, ap);
448
449         if (size < realsize) {
450             nbuffer = (char *)realloc(buffer, realsize + 1);
451             if (!nbuffer) {
452                 if (buffer) {
453                     free(buffer);
454                 }
455                 return "";
456             }
457             size = realsize;
458             buffer = nbuffer;
459         }
460
461         realsize = vsnprintf(buffer, size + 1, format, aq);
462         if (realsize == -1) {
463             // old-style vnsprintf: exact len unknown, try again with doubled size
464             realsize = size * 2;
465         }
466         va_end(aq);
467     } while(realsize > size);
468
469     std::string res = buffer;
470     free(buffer);
471     return res;
472 }
473
474 SyncMLStatus Exception::handle(SyncMLStatus *status, Logger *logger)
475 {
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);
479
480     try {
481         throw;
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());
501     } catch (...) {
502         SE_LOG_ERROR(logger, NULL, "unknown error");
503     }
504
505     if (status && *status == STATUS_OK) {
506         *status = new_status;
507     }
508     return status ? *status : new_status;
509 }
510
511 std::string SubstEnvironment(const std::string &str)
512 {
513     std::stringstream res;
514     size_t envstart = std::string::npos;
515     size_t off;
516
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;
522
523                 const char *val = getenv(envname.c_str());
524                 if (val) {
525                     res << val;
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";
532                 }
533             }
534         } else {
535             if (str[off] == '$' &&
536                 off + 1 < str.size() &&
537                 str[off + 1] == '{') {
538                 envstart = off + 2;
539                 off++;
540             } else {
541                 res << str[off];
542             }
543         }
544     }
545
546     return res.str();
547 }
548
549 std::vector<std::string> unescapeJoinedString (const std::string& src, char sep)
550 {
551     std::vector<std::string> splitStrings;
552     size_t pos1 = 0, pos2 = 0, pos3 = 0;
553     std::string s1, s2;
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 '\'
561         // characters
562         if (!((s1.length() - ((pos == s1.npos) ? 0: pos-1)) &1 )) {
563             s2="";
564             boost::trim (s1);
565             for (std::string::iterator i = s1.begin(); i != s1.end(); i++) {
566                 //unescape characters
567                 if (*i == '\\') {
568                     if(++i == s1.end()) {
569                         break;
570                     }
571                 }
572                 s2+=*i;
573             }
574             splitStrings.push_back (s2);
575             pos1 = pos3;
576         }
577     }
578     return splitStrings;
579 }
580
581 ScopedEnvChange::ScopedEnvChange(const string &var, const string &value) :
582     m_var(var)
583 {
584     const char *oldval = getenv(var.c_str());
585     if (oldval) {
586         m_oldvalset = true;
587         m_oldval = oldval;
588     } else {
589         m_oldvalset = false;
590     }
591     setenv(var.c_str(), value.c_str(), 1);
592 }
593
594 ScopedEnvChange::~ScopedEnvChange()
595 {
596     if (m_oldvalset) {
597         setenv(m_var.c_str(), m_oldval.c_str(), 1);
598     } else {
599         unsetenv(m_var.c_str());
600     } 
601 }
602
603 std::string getCurrentTime()
604 {
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);
609     return buffer.get();
610 }
611
612 SE_END_CXX