Initialize Tizen 2.3
[framework/web/wrt-plugins-common.git] / src_mobile / modules / tizen / Filesystem / Node.cpp
1 /*
2  * Copyright (c) 2011 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  *    Licensed under the Apache License, Version 2.0 (the "License");
5  *    you may not use this file except in compliance with the License.
6  *    You may obtain a copy of the License at
7  *
8  *        http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *    Unless required by applicable law or agreed to in writing, software
11  *    distributed under the License is distributed on an "AS IS" BASIS,
12  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *    See the License for the specific language governing permissions and
14  *    limitations under the License.
15  */
16 #include "Node.h"
17 #include <algorithm>
18 #include <memory>
19 #include <typeinfo>
20 #include <sys/types.h>
21 #include <cstdio>
22 #include <unistd.h>
23 #include <fts.h>
24 #include <dirent.h>
25 #include <errno.h>
26 #include <pcrecpp.h>
27 #include <sstream>
28 #include <Commons/Exception.h>
29 #include <Filesystem/PathUtils.h>
30 #include <Filesystem/Enums.h>
31 #include "Manager.h"
32 #include "NodeFilterMatcher.h"
33
34 namespace WrtDeviceApis {
35 namespace Filesystem {
36 using namespace Api;
37
38 INodePtr Node::resolve(const IPathPtr& path)
39 {
40     struct stat info;
41     if (lstat(path->getFullPath().c_str(), &info) != 0) {
42         LogError("File: " << path->getFullPath().c_str());
43         ThrowMsg(Commons::PlatformException,
44                  "Node does not exist or access denied.");
45     }
46
47     if (!S_ISDIR(info.st_mode) && !S_ISREG(info.st_mode)) {
48         ThrowMsg(Commons::PlatformException,
49                  "Platform node is of unsupported type.");
50     }
51
52     NodeType type = S_ISDIR(info.st_mode) ? NT_DIRECTORY : NT_FILE;
53     NodePtr result(new Node(path, type));
54     return DPL::StaticPointerCast<INode>(result);
55 }
56
57 IPathPtr Node::getPath() const
58 {
59     return IPath::create(m_path->getFullPath());
60 }
61
62 INodePtr Node::getChild(const IPathPtr& path)
63 {
64     if (m_type != NT_DIRECTORY) {
65         ThrowMsg(Commons::PlatformException, "Not a directory.");
66     }
67     return Node::resolve(*m_path + *path);
68 }
69
70 NodeType Node::getType() const
71 {
72     return m_type;
73 }
74
75 int Node::getPermissions() const
76 {
77     return m_perms;
78 }
79
80 void Node::setPermissions(int perms)
81 {
82     m_perms = perms;
83 }
84
85 Node::NameList Node::getChildNames() const
86 {
87     if (m_type != NT_DIRECTORY) {
88         ThrowMsg(Commons::PlatformException, "Node is not directory.");
89     }
90
91     if ((m_perms & PERM_READ) == 0) {
92         ThrowMsg(Commons::SecurityException, "No permission.");
93     }
94
95     DIR* dir = opendir(m_path->getFullPath().c_str());
96     if (!dir) {
97         ThrowMsg(Commons::PlatformException,
98                  "Node has been deleted from platform.");
99     }
100
101     NameList result;
102     errno = 0;
103     int return_code;
104     struct dirent entry;
105     struct dirent *entry_result;
106     for (return_code = readdir_r(dir, &entry, &entry_result);
107             entry_result != NULL && return_code == 0;
108             return_code = readdir_r(dir, &entry, &entry_result)) {
109         if (!strncmp(entry.d_name, ".", 1) ||
110             !strncmp(entry.d_name, "..", 2))
111         {
112             continue;
113         }
114         result.push_back(entry.d_name);
115     }
116     if (return_code != 0 || errno != 0) {
117         ThrowMsg(Commons::PlatformException, "Error while reading directory.");
118     }
119
120     if (closedir(dir) != 0) {
121         ThrowMsg(Commons::PlatformException, "Could not close platform node.");
122     }
123
124     return result;
125 }
126
127 NodeList Node::getChildNodes(const NodeFilterPtr& filter) const
128 {
129     if (m_type != NT_DIRECTORY) {
130         ThrowMsg(Commons::PlatformException, "Node is not directory.");
131     }
132
133     if ((m_perms & PERM_READ) == 0) {
134         ThrowMsg(Commons::SecurityException, "No permission.");
135     }
136
137     DIR* dir = opendir(m_path->getFullPath().c_str());
138     if (!dir) {
139         ThrowMsg(Commons::PlatformException,
140                  "Node has been deleted from platform.");
141     }
142
143     errno = 0;
144     NodeList result;
145     int return_code;
146     struct dirent entry;
147     struct dirent *entry_result;
148     for (return_code = readdir_r(dir, &entry, &entry_result);
149             entry_result != NULL && return_code == 0;
150             return_code = readdir_r(dir, &entry, &entry_result)) {
151         if (!strncmp(entry.d_name, ".", 1) ||
152             !strncmp(entry.d_name, "..", 2))
153         {
154             continue;
155         }
156         Try {
157             Assert(m_path);
158             INodePtr node = Node::resolve(*m_path + entry.d_name);
159             node->setPermissions(getPermissions()); // inherit access rights
160             if (NodeFilterMatcher::match(node, filter)) {
161                 result.push_back(node);
162             }
163         }
164         Catch(Commons::PlatformException) {}
165     }
166
167     if (return_code != 0 || errno != 0) {
168         ThrowMsg(Commons::PlatformException, "Error while reading directory.");
169     }
170
171     if (closedir(dir) != 0) {
172         ThrowMsg(Commons::PlatformException, "Could not close platform node.");
173     }
174
175     return result;
176 }
177
178 void Node::getChildNodes(const EventListNodesPtr& event)
179 {
180     LogDebug("ENTER");
181     EventRequestReceiver<EventListNodes>::PostRequest(event);
182 }
183
184 INodePtr Node::createChild(
185     const IPathPtr& path,
186     NodeType type,
187     int options)
188 {
189     if (m_type != NT_DIRECTORY) {
190         ThrowMsg(Commons::PlatformException, "Parent node is not a directory.");
191     }
192
193     if ((m_perms & PERM_WRITE) == 0) {
194         ThrowMsg(Commons::SecurityException, "Not enough permissions.");
195     }
196
197     Assert(m_path);
198     Assert(path);
199     IPathPtr childPath = *m_path + *path;
200     if (exists(childPath)) {
201         ThrowMsg(Commons::PlatformException, "Node already exists.");
202     }
203
204     NodePtr result;
205     switch (type) {
206     case NT_FILE:
207         result.Reset(createAsFile(childPath, options));
208         break;
209     case NT_DIRECTORY:
210         result.Reset(createAsDirectory(childPath, options));
211         break;
212     default:
213         ThrowMsg(Commons::PlatformException, "Unsupported node type.");
214     }
215     if (!!result) {
216         result->m_perms = m_perms;
217     } else {
218         ThrowMsg(Commons::PlatformException, "Node creation error");
219     }
220
221     return DPL::StaticPointerCast<INode>(result);
222 }
223
224 IStreamPtr Node::open(int mode)
225 {
226     if (m_type == NT_DIRECTORY) {
227         ThrowMsg(Commons::PlatformException,
228                  "Cannot attach stream to directory.");
229     }
230
231     if (((mode & AM_READ) && ((m_perms & PERM_READ) == 0)) ||
232         (((mode & AM_WRITE) ||
233           (mode & AM_APPEND)) && ((m_perms & PERM_WRITE) == 0)))
234     {
235         ThrowMsg(Commons::SecurityException, "Not enough permissions.");
236     }
237
238     DPL::Mutex::ScopedLock lock(&m_openStreamsMutex);
239     StreamPtr stream(new Stream(SharedFromThis(), mode));
240     m_openStreams.insert(stream);
241     IManager::getInstance().addOpenedNode(DPL::StaticPointerCast<INode>(
242                                               SharedFromThis()));
243     return DPL::StaticPointerCast<IStream>(stream);
244 }
245
246 void Node::open(const EventOpenPtr& event)
247 {
248     LogDebug("ENTER");
249     EventRequestReceiver<EventOpen>::PostRequest(event);
250 }
251
252 void Node::remove(int options)
253 {
254     switch (m_type) {
255     case NT_FILE:
256         removeAsFile(m_path);
257         break;
258     case NT_DIRECTORY:
259         removeAsDirectory(m_path, (options & OPT_RECURSIVE));
260         break;
261     }
262 }
263
264 std::size_t Node::getSize() const
265 {
266     if (m_type == NT_DIRECTORY) {
267         ThrowMsg(Commons::PlatformException,
268                  "Getting size for directories is not supported.");
269     }
270
271     struct stat info = stat(m_path);
272     if (!S_ISREG(info.st_mode)) {
273         ThrowMsg(Commons::PlatformException,
274                  "Specified node is not a regular file.");
275     }
276
277     return info.st_size;
278 }
279
280 std::time_t Node::getCreated() const
281 {
282     return stat(m_path).st_ctime;
283 }
284
285 std::time_t Node::getModified() const
286 {
287     return stat(m_path).st_mtime;
288 }
289
290 // TODO Optimize it, maybe store a flag indicating that node is a root.
291 INodePtr Node::getParent() const
292 {
293     LocationPaths roots = IManager::getInstance().getLocationPaths();
294     for (LocationPaths::iterator it = roots.begin(); it != roots.end(); ++it) {
295         Assert(*it);
296         Assert(m_path);
297         if (*(*it) == *m_path) {
298             return INodePtr();
299         }
300     }
301     return Node::resolve(IPath::create(m_path->getPath()));
302 }
303
304 int Node::getMode() const
305 {
306     int result = 0;
307     struct stat info = stat(m_path);
308     if (info.st_mode & S_IRUSR) {
309         result |= PM_USER_READ;
310     }
311     if (info.st_mode & S_IWUSR) {
312         result |= PM_USER_WRITE;
313     }
314     if (info.st_mode & S_IXUSR) {
315         result |= PM_USER_EXEC;
316     }
317     if (info.st_mode & S_IRGRP) {
318         result |= PM_GROUP_READ;
319     }
320     if (info.st_mode & S_IWGRP) {
321         result |= PM_GROUP_WRITE;
322     }
323     if (info.st_mode & S_IXGRP) {
324         result |= PM_GROUP_EXEC;
325     }
326     if (info.st_mode & S_IROTH) {
327         result |= PM_OTHER_READ;
328     }
329     if (info.st_mode & S_IWOTH) {
330         result |= PM_OTHER_WRITE;
331     }
332     if (info.st_mode & S_IXOTH) {
333         result |= PM_OTHER_EXEC;
334     }
335     return result;
336 }
337
338 void Node::read(const EventReadTextPtr& event)
339 {
340     LogDebug("ENTER");
341     EventRequestReceiver<EventReadText>::PostRequest(event);
342 }
343
344 void Node::onStreamClose(const StreamPtr& stream)
345 {
346     {
347         DPL::Mutex::ScopedLock lock(&m_openStreamsMutex);
348         m_openStreams.erase(stream);
349     }
350     if (m_openStreams.empty()) {
351         IManager::getInstance().removeOpenedNode(DPL::StaticPointerCast<INode>(
352                                                      SharedFromThis()));
353     }
354 }
355
356 bool Node::exists(const IPathPtr& path)
357 {
358     struct stat info;
359     memset(&info, 0, sizeof(struct stat));
360     int status = lstat(path->getFullPath().c_str(), &info);
361     if ((status == 0) || ((status != 0) && (errno != ENOENT))) {
362         return true;
363     }
364     return false;
365 }
366
367 struct stat Node::stat(const IPathPtr& path)
368 {
369     struct stat result;
370     memset(&result, 0, sizeof(struct stat));
371     if (::stat(path->getFullPath().c_str(),
372                &result) != 0)
373     {
374         LogError("File: " << path->getFullPath().c_str());
375         ThrowMsg(Commons::PlatformException, "Node does not exist or no access");
376     }
377     return result;
378 }
379
380 Node::Node(const IPathPtr& path,
381            NodeType type) :
382     m_path(path),
383     m_type(type),
384     m_perms(PERM_NONE)
385 {}
386
387 Node* Node::createAsFile(const IPathPtr& path,
388                          int /* options */)
389 {
390     LogDebug("ENTER");
391     createAsFileInternal(path);
392     return new Node(path, NT_FILE);
393 }
394
395 void Node::createAsFileInternal(const IPathPtr& path)
396 {
397     LogDebug("ENTER");
398     FILE* file = std::fopen(path->getFullPath().c_str(), "wb");
399     if (!file) {
400         ThrowMsg(Commons::PlatformException,
401                  "Platform node could not be created.");
402     }
403     std::fclose(file);
404 }
405
406 Node* Node::createAsDirectory(const IPathPtr& path,
407                               int options)
408 {
409     if (options & OPT_RECURSIVE) {
410         PathUtils::PathList parts = PathUtils::getParts(path);
411         PathUtils::PathListIterator it = parts.begin();
412         for (; it != parts.end(); ++it) {
413             if (!exists(*it)) {
414                 createAsDirectoryInternal(*it);
415             }
416         }
417     }
418     createAsDirectoryInternal(path);
419     return new Node(path, NT_DIRECTORY);
420 }
421
422 void Node::createAsDirectoryInternal(const IPathPtr& path)
423 {
424     if (mkdir(path->getFullPath().c_str(), S_IRWXU | S_IRWXG | S_IROTH |
425               S_IXOTH) != 0)
426     {
427         ThrowMsg(Commons::PlatformException,
428                  "Platform node could not be created.");
429     }
430 }
431
432 void Node::removeAsFile(const IPathPtr& path)
433 {
434     DPL::Mutex::ScopedLock lock(&m_openStreamsMutex);
435     if (!m_openStreams.empty()) {
436         ThrowMsg(Commons::PlatformException, "Node is locked for I/O.");
437     }
438     if (IManager::getInstance().checkIfOpened(path)) {
439         ThrowMsg(Commons::PlatformException, "Node is locked for I/O.");
440     }
441
442     if (unlink(path->getFullPath().c_str()) != 0) {
443         ThrowMsg(Commons::PlatformException,
444                  "Error while removing platform node.");
445     }
446 }
447
448 void Node::removeAsDirectory(const IPathPtr& path,
449                              bool recursive)
450 {
451     Assert(path);
452     if (recursive) {
453         FTS *fts;
454         FTSENT *ftsent;
455         int error = 0;
456         std::string pth = path->getFullPath();
457         char * const paths[] = { const_cast<char * const>(pth.c_str()), NULL };
458
459         if ((fts =
460                  fts_open(paths, FTS_PHYSICAL | FTS_NOCHDIR, NULL)) == NULL)
461         {
462             //ERROR
463             error = errno;
464             LogError(__PRETTY_FUNCTION__ << ": fts_open on "
465                                          << pth
466                                          << " failed with error: "
467                                          << strerror(error));
468             ThrowMsg(Commons::PlatformException, "Failed to traverse Node");
469         }
470
471         while ((ftsent = fts_read(fts)) != NULL) {
472             switch (ftsent->fts_info) {
473             case FTS_D:
474                 //directory in preorder - do nothing
475                 break;
476             case FTS_DP:
477                 //directory in postorder - remove
478                 errno = 0;
479                 if (rmdir(ftsent->fts_accpath) != 0) {
480                     if (errno == EEXIST) {
481                         ThrowMsg(Commons::PlatformException,
482                                  "Node has child nodes.");
483                     }
484                     ThrowMsg(Commons::PlatformException,
485                              "Error while removing platform node.");
486                 }
487                 break;
488             case FTS_DC:
489             case FTS_F:
490             case FTS_NSOK:
491             case FTS_SL:
492             case FTS_SLNONE:
493             case FTS_DEFAULT:
494             {
495                 //regular files and other objects that can safely be removed
496                 IPathPtr file_path = IPath::create(ftsent->fts_path);
497                 removeAsFile(file_path);
498                 break;
499             }
500             case FTS_NS:
501             case FTS_DOT:
502             case FTS_DNR:
503             case FTS_ERR:
504             default:
505                 LogWarning(__PRETTY_FUNCTION__
506                            << ": traversal failed with error: "
507                            << strerror(ftsent->fts_errno));
508                 break;
509             }
510         }
511
512         if (fts_close(fts) == -1) {
513             error = errno;
514             LogWarning(__PRETTY_FUNCTION__ << ": fts_close failed with error: "
515                                            << strerror(error));
516         }
517     } else {
518         if (rmdir(path->getFullPath().c_str()) != 0) {
519             if (errno == EEXIST) {
520                 ThrowMsg(Commons::PlatformException, "Node has child nodes.");
521             }
522             ThrowMsg(Commons::PlatformException,
523                      "Error while removing platform node.");
524         }
525     }
526 }
527
528 void Node::OnRequestReceived(const EventListNodesPtr& event)
529 {
530     try {
531         NodeList list = event->getNode()->getChildNodes(event->getFilter());
532         event->setResult(list);
533     } catch (const Commons::PlatformException& ex) {
534         LogError("Exception: " << ex.GetMessage());
535         event->setExceptionCode(Commons::ExceptionCodes::PlatformException);
536     } catch (const Commons::SecurityException& ex) {
537         LogError("Exception: " << ex.GetMessage());
538         event->setExceptionCode(Commons::ExceptionCodes::SecurityException);
539     }
540 }
541
542 void Node::OnRequestReceived(const EventOpenPtr& event)
543 {
544     if (!event->checkCancelled()) {
545         try {
546             IStreamPtr result = open(event->getMode());
547             event->setResult(result);
548         } catch (const Commons::PlatformException& ex) {
549             LogError("Exception: " << ex.GetMessage());
550             event->setExceptionCode(Commons::ExceptionCodes::PlatformException);
551         } catch (const Commons::SecurityException& ex) {
552             LogError("Exception: " << ex.GetMessage());
553             event->setExceptionCode(Commons::ExceptionCodes::SecurityException);
554         }
555         //event can be cancelled before executing this code.
556         //when it comes here we doesn't allow it anymore
557         event->setCancelAllowed(false);
558     } else {
559         event->setCancelAllowed(true);
560     }
561 }
562
563 void Node::OnRequestReceived(const EventReadTextPtr& event)
564 {
565     Try {
566         event->setResult(readText());
567         LogDebug("LEAVIN GRACEFULLY");
568     }
569     Catch(Commons::PlatformException) {
570         event->setExceptionCode(Commons::ExceptionCodes::PlatformException);
571     }
572     Catch(Commons::SecurityException) {
573         event->setExceptionCode(Commons::ExceptionCodes::SecurityException);
574     }
575     //this function doesn't change state of the platform,
576     //so we can allow to cancel it and discard results.
577     event->setCancelAllowed(true);
578 }
579
580 std::string Node::readText()
581 {
582     if (m_type != NT_FILE) {
583         ThrowMsg(Commons::PlatformException, "Node is not a file.");
584     }
585
586     if ((m_perms & PERM_READ) == 0) {
587         ThrowMsg(Commons::SecurityException, "No permission.");
588     }
589
590     std::stringstream result;
591     DPL::SharedPtr<Stream> stream(new Stream(SharedFromThis(), AM_READ));
592     while (!stream->isEof()) {
593         result << stream->getLine();
594         if (!stream->isEof()) {
595             result << '\n';
596         }
597     }
598     stream->close();
599     return result.str();
600 }
601
602 std::string Node::toUri(int /*widgetId*/) const
603 {
604     // TODO I believe moving this feature to WrtWrapper would make more sense.
605     return "file://" + m_path->getFullPath();
606 }
607 } // Filesystem
608 } // WrtDeviceApis