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