1// Copyright 2014 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/jsfs/js_fs.h"
6
7#include <assert.h>
8#include <errno.h>
9#include <fcntl.h>
10#include <limits.h>
11#include <string.h>
12
13#include "nacl_io/ioctl.h"
14#include "nacl_io/jsfs/js_fs_node.h"
15#include "nacl_io/kernel_handle.h"
16#include "nacl_io/log.h"
17#include "nacl_io/osdirent.h"
18#include "nacl_io/pepper_interface.h"
19#include "sdk_util/macros.h"
20
21namespace nacl_io {
22
23JsFs::JsFs()
24    : messaging_iface_(NULL),
25      array_iface_(NULL),
26      buffer_iface_(NULL),
27      dict_iface_(NULL),
28      var_iface_(NULL),
29      request_id_(0) {
30}
31
32Error JsFs::Init(const FsInitArgs& args) {
33  Error error = Filesystem::Init(args);
34  if (error)
35    return error;
36
37  pthread_cond_init(&response_cond_, NULL);
38
39  messaging_iface_ = ppapi_->GetMessagingInterface();
40  array_iface_ = ppapi_->GetVarArrayInterface();
41  buffer_iface_ = ppapi_->GetVarArrayBufferInterface();
42  dict_iface_ = ppapi_->GetVarDictionaryInterface();
43  var_iface_ = ppapi_->GetVarInterface();
44
45  if (!messaging_iface_ || !array_iface_ || !buffer_iface_ || !dict_iface_ ||
46      !var_iface_) {
47    LOG_ERROR("Got 1+ NULL interface(s): %s%s%s%s%s",
48              messaging_iface_ ? "" : "Messaging ",
49              array_iface_ ? "" : "VarArray ",
50              buffer_iface_ ? "" : "VarArrayBuffer ",
51              dict_iface_ ? "" : "VarDictionary ",
52              var_iface_ ? "" : "Var ");
53    return ENOSYS;
54  }
55
56  return 0;
57}
58
59void JsFs::Destroy() {
60  pthread_cond_destroy(&response_cond_);
61}
62
63bool JsFs::SetDictVar(PP_Var dict, const char* key, PP_Var value) {
64  PP_Var key_var = var_iface_->VarFromUtf8(key, strlen(key));
65  ScopedVar scoped_key(ppapi_, key_var);
66  if (key_var.type != PP_VARTYPE_STRING) {
67    LOG_ERROR("Unable to create string key \"%s\".", key);
68    return false;
69  }
70
71  PP_Bool success = dict_iface_->Set(dict, key_var, value);
72  if (!success) {
73    LOG_ERROR("Unable to set \"%s\" key of dictionary.", key);
74    return false;
75  }
76
77  return true;
78}
79
80PP_Var JsFs::GetDictVar(PP_Var dict, const char* key) {
81  PP_Var key_var = var_iface_->VarFromUtf8(key, strlen(key));
82  ScopedVar scoped_key(ppapi_, key_var);
83  if (key_var.type != PP_VARTYPE_STRING) {
84    LOG_ERROR("Unable to create string key \"%s\".", key);
85    return PP_MakeUndefined();
86  }
87
88  return dict_iface_->Get(dict, key_var);
89}
90
91bool JsFs::GetVarInt32(PP_Var var, int32_t* out_value) {
92  switch (var.type) {
93    case PP_VARTYPE_INT32:
94      *out_value = var.value.as_int;
95      return true;
96
97    case PP_VARTYPE_DOUBLE:
98      *out_value = static_cast<int32_t>(var.value.as_double);
99      return true;
100
101    default:
102      return false;
103  }
104}
105
106bool JsFs::GetVarUint32(PP_Var var, uint32_t* out_value) {
107  switch (var.type) {
108    case PP_VARTYPE_INT32:
109      *out_value = static_cast<uint32_t>(var.value.as_int);
110      return true;
111
112    case PP_VARTYPE_DOUBLE:
113      *out_value = static_cast<uint32_t>(var.value.as_double);
114      return true;
115
116    default:
117      return false;
118  }
119}
120
121bool JsFs::GetVarInt64(PP_Var var, int64_t* out_value) {
122  switch (var.type) {
123    case PP_VARTYPE_INT32:
124      *out_value = var.value.as_int;
125      return true;
126
127    case PP_VARTYPE_DOUBLE:
128      *out_value = static_cast<int64_t>(var.value.as_double);
129      return true;
130
131    case PP_VARTYPE_ARRAY: {
132      uint32_t len = array_iface_->GetLength(var);
133      if (len != 2) {
134        LOG_ERROR("Expected int64 array type to have 2 elements, not %d", len);
135        return false;
136      }
137
138      PP_Var high_int_var = array_iface_->Get(var, 0);
139      ScopedVar scoped_high_int_var(ppapi_, high_int_var);
140      uint32_t high_int;
141      if (!GetVarUint32(high_int_var, &high_int))
142        return false;
143
144      PP_Var low_int_var = array_iface_->Get(var, 1);
145      ScopedVar scoped_low_int_var(ppapi_, low_int_var);
146      uint32_t low_int;
147      if (!GetVarUint32(low_int_var, &low_int))
148        return false;
149
150      *out_value = static_cast<int64_t>(
151          (static_cast<uint64_t>(high_int) << 32) | low_int);
152      return true;
153    }
154
155    default:
156      return false;
157  }
158}
159
160PP_Var JsFs::VMakeRequest(RequestId request_id,
161                          const char* format,
162                          va_list args) {
163  PP_Var dict = dict_iface_->Create();
164  ScopedVar scoped_dict(ppapi_, dict);
165
166  if (!SetDictVar(dict, "id", PP_MakeInt32(request_id)))
167    return PP_MakeNull();
168
169  const char* p = format;
170  while (*p) {
171    assert(*p == '%');
172    ++p;
173
174    const char* key = va_arg(args, const char*);
175    PP_Var value_var = PP_MakeUndefined();
176
177    switch(*p) {
178      case 'd':
179        value_var = PP_MakeInt32(va_arg(args, int32_t));
180        break;
181      case 'u':
182        value_var = PP_MakeInt32(va_arg(args, uint32_t));
183        break;
184      case 's': {
185        const char* value = va_arg(args, const char*);
186        value_var = var_iface_->VarFromUtf8(value, strlen(value));
187        if (value_var.type != PP_VARTYPE_STRING) {
188          LOG_ERROR("Unable to create \"%s\" string var.", value);
189          return PP_MakeNull();
190        }
191        break;
192      }
193      case 'p':
194        value_var = *va_arg(args, const PP_Var*);
195        var_iface_->AddRef(value_var);
196        break;
197      case 'l': {
198        // Only '%lld' is supported.
199        ++p;
200        assert(*p == 'l');
201        ++p;
202        assert(*p == 'd');
203
204        int64_t value = va_arg(args, int64_t);
205        if (value >= INT_MIN && value <= INT_MAX) {
206          // Send as an int.
207          value_var = PP_MakeInt32(static_cast<int32_t>(value));
208        } else {
209          // Send as an array of two ints: [high int32, low int32].
210          value_var = array_iface_->Create();
211          if (!array_iface_->SetLength(value_var, 2)) {
212            LOG_ERROR("Unable to set length of s64 array.");
213            return PP_MakeNull();
214          }
215
216          if (!array_iface_->Set(value_var, 0, PP_MakeInt32(value >> 32))) {
217            LOG_ERROR("Unable to set of high int32 of s64 array.");
218            return PP_MakeNull();
219          }
220
221          if (!array_iface_->Set(
222                  value_var, 1, PP_MakeInt32(value & 0xffffffff))) {
223            LOG_ERROR("Unable to set of low int32 of s64 array.");
224            return PP_MakeNull();
225          }
226        }
227
228        break;
229      }
230      default:
231        LOG_ERROR("Unknown format specifier %%\"%s\"", p);
232        assert(0);
233        return PP_MakeNull();
234    }
235
236    ++p;
237
238    if (!SetDictVar(dict, key, value_var))
239      return PP_MakeNull();
240
241    // Unconditionally release the value var. It is legal to do this even for
242    // non-refcounted types.
243    var_iface_->Release(value_var);
244  }
245
246  return scoped_dict.Release();
247}
248
249JsFs::RequestId JsFs::VSendRequest(const char* format, va_list args) {
250  AUTO_LOCK(lock_);
251  RequestId id = ++request_id_;
252  // Skip 0 (the invalid request id) in the very unlikely case that the request
253  // id wraps.
254  if (id == 0)
255    id = ++request_id_;
256
257  PP_Var dict_var = VMakeRequest(id, format, args);
258  ScopedVar scoped_dict_var(ppapi_, dict_var);
259  if (dict_var.type != PP_VARTYPE_DICTIONARY)
260    return 0;
261
262  messaging_iface_->PostMessage(ppapi_->GetInstance(), dict_var);
263  return id;
264}
265
266bool JsFs::VSendRequestAndWait(ScopedVar* out_response,
267                               const char* format,
268                               va_list args) {
269  RequestId id = VSendRequest(format, args);
270  if (id == 0)
271    return false;
272
273  out_response->Reset(WaitForResponse(id));
274  return true;
275}
276
277bool JsFs::SendRequestAndWait(ScopedVar* out_response,
278                              const char* format,
279                              ...) {
280  va_list args;
281  va_start(args, format);
282  bool result = VSendRequestAndWait(out_response, format, args);
283  va_end(args);
284  return result;
285}
286
287Error JsFs::ErrorFromResponse(const ScopedVar& response) {
288  int32_t error;
289  if (ScanVar(response.pp_var(), "%d", "error", &error) != 1) {
290    LOG_ERROR("Expected \"error\" field in response.");
291    return EINVAL;
292  }
293
294  return error;
295}
296
297int JsFs::ScanVar(PP_Var var, const char* format, ...) {
298  va_list args;
299  va_start(args, format);
300  int result = VScanVar(var, format, args);
301  va_end(args);
302  return result;
303}
304
305int JsFs::VScanVar(PP_Var dict_var, const char* format, va_list args) {
306  if (dict_var.type != PP_VARTYPE_DICTIONARY) {
307    LOG_ERROR("Expected var of type dictionary, not %d.", dict_var.type);
308    return 0;
309  }
310
311  int num_values = 0;
312
313  const char* p = format;
314  while (*p) {
315    assert(*p == '%');
316    ++p;
317
318    const char* key = va_arg(args, const char*);
319    PP_Var value_var = GetDictVar(dict_var, key);
320    ScopedVar scoped_value_var(ppapi_, value_var);
321
322    if (value_var.type == PP_VARTYPE_UNDEFINED)
323      break;
324
325    bool ok = true;
326
327    switch (*p) {
328      case 'd': {
329        int32_t* value = va_arg(args, int32_t*);
330        if (!GetVarInt32(value_var, value)) {
331          LOG_ERROR("Expected int32_t value for key \"%s\"", key);
332          ok = false;
333        }
334        break;
335      }
336      case 'u': {
337        uint32_t* value = va_arg(args, uint32_t*);
338        if (!GetVarUint32(value_var, value)) {
339          LOG_ERROR("Expected uint32_t value for key \"%s\"", key);
340          ok = false;
341        }
342        break;
343      }
344      case 'l': {
345        // Only '%lld' is supported.
346        ++p;
347        assert(*p == 'l');
348        ++p;
349        assert(*p == 'd');
350
351        int64_t* value = va_arg(args, int64_t*);
352        if (!GetVarInt64(value_var, value)) {
353          LOG_ERROR("Expected int64_t value for key \"%s\"", key);
354          ok = false;
355        }
356        break;
357      }
358      case 'p': {
359        PP_Var* value = va_arg(args, PP_Var*);
360        *value = scoped_value_var.Release();
361        break;
362      }
363      default:
364        LOG_ERROR("Unknown format specifier %%\"%s\"", p);
365        assert(0);
366        ok = false;
367        break;
368    }
369
370    if (!ok)
371      break;
372
373    p++;
374    num_values++;
375  }
376
377  return num_values;
378}
379
380PP_Var JsFs::WaitForResponse(RequestId request_id) {
381  AUTO_LOCK(lock_);
382  while (1) {
383    ResponseMap_t::iterator iter = responses_.find(request_id);
384    if (iter != responses_.end()) {
385      PP_Var response = iter->second;
386      responses_.erase(iter);
387      return response;
388    }
389
390    pthread_cond_wait(&response_cond_, lock_.mutex());
391  }
392}
393
394Error JsFs::OpenWithMode(const Path& path, int open_flags, mode_t t,
395                         ScopedNode* out_node) {
396  out_node->reset(NULL);
397  ScopedVar response(ppapi_);
398  if (!SendRequestAndWait(&response, "%s%s%d",
399                          "cmd", "open",
400                          "path", path.Join().c_str(),
401                          "oflag", open_flags)) {
402    LOG_ERROR("Failed to send request.");
403    return EINVAL;
404  }
405
406  int32_t error;
407  int32_t fd;
408  int result = ScanVar(response.pp_var(), "%d%d", "error", &error, "fd", &fd);
409  if (result >= 1 && error)
410    return error;
411
412  if (result != 2) {
413    LOG_ERROR("Expected \"error\" and \"fd\" fields in response.");
414    return EINVAL;
415  }
416
417  out_node->reset(new JsFsNode(this, fd));
418  return 0;
419}
420
421Error JsFs::Unlink(const Path& path) {
422  ScopedVar response(ppapi_);
423  if (!SendRequestAndWait(
424          &response, "%s%s", "cmd", "unlink", "path", path.Join().c_str())) {
425    LOG_ERROR("Failed to send request.");
426    return EINVAL;
427  }
428
429  return ErrorFromResponse(response);
430}
431
432Error JsFs::Mkdir(const Path& path, int perm) {
433  ScopedVar response(ppapi_);
434  if (!SendRequestAndWait(&response, "%s%s%d",
435                          "cmd", "mkdir",
436                          "path", path.Join().c_str(),
437                          "mode", perm)) {
438    LOG_ERROR("Failed to send request.");
439    return EINVAL;
440  }
441
442  return ErrorFromResponse(response);
443}
444
445Error JsFs::Rmdir(const Path& path) {
446  ScopedVar response(ppapi_);
447  if (!SendRequestAndWait(
448          &response, "%s%s", "cmd", "rmdir", "path", path.Join().c_str())) {
449    LOG_ERROR("Failed to send request.");
450    return EINVAL;
451  }
452
453  return ErrorFromResponse(response);
454}
455
456Error JsFs::Remove(const Path& path) {
457  ScopedVar response(ppapi_);
458  if (!SendRequestAndWait(
459          &response, "%s%s", "cmd", "remove", "path", path.Join().c_str())) {
460    LOG_ERROR("Failed to send request.");
461    return EINVAL;
462  }
463
464  return ErrorFromResponse(response);
465}
466
467Error JsFs::Rename(const Path& path, const Path& newpath) {
468  ScopedVar response(ppapi_);
469  if (!SendRequestAndWait(&response, "%s%s%s",
470                          "cmd", "rename",
471                          "old", path.Join().c_str(),
472                          "new", newpath.Join().c_str())) {
473    LOG_ERROR("Failed to send request.");
474    return EINVAL;
475  }
476
477  return ErrorFromResponse(response);
478}
479
480Error JsFs::Filesystem_VIoctl(int request, va_list args) {
481  if (request != NACL_IOC_HANDLEMESSAGE) {
482    LOG_ERROR("Unknown ioctl: %#x", request);
483    return EINVAL;
484  }
485
486  PP_Var response = *va_arg(args, PP_Var*);
487
488  AUTO_LOCK(lock_);
489
490  RequestId response_id;
491  if (ScanVar(response, "%d", "id", &response_id) != 1) {
492    LOG_TRACE("ioctl with no \"id\", ignoring.\n");
493    return EINVAL;
494  }
495
496  responses_.insert(ResponseMap_t::value_type(response_id, response));
497  pthread_cond_broadcast(&response_cond_);
498  return 0;
499}
500
501}  // namespace nacl_io
502