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