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