tizen beta release
[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 <dirent.h>
24 #include <errno.h>
25 #include <pcrecpp.h>
26 #include <sstream>
27 #include <Commons/Exception.h>
28 #include <Filesystem/PathUtils.h>
29 #include <Filesystem/Enums.h>
30 #include "Manager.h"
31 #include "NodeFilterMatcher.h"
32
33 namespace WrtDeviceApis {
34 namespace Filesystem {
35
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     struct dirent *entry = NULL;
104     while ((entry = readdir(dir))) {
105         if (!strncmp(entry->d_name, ".",
106                      1) || !strncmp(entry->d_name, "..", 2)) {
107             continue;
108         }
109         result.push_back(entry->d_name);
110     }
111     if (errno != 0) {
112         ThrowMsg(Commons::PlatformException, "Error while reading directory.");
113     }
114
115     if (closedir(dir) != 0) {
116         ThrowMsg(Commons::PlatformException, "Could not close platform node.");
117     }
118
119     return result;
120 }
121
122 NodeList Node::getChildNodes(const NodeFilterPtr& filter) const
123 {
124     if (m_type != NT_DIRECTORY) {
125         ThrowMsg(Commons::PlatformException, "Node is not directory.");
126     }
127
128     if ((m_perms & PERM_READ) == 0) {
129         ThrowMsg(Commons::SecurityException, "No permission.");
130     }
131
132     DIR* dir = opendir(m_path->getFullPath().c_str());
133     if (!dir) {
134         ThrowMsg(Commons::PlatformException,
135                  "Node has been deleted from platform.");
136     }
137
138     errno = 0;
139     NodeList result;
140     struct dirent *entry = NULL;
141     while ((entry = readdir(dir))) {
142         if (!strncmp(entry->d_name, ".",
143                      1) || !strncmp(entry->d_name, "..", 2)) {
144             continue;
145         }
146         Try {
147             INodePtr node = Node::resolve(*m_path + entry->d_name);
148             node->setPermissions(getPermissions()); // inherit access rights
149             if (NodeFilterMatcher::match(node, filter)) {
150                 result.push_back(node);
151             }
152         }
153         Catch(Commons::PlatformException) {
154         }
155     }
156
157     if (errno != 0) {
158         ThrowMsg(Commons::PlatformException, "Error while reading directory.");
159     }
160
161     if (closedir(dir) != 0) {
162         ThrowMsg(Commons::PlatformException, "Could not close platform node.");
163     }
164
165     return result;
166 }
167
168 void Node::getChildNodes(const EventListNodesPtr& event)
169 {
170     LogDebug("ENTER");
171     EventRequestReceiver<EventListNodes>::PostRequest(event);
172 }
173
174 INodePtr Node::createChild(
175         const IPathPtr& path,
176         NodeType type,
177         int options)
178 {
179     if (m_type != NT_DIRECTORY) {
180         ThrowMsg(Commons::PlatformException, "Parent node is not a directory.");
181     }
182
183     if ((m_perms & PERM_WRITE) == 0) {
184         ThrowMsg(Commons::SecurityException, "Not enough permissions.");
185     }
186
187     IPathPtr childPath = *m_path + *path;
188     if (exists(childPath)) {
189         ThrowMsg(Commons::PlatformException, "Node already exists.");
190     }
191
192     NodePtr result;
193     switch (type) {
194     case NT_FILE:
195         result.Reset(createAsFile(childPath, options));
196         break;
197     case NT_DIRECTORY:
198         result.Reset(createAsDirectory(childPath, options));
199         break;
200     default:
201         ThrowMsg(Commons::PlatformException, "Unsupported node type.");
202     }
203     if (!!result) {
204         result->m_perms = m_perms;
205     } else {
206         ThrowMsg(Commons::PlatformException, "Node creation error");
207     }
208
209     return DPL::StaticPointerCast<INode>(result);
210 }
211
212 IStreamPtr Node::open(int mode)
213 {
214     if (m_type == NT_DIRECTORY) {
215         ThrowMsg(Commons::PlatformException,
216                  "Cannot attach stream to directory.");
217     }
218
219     if (((mode & AM_READ) && ((m_perms & PERM_READ) == 0)) ||
220         (((mode & AM_WRITE) ||
221           (mode & AM_APPEND)) && ((m_perms & PERM_WRITE) == 0))) {
222         ThrowMsg(Commons::SecurityException, "Not enough permissions.");
223     }
224
225     DPL::Mutex::ScopedLock lock(&m_openStreamsMutex);
226     StreamPtr stream(new Stream(SharedFromThis(), mode));
227     m_openStreams.insert(stream);
228     IManager::getInstance().addOpenedNode(DPL::StaticPointerCast<INode>(
229                                               SharedFromThis()));
230     return DPL::StaticPointerCast<IStream>(stream);
231 }
232
233 void Node::open(const EventOpenPtr& event)
234 {
235     LogDebug("ENTER");
236     EventRequestReceiver<EventOpen>::PostRequest(event);
237 }
238
239 void Node::remove(int options)
240 {
241     switch (m_type) {
242     case NT_FILE:
243         removeAsFile(m_path);
244         break;
245     case NT_DIRECTORY:
246         removeAsDirectory(m_path, (options & OPT_RECURSIVE));
247         break;
248     }
249 }
250
251 std::size_t Node::getSize() const
252 {
253     if (m_type == NT_DIRECTORY) {
254         ThrowMsg(Commons::PlatformException,
255                  "Getting size for directories is not supported.");
256     }
257
258     struct stat info = stat(m_path);
259     if (!S_ISREG(info.st_mode)) {
260         ThrowMsg(Commons::PlatformException,
261                  "Specified node is not a regular file.");
262     }
263
264     return info.st_size;
265 }
266
267 std::time_t Node::getCreated() const
268 {
269     return stat(m_path).st_ctime;
270 }
271
272 std::time_t Node::getModified() const
273 {
274     return stat(m_path).st_mtime;
275 }
276
277 // TODO Optimize it, maybe store a flag indicating that node is a root.
278 INodePtr Node::getParent() const
279 {
280     LocationPaths roots = IManager::getInstance().getLocationPaths();
281     for (LocationPaths::iterator it = roots.begin(); it != roots.end(); ++it) {
282         if (*(*it) == *m_path) {
283             return INodePtr();
284         }
285     }
286     return Node::resolve(IPath::create(m_path->getPath()));
287 }
288
289 int Node::getMode() const
290 {
291     int result = 0;
292     struct stat info = stat(m_path);
293     if (info.st_mode & S_IRUSR) { result |= PM_USER_READ; }
294     if (info.st_mode & S_IWUSR) { result |= PM_USER_WRITE; }
295     if (info.st_mode & S_IXUSR) { result |= PM_USER_EXEC; }
296     if (info.st_mode & S_IRGRP) { result |= PM_GROUP_READ; }
297     if (info.st_mode & S_IWGRP) { result |= PM_GROUP_WRITE; }
298     if (info.st_mode & S_IXGRP) { result |= PM_GROUP_EXEC; }
299     if (info.st_mode & S_IROTH) { result |= PM_OTHER_READ; }
300     if (info.st_mode & S_IWOTH) { result |= PM_OTHER_WRITE; }
301     if (info.st_mode & S_IXOTH) { result |= PM_OTHER_EXEC; }
302     return result;
303 }
304
305 void Node::read(const EventReadTextPtr& event)
306 {
307     LogDebug("ENTER");
308     EventRequestReceiver<EventReadText>::PostRequest(event);
309 }
310
311 void Node::onStreamClose(const StreamPtr& stream)
312 {
313     {
314         DPL::Mutex::ScopedLock lock(&m_openStreamsMutex);
315         m_openStreams.erase(stream);
316     }
317     if (m_openStreams.empty()) {
318         IManager::getInstance().removeOpenedNode(DPL::StaticPointerCast<INode>(
319                                                      SharedFromThis()));
320     }
321 }
322
323 bool Node::exists(const IPathPtr& path)
324 {
325     struct stat info;
326     memset(&info, 0, sizeof(struct stat));
327     int status = lstat(path->getFullPath().c_str(), &info);
328     if ((status == 0) || ((status != 0) && (errno != ENOENT))) {
329         return true;
330     }
331     return false;
332 }
333
334 struct stat Node::stat(const IPathPtr& path)
335 {
336     struct stat result;
337     memset(&result, 0, sizeof(struct stat));
338     if (::stat(path->getFullPath().c_str(),
339                 &result) != 0)
340     {
341         LogError("File: " << path->getFullPath().c_str());
342         ThrowMsg(Commons::PlatformException, "Node does not exist or no access");
343     }
344     return result;
345 }
346
347 Node::Node(const IPathPtr& path,
348            NodeType type) :
349     m_path(path),
350     m_type(type),
351     m_perms(PERM_NONE)
352 {
353 }
354
355 Node* Node::createAsFile(const IPathPtr& path,
356         int /* options */)
357 {
358     LogDebug("ENTER");
359     createAsFileInternal(path);
360     return new Node(path, NT_FILE);
361 }
362
363 void Node::createAsFileInternal(const IPathPtr& path)
364 {
365     LogDebug("ENTER");
366     FILE* file = std::fopen(path->getFullPath().c_str(), "wb");
367     if (!file) {
368         ThrowMsg(Commons::PlatformException,
369                  "Platform node could not be created.");
370     }
371     std::fclose(file);
372 }
373
374 Node* Node::createAsDirectory(const IPathPtr& path,
375         int options)
376 {
377     if (options & OPT_RECURSIVE) {
378         PathUtils::PathList parts = PathUtils::getParts(path);
379         PathUtils::PathListIterator it = parts.begin();
380         for (; it != parts.end(); ++it) {
381             if (!exists(*it)) { createAsDirectoryInternal(*it); }
382         }
383     }
384     createAsDirectoryInternal(path);
385     return new Node(path, NT_DIRECTORY);
386 }
387
388 void Node::createAsDirectoryInternal(const IPathPtr& path)
389 {
390     if (mkdir(path->getFullPath().c_str(), S_IRWXU | S_IRWXG | S_IROTH |
391               S_IXOTH) != 0) {
392         ThrowMsg(Commons::PlatformException,
393                  "Platform node could not be created.");
394     }
395 }
396
397 void Node::removeAsFile(const IPathPtr& path)
398 {
399     DPL::Mutex::ScopedLock lock(&m_openStreamsMutex);
400     if (!m_openStreams.empty()) {
401         ThrowMsg(Commons::PlatformException, "Node is locked for I/O.");
402     }
403     if (IManager::getInstance().checkIfOpened(path)) {
404         ThrowMsg(Commons::PlatformException, "Node is locked for I/O.");
405     }
406
407     if (unlink(path->getFullPath().c_str()) != 0) {
408         ThrowMsg(Commons::PlatformException,
409                  "Error while removing platform node.");
410     }
411 }
412
413 void Node::removeAsDirectory(const IPathPtr& path,
414                              bool recursive)
415 {
416     if (recursive) {
417         DIR* dir = opendir(path->getFullPath().c_str());
418         if (!dir) {
419             LogError("File: " << path->getFullPath().c_str());
420             ThrowMsg(Commons::PlatformException,
421                      "Node does not exist or access denied.");
422         }
423         errno = 0;
424         struct dirent *entry = NULL;
425         while ((entry = readdir(dir))) {
426             if (!strncmp(entry->d_name, ".",
427                          1) || !strncmp(entry->d_name, "..", 2)) {
428                 continue;
429             }
430             IPathPtr subPath = *path + entry->d_name;
431             struct stat info;
432             memset(&info, 0, sizeof(struct stat));
433             if (lstat(subPath->getFullPath().c_str(), &info) == 0) {
434                 Try {
435                     if (S_ISDIR(info.st_mode)) {
436                         removeAsDirectory(subPath, true);
437                     } else if (S_ISREG(info.st_mode)) {
438                         removeAsFile(subPath);
439                     }
440                 }
441                 Catch(Commons::PlatformException) {
442                 }
443                 // TODO: Not sure if above exception should be swallowed.
444             }
445         }
446         closedir(dir);
447     }
448
449     errno = 0;
450     if (rmdir(path->getFullPath().c_str()) != 0) {
451         if (errno == EEXIST) {
452             ThrowMsg(Commons::PlatformException, "Node has child nodes.");
453         }
454         ThrowMsg(Commons::PlatformException,
455                  "Error while removing platform node.");
456     }
457 }
458
459 void Node::OnRequestReceived(const EventListNodesPtr& event)
460 {
461     try {
462         NodeList list = event->getNode()->getChildNodes(event->getFilter());
463         event->setResult(list);
464     }
465     catch (const Commons::PlatformException& ex) {
466         LogError("Exception: " << ex.GetMessage());
467         event->setExceptionCode(Commons::ExceptionCodes::PlatformException);
468     }
469     catch (const Commons::SecurityException& ex) {
470         LogError("Exception: " << ex.GetMessage());
471         event->setExceptionCode(Commons::ExceptionCodes::SecurityException);
472     }
473 }
474
475 void Node::OnRequestReceived(const EventOpenPtr& event)
476 {
477     if (!event->checkCancelled()) {
478         try {
479             IStreamPtr result = open(event->getMode());
480             event->setResult(result);
481         }
482         catch (const Commons::PlatformException& ex) {
483             LogError("Exception: " << ex.GetMessage());
484             event->setExceptionCode(Commons::ExceptionCodes::PlatformException);
485         }
486         catch (const Commons::SecurityException& ex) {
487             LogError("Exception: " << ex.GetMessage());
488             event->setExceptionCode(Commons::ExceptionCodes::SecurityException);
489         }
490         //event can be cancelled before executing this code.
491         //when it comes here we doesn't allow it anymore
492         event->setCancelAllowed(false);
493     } else {
494         event->setCancelAllowed(true);
495     }
496 }
497
498 void Node::OnRequestReceived(const EventReadTextPtr& event)
499 {
500     Try {
501         event->setResult(readText());
502         LogDebug("LEAVIN GRACEFULLY");
503     }
504     Catch(Commons::PlatformException) {
505         event->setExceptionCode(Commons::ExceptionCodes::PlatformException);
506     }
507     Catch(Commons::SecurityException) {
508         event->setExceptionCode(Commons::ExceptionCodes::SecurityException);
509     }
510     //this function doesn't change state of the platform,
511     //so we can allow to cancel it and discard results.
512     event->setCancelAllowed(true);
513 }
514
515 std::string Node::readText()
516 {
517     if (m_type != NT_FILE) {
518         ThrowMsg(Commons::PlatformException, "Node is not a file.");
519     }
520
521     if ((m_perms & PERM_READ) == 0) {
522         ThrowMsg(Commons::SecurityException, "No permission.");
523     }
524
525     std::stringstream result;
526     DPL::SharedPtr<Stream> stream(new Stream(SharedFromThis(), AM_READ));
527     while (!stream->isEof()) {
528         result << stream->getLine();
529         if(!stream->isEof())
530             result << '\n';
531     }
532     stream->close();
533     return result.str();
534 }
535
536 std::string Node::toUri(int /*widgetId*/) const
537 {
538     // TODO I believe moving this feature to WrtWrapper would make more sense.
539     return "file://" + m_path->getFullPath();
540 }
541
542 } // Filesystem
543 } // WrtDeviceApis