| 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 |  |