1 //============================================================================
2 //
3 //   SSSS    tt          lll  lll
4 //  SS  SS   tt           ll   ll
5 //  SS     tttttt  eeee   ll   ll   aaaa
6 //   SSSS    tt   ee  ee  ll   ll      aa
7 //      SS   tt   eeeeee  ll   ll   aaaaa  --  "An Atari 2600 VCS Emulator"
8 //  SS  SS   tt   ee      ll   ll  aa  aa
9 //   SSSS     ttt  eeeee llll llll  aaaaa
10 //
11 // Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony
12 // and the Stella Team
13 //
14 // See the file "License.txt" for information on usage and redistribution of
15 // this file, and for a DISCLAIMER OF ALL WARRANTIES.
16 //============================================================================
17 
18 #if defined(RETRON77)
19   #define ROOT_DIR "/mnt/games/"
20 #else
21   #define ROOT_DIR "/"
22 #endif
23 
24 #include "FSNodePOSIX.hxx"
25 
26 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FilesystemNodePOSIX()27 FilesystemNodePOSIX::FilesystemNodePOSIX()
28   : _path{ROOT_DIR},
29     _displayName{_path}
30 {
31 }
32 
33 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FilesystemNodePOSIX(const string & path,bool verify)34 FilesystemNodePOSIX::FilesystemNodePOSIX(const string& path, bool verify)
35   : _path{path.length() > 0 ? path : "~"}  // Default to home directory
36 {
37   // Expand '~' to the HOME environment variable
38   if(_path[0] == '~')
39   {
40     string home = BSPF::getenv("HOME");
41     if(home != EmptyString)
42       _path.replace(0, 1, home);
43   }
44   // Get absolute path (only used for relative directories)
45   else if(_path[0] == '.')
46   {
47     std::array<char, MAXPATHLEN> buf;
48     if(realpath(_path.c_str(), buf.data()))
49       _path = buf.data();
50   }
51 
52   _displayName = lastPathComponent(_path);
53 
54   if(verify)
55     setFlags();
56 }
57 
58 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setFlags()59 void FilesystemNodePOSIX::setFlags()
60 {
61   struct stat st;
62 
63   _isValid = (0 == stat(_path.c_str(), &st));
64   if(_isValid)
65   {
66     _isDirectory = S_ISDIR(st.st_mode);
67     _isFile = S_ISREG(st.st_mode);
68 
69     // Add a trailing slash, if necessary
70     if (_isDirectory && _path.length() > 0 && _path[_path.length()-1] != '/')
71       _path += '/';
72   }
73   else
74     _isDirectory = _isFile = false;
75 }
76 
77 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
getShortPath() const78 string FilesystemNodePOSIX::getShortPath() const
79 {
80   // If the path starts with the home directory, replace it with '~'
81   string home = BSPF::getenv("HOME");
82   if(home != EmptyString && BSPF::startsWithIgnoreCase(_path, home))
83   {
84     string path = "~";
85     const char* offset = _path.c_str() + home.size();
86     if(*offset != '/') path += "/";
87     path += offset;
88     return path;
89   }
90   return _path;
91 }
92 
93 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
hasParent() const94 bool FilesystemNodePOSIX::hasParent() const
95 {
96   return _path != "" && _path != ROOT_DIR;
97 }
98 
99 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
getChildren(AbstractFSList & myList,ListMode mode) const100 bool FilesystemNodePOSIX::getChildren(AbstractFSList& myList, ListMode mode) const
101 {
102   assert(_isDirectory);
103 
104   DIR* dirp = opendir(_path.c_str());
105   if (dirp == nullptr)
106     return false;
107 
108   // Loop over dir entries using readdir
109   struct dirent* dp;
110   while ((dp = readdir(dirp)) != nullptr)
111   {
112     // Ignore all hidden files
113     if (dp->d_name[0] == '.')
114       continue;
115 
116     string newPath(_path);
117     if (newPath.length() > 0 && newPath[newPath.length()-1] != '/')
118       newPath += '/';
119     newPath += dp->d_name;
120 
121     FilesystemNodePOSIX entry(newPath, false);
122 
123 #if defined(SYSTEM_NOT_SUPPORTING_D_TYPE)
124     /* TODO: d_type is not part of POSIX, so it might not be supported
125      * on some of our targets. For those systems where it isn't supported,
126      * add this #elif case, which tries to use stat() instead.
127      *
128      * The d_type method is used to avoid costly recurrent stat() calls in big
129      * directories.
130      */
131     entry.setFlags();
132 #else
133     if (dp->d_type == DT_UNKNOWN)
134     {
135       // Fall back to stat()
136       entry.setFlags();
137     }
138     else
139     {
140       if (dp->d_type == DT_LNK)
141       {
142         struct stat st;
143         if (stat(entry._path.c_str(), &st) == 0)
144         {
145           entry._isDirectory = S_ISDIR(st.st_mode);
146           entry._isFile = S_ISREG(st.st_mode);
147         }
148         else
149           entry._isDirectory = entry._isFile = false;
150       }
151       else
152       {
153         entry._isDirectory = (dp->d_type == DT_DIR);
154         entry._isFile = (dp->d_type == DT_REG);
155       }
156 
157       if (entry._isDirectory)
158         entry._path += "/";
159 
160       entry._isValid = true;
161     }
162 #endif
163 
164     // Skip files that are invalid for some reason (e.g. because we couldn't
165     // properly stat them).
166     if (!entry._isValid)
167       continue;
168 
169     // Honor the chosen mode
170     if ((mode == FilesystemNode::ListMode::FilesOnly && !entry._isFile) ||
171         (mode == FilesystemNode::ListMode::DirectoriesOnly && !entry._isDirectory))
172       continue;
173 
174     myList.emplace_back(make_shared<FilesystemNodePOSIX>(entry));
175   }
176   closedir(dirp);
177 
178   return true;
179 }
180 
181 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
makeDir()182 bool FilesystemNodePOSIX::makeDir()
183 {
184   if(mkdir(_path.c_str(), 0777) == 0)
185   {
186     // Get absolute path
187     std::array<char, MAXPATHLEN> buf;
188     if(realpath(_path.c_str(), buf.data()))
189       _path = buf.data();
190 
191     _displayName = lastPathComponent(_path);
192     setFlags();
193 
194     // Add a trailing slash, if necessary
195     if (_path.length() > 0 && _path[_path.length()-1] != '/')
196       _path += '/';
197 
198     return true;
199   }
200   else
201     return false;
202 }
203 
204 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
rename(const string & newfile)205 bool FilesystemNodePOSIX::rename(const string& newfile)
206 {
207   if(std::rename(_path.c_str(), newfile.c_str()) == 0)
208   {
209     _path = newfile;
210 
211     // Get absolute path
212     std::array<char, MAXPATHLEN> buf;
213     if(realpath(_path.c_str(), buf.data()))
214       _path = buf.data();
215 
216     _displayName = lastPathComponent(_path);
217     setFlags();
218 
219     // Add a trailing slash, if necessary
220     if (_isDirectory && _path.length() > 0 && _path[_path.length()-1] != '/')
221       _path += '/';
222 
223     return true;
224   }
225   else
226     return false;
227 }
228 
229 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
getParent() const230 AbstractFSNodePtr FilesystemNodePOSIX::getParent() const
231 {
232   if (_path == ROOT_DIR)
233     return nullptr;
234 
235   const char* start = _path.c_str();
236   const char* end = lastPathComponent(_path);
237 
238   return make_unique<FilesystemNodePOSIX>(string(start, size_t(end - start)));
239 }
240