1// Copyright 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "nacl_io/html5fs/html5_fs.h" 6 7#include <errno.h> 8#include <fcntl.h> 9#include <stdlib.h> 10#include <string.h> 11 12#include <algorithm> 13 14#include <ppapi/c/pp_completion_callback.h> 15#include <ppapi/c/pp_errors.h> 16 17#include "nacl_io/html5fs/html5_fs_node.h" 18#include "sdk_util/auto_lock.h" 19 20namespace nacl_io { 21 22namespace { 23 24#if defined(WIN32) 25int64_t strtoull(const char* nptr, char** endptr, int base) { 26 return _strtoui64(nptr, endptr, base); 27} 28#endif 29 30} // namespace 31 32// Continuing DJB2a hash 33ino_t Html5Fs::HashPathSegment(ino_t hash, const char *str, size_t len) { 34 // First add the path seperator 35 hash = (hash * static_cast<ino_t>(33)) ^ '/'; 36 while (len--) { 37 hash = (hash * static_cast<ino_t>(33)) ^ *str++; 38 } 39 return hash; 40} 41 42ino_t Html5Fs::HashPath(const Path& path) { 43 // Prime the DJB2a hash 44 ino_t hash = 5381; 45 46 // Apply a running DJB2a to each part of the path 47 for (size_t segment = 0; segment < path.Size(); segment++) { 48 const char *ptr = path.Part(segment).c_str(); 49 size_t len = path.Part(segment).length(); 50 hash = HashPathSegment(hash, ptr, len); 51 } 52 return hash; 53} 54 55 56// For HTML5, the INO should be the one used by the system, however PPAPI 57// does not provide access to the real INO. Instead, since HTML5 does not 58// suport links, we assume that files are unique based on path to the base 59// of the mount. 60void Html5Fs::OnNodeCreated(Node* node) { 61 node->stat_.st_dev = dev_; 62} 63 64void Html5Fs::OnNodeDestroyed(Node* node) {} 65 66 67Error Html5Fs::OpenWithMode(const Path& path, int open_flags, mode_t mode, 68 ScopedNode* out_node) { 69 out_node->reset(NULL); 70 Error error = BlockUntilFilesystemOpen(); 71 if (error) 72 return error; 73 74 PP_Resource fileref = file_ref_iface_->Create( 75 filesystem_resource_, GetFullPath(path).Join().c_str()); 76 if (!fileref) 77 return ENOENT; 78 79 ScopedNode node(new Html5FsNode(this, fileref)); 80 error = node->Init(open_flags); 81 82 // Set the INO based on the path 83 node->stat_.st_ino = HashPath(path); 84 85 if (error) 86 return error; 87 88 *out_node = node; 89 return 0; 90} 91 92Path Html5Fs::GetFullPath(const Path& path) { 93 Path full_path(path); 94 full_path.Prepend(prefix_); 95 return full_path; 96} 97 98Error Html5Fs::Unlink(const Path& path) { 99 return RemoveInternal(path, REMOVE_FILE); 100} 101 102Error Html5Fs::Mkdir(const Path& path, int permissions) { 103 Error error = BlockUntilFilesystemOpen(); 104 if (error) 105 return error; 106 107 // FileRef returns PP_ERROR_NOACCESS which is translated to EACCES if you 108 // try to create the root directory. EEXIST is a better errno here. 109 if (path.IsRoot()) 110 return EEXIST; 111 112 ScopedResource fileref_resource( 113 ppapi(), 114 file_ref_iface_->Create(filesystem_resource_, 115 GetFullPath(path).Join().c_str())); 116 if (!fileref_resource.pp_resource()) 117 return ENOENT; 118 119 int32_t result = file_ref_iface_->MakeDirectory( 120 fileref_resource.pp_resource(), PP_FALSE, PP_BlockUntilComplete()); 121 if (result != PP_OK) 122 return PPErrorToErrno(result); 123 124 return 0; 125} 126 127Error Html5Fs::Rmdir(const Path& path) { 128 return RemoveInternal(path, REMOVE_DIR); 129} 130 131Error Html5Fs::Remove(const Path& path) { 132 return RemoveInternal(path, REMOVE_ALL); 133} 134 135Error Html5Fs::RemoveInternal(const Path& path, int remove_type) { 136 Error error = BlockUntilFilesystemOpen(); 137 if (error) 138 return error; 139 140 ScopedResource fileref_resource( 141 ppapi(), 142 file_ref_iface_->Create(filesystem_resource_, 143 GetFullPath(path).Join().c_str())); 144 if (!fileref_resource.pp_resource()) 145 return ENOENT; 146 147 // Check file type 148 if (remove_type != REMOVE_ALL) { 149 PP_FileInfo file_info; 150 int32_t query_result = file_ref_iface_->Query( 151 fileref_resource.pp_resource(), &file_info, PP_BlockUntilComplete()); 152 if (query_result != PP_OK) { 153 LOG_ERROR("Error querying file type"); 154 return EINVAL; 155 } 156 switch (file_info.type) { 157 case PP_FILETYPE_DIRECTORY: 158 if (!(remove_type & REMOVE_DIR)) 159 return EISDIR; 160 break; 161 case PP_FILETYPE_REGULAR: 162 if (!(remove_type & REMOVE_FILE)) 163 return ENOTDIR; 164 break; 165 default: 166 LOG_ERROR("Invalid file type: %d", file_info.type); 167 return EINVAL; 168 } 169 } 170 171 int32_t result = file_ref_iface_->Delete(fileref_resource.pp_resource(), 172 PP_BlockUntilComplete()); 173 if (result != PP_OK) 174 return PPErrorToErrno(result); 175 176 return 0; 177} 178 179Error Html5Fs::Rename(const Path& path, const Path& newpath) { 180 Error error = BlockUntilFilesystemOpen(); 181 if (error) 182 return error; 183 184 std::string oldpath_full = GetFullPath(path).Join(); 185 ScopedResource fileref_resource( 186 ppapi(), 187 file_ref_iface_->Create(filesystem_resource_, oldpath_full.c_str())); 188 if (!fileref_resource.pp_resource()) 189 return ENOENT; 190 191 std::string newpath_full = GetFullPath(newpath).Join(); 192 ScopedResource new_fileref_resource( 193 ppapi(), 194 file_ref_iface_->Create(filesystem_resource_, newpath_full.c_str())); 195 if (!new_fileref_resource.pp_resource()) 196 return ENOENT; 197 198 int32_t result = file_ref_iface_->Rename(fileref_resource.pp_resource(), 199 new_fileref_resource.pp_resource(), 200 PP_BlockUntilComplete()); 201 if (result != PP_OK) 202 return PPErrorToErrno(result); 203 204 return 0; 205} 206 207Html5Fs::Html5Fs() 208 : filesystem_iface_(NULL), 209 file_ref_iface_(NULL), 210 file_io_iface_(NULL), 211 filesystem_resource_(0), 212 filesystem_open_has_result_(false), 213 filesystem_open_error_(0) { 214} 215 216Error Html5Fs::Init(const FsInitArgs& args) { 217 pthread_cond_init(&filesystem_open_cond_, NULL); 218 219 Error error = Filesystem::Init(args); 220 if (error) 221 return error; 222 223 if (!args.ppapi) { 224 LOG_ERROR("ppapi is NULL."); 225 return ENOSYS; 226 } 227 228 core_iface_ = ppapi()->GetCoreInterface(); 229 filesystem_iface_ = ppapi()->GetFileSystemInterface(); 230 file_io_iface_ = ppapi()->GetFileIoInterface(); 231 file_ref_iface_ = ppapi()->GetFileRefInterface(); 232 233 if (!(core_iface_ && filesystem_iface_ && file_io_iface_ && 234 file_ref_iface_)) { 235 LOG_ERROR("Got NULL interface(s): %s%s%s%s", 236 core_iface_ ? "" : "Core ", 237 filesystem_iface_ ? "" : "FileSystem ", 238 file_ref_iface_ ? "" : "FileRef", 239 file_io_iface_ ? "" : "FileIo "); 240 return ENOSYS; 241 } 242 243 // Parse filesystem args. 244 PP_FileSystemType filesystem_type = PP_FILESYSTEMTYPE_LOCALPERSISTENT; 245 int64_t expected_size = 0; 246 for (StringMap_t::const_iterator iter = args.string_map.begin(); 247 iter != args.string_map.end(); 248 ++iter) { 249 if (iter->first == "type") { 250 if (iter->second == "PERSISTENT") { 251 filesystem_type = PP_FILESYSTEMTYPE_LOCALPERSISTENT; 252 } else if (iter->second == "TEMPORARY") { 253 filesystem_type = PP_FILESYSTEMTYPE_LOCALTEMPORARY; 254 } else if (iter->second == "") { 255 filesystem_type = PP_FILESYSTEMTYPE_LOCALPERSISTENT; 256 } else { 257 LOG_ERROR("Unknown filesystem type: '%s'", iter->second.c_str()); 258 return EINVAL; 259 } 260 } else if (iter->first == "expected_size") { 261 expected_size = strtoull(iter->second.c_str(), NULL, 10); 262 } else if (iter->first == "filesystem_resource") { 263 PP_Resource resource = strtoull(iter->second.c_str(), NULL, 10); 264 if (!filesystem_iface_->IsFileSystem(resource)) 265 return EINVAL; 266 267 filesystem_resource_ = resource; 268 ppapi_->AddRefResource(filesystem_resource_); 269 } else if (iter->first == "SOURCE") { 270 prefix_ = iter->second; 271 } else { 272 LOG_ERROR("Invalid mount param: %s", iter->first.c_str()); 273 return EINVAL; 274 } 275 } 276 277 if (filesystem_resource_ != 0) { 278 filesystem_open_has_result_ = true; 279 filesystem_open_error_ = PP_OK; 280 return 0; 281 } 282 283 // Initialize filesystem. 284 filesystem_resource_ = 285 filesystem_iface_->Create(ppapi_->GetInstance(), filesystem_type); 286 if (filesystem_resource_ == 0) 287 return ENOSYS; 288 289 // We can't block the main thread, so make an asynchronous call if on main 290 // thread. If we are off-main-thread, then don't make an asynchronous call; 291 // otherwise we require a message loop. 292 bool main_thread = core_iface_->IsMainThread(); 293 PP_CompletionCallback cc = 294 main_thread ? PP_MakeCompletionCallback( 295 &Html5Fs::FilesystemOpenCallbackThunk, this) 296 : PP_BlockUntilComplete(); 297 298 int32_t result = 299 filesystem_iface_->Open(filesystem_resource_, expected_size, cc); 300 301 if (!main_thread) { 302 filesystem_open_has_result_ = true; 303 filesystem_open_error_ = PPErrorToErrno(result); 304 305 return filesystem_open_error_; 306 } 307 308 // We have to assume the call to Open will succeed; there is no better 309 // result to return here. 310 return 0; 311} 312 313void Html5Fs::Destroy() { 314 if (ppapi_ != NULL && filesystem_resource_ != 0) 315 ppapi_->ReleaseResource(filesystem_resource_); 316 pthread_cond_destroy(&filesystem_open_cond_); 317} 318 319Error Html5Fs::BlockUntilFilesystemOpen() { 320 AUTO_LOCK(filesysem_open_lock_); 321 while (!filesystem_open_has_result_) { 322 pthread_cond_wait(&filesystem_open_cond_, filesysem_open_lock_.mutex()); 323 } 324 return filesystem_open_error_; 325} 326 327// static 328void Html5Fs::FilesystemOpenCallbackThunk(void* user_data, int32_t result) { 329 Html5Fs* self = static_cast<Html5Fs*>(user_data); 330 self->FilesystemOpenCallback(result); 331} 332 333void Html5Fs::FilesystemOpenCallback(int32_t result) { 334 AUTO_LOCK(filesysem_open_lock_); 335 filesystem_open_has_result_ = true; 336 filesystem_open_error_ = PPErrorToErrno(result); 337 pthread_cond_signal(&filesystem_open_cond_); 338} 339 340} // namespace nacl_io 341