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