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