summaryrefslogtreecommitdiffstats
path: root/xpcom/build/NSPRInterposer.cpp
blob: 184c3793e746a58f6f6aa1b9b4d761e1b8e26ed5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "IOInterposer.h"
#include "NSPRInterposer.h"

#include "prio.h"
#include "private/pprio.h"
#include "nsDebug.h"
#include "nscore.h"

#include <sys/param.h>
#ifdef XP_MACOSX
#  include <fcntl.h>
#else
#  include "prprf.h"
#  include <unistd.h>
#endif

namespace {

/* Original IO methods */
PRCloseFN sCloseFn = nullptr;
PRReadFN sReadFn = nullptr;
PRWriteFN sWriteFn = nullptr;
PRFsyncFN sFSyncFn = nullptr;
PRFileInfoFN sFileInfoFn = nullptr;
PRFileInfo64FN sFileInfo64Fn = nullptr;

static int32_t GetPathFromFd(int32_t aFd, char* aBuf, size_t aBufSize) {
#ifdef XP_MACOSX
  NS_ASSERTION(aBufSize >= MAXPATHLEN,
               "aBufSize should be a least MAXPATHLEN long");

  return fcntl(aFd, F_GETPATH, aBuf);
#else
  char procPath[32];
  if (PR_snprintf(procPath, sizeof(procPath), "/proc/self/fd/%i", aFd) ==
      (PRUint32)-1) {
    return -1;
  }

  int32_t ret = readlink(procPath, aBuf, aBufSize - 1);
  if (ret > -1) {
    aBuf[ret] = '\0';
  }

  return ret;
#endif
}

/**
 * RAII class for timing the duration of an NSPR I/O call and reporting the
 * result to the mozilla::IOInterposeObserver API.
 */
class NSPRIOAutoObservation : public mozilla::IOInterposeObserver::Observation {
 public:
  explicit NSPRIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp,
                                 PRFileDesc* aFd)
      : mozilla::IOInterposeObserver::Observation(aOp, "NSPRIOInterposer") {
    char filename[MAXPATHLEN];
    if (mShouldReport && aFd &&
        GetPathFromFd(PR_FileDesc2NativeHandle(aFd), filename,
                      sizeof(filename)) != -1) {
      CopyUTF8toUTF16(mozilla::MakeStringSpan(filename), mFilename);
    } else {
      mFilename.Truncate();
    }
  }

  void Filename(nsAString& aFilename) override { aFilename = mFilename; }

  ~NSPRIOAutoObservation() override { Report(); }

 private:
  nsString mFilename;
};

PRStatus PR_CALLBACK interposedClose(PRFileDesc* aFd) {
  // If we don't have a valid original function pointer something is very wrong.
  NS_ASSERTION(sCloseFn, "NSPR IO Interposing: sCloseFn is NULL");

  NSPRIOAutoObservation timer(mozilla::IOInterposeObserver::OpClose, aFd);
  return sCloseFn(aFd);
}

int32_t PR_CALLBACK interposedRead(PRFileDesc* aFd, void* aBuf, int32_t aAmt) {
  // If we don't have a valid original function pointer something is very wrong.
  NS_ASSERTION(sReadFn, "NSPR IO Interposing: sReadFn is NULL");

  NSPRIOAutoObservation timer(mozilla::IOInterposeObserver::OpRead, aFd);
  return sReadFn(aFd, aBuf, aAmt);
}

int32_t PR_CALLBACK interposedWrite(PRFileDesc* aFd, const void* aBuf,
                                    int32_t aAmt) {
  // If we don't have a valid original function pointer something is very wrong.
  NS_ASSERTION(sWriteFn, "NSPR IO Interposing: sWriteFn is NULL");

  NSPRIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFd);
  return sWriteFn(aFd, aBuf, aAmt);
}

PRStatus PR_CALLBACK interposedFSync(PRFileDesc* aFd) {
  // If we don't have a valid original function pointer something is very wrong.
  NS_ASSERTION(sFSyncFn, "NSPR IO Interposing: sFSyncFn is NULL");

  NSPRIOAutoObservation timer(mozilla::IOInterposeObserver::OpFSync, aFd);
  return sFSyncFn(aFd);
}

PRStatus PR_CALLBACK interposedFileInfo(PRFileDesc* aFd, PRFileInfo* aInfo) {
  // If we don't have a valid original function pointer something is very wrong.
  NS_ASSERTION(sFileInfoFn, "NSPR IO Interposing: sFileInfoFn is NULL");

  NSPRIOAutoObservation timer(mozilla::IOInterposeObserver::OpStat, aFd);
  return sFileInfoFn(aFd, aInfo);
}

PRStatus PR_CALLBACK interposedFileInfo64(PRFileDesc* aFd,
                                          PRFileInfo64* aInfo) {
  // If we don't have a valid original function pointer something is very wrong.
  NS_ASSERTION(sFileInfo64Fn, "NSPR IO Interposing: sFileInfo64Fn is NULL");

  NSPRIOAutoObservation timer(mozilla::IOInterposeObserver::OpStat, aFd);
  return sFileInfo64Fn(aFd, aInfo);
}

}  // namespace

namespace mozilla {

void InitNSPRIOInterposing() {
  // Check that we have not interposed any of the IO methods before
  MOZ_ASSERT(!sCloseFn && !sReadFn && !sWriteFn && !sFSyncFn && !sFileInfoFn &&
             !sFileInfo64Fn);

  // We can't actually use this assertion because we initialize this code
  // before XPCOM is initialized, so NS_IsMainThread() always returns false.
  // MOZ_ASSERT(NS_IsMainThread());

  // Get IO methods from NSPR and const cast the structure so we can modify it.
  PRIOMethods* methods = const_cast<PRIOMethods*>(PR_GetFileMethods());

  // Something is badly wrong if we don't get IO methods... However, we don't
  // want to crash over that in non-debug builds. This is unlikely to happen
  // so an assert is enough, no need to report it to the caller.
  MOZ_ASSERT(methods);
  if (!methods) {
    return;
  }

  // Store original functions
  sCloseFn = methods->close;
  sReadFn = methods->read;
  sWriteFn = methods->write;
  sFSyncFn = methods->fsync;
  sFileInfoFn = methods->fileInfo;
  sFileInfo64Fn = methods->fileInfo64;

  // Overwrite with our interposed functions
  methods->close = &interposedClose;
  methods->read = &interposedRead;
  methods->write = &interposedWrite;
  methods->fsync = &interposedFSync;
  methods->fileInfo = &interposedFileInfo;
  methods->fileInfo64 = &interposedFileInfo64;
}

void ClearNSPRIOInterposing() {
  // If we have already cleared IO interposing, or not initialized it this is
  // actually bad.
  MOZ_ASSERT(sCloseFn && sReadFn && sWriteFn && sFSyncFn && sFileInfoFn &&
             sFileInfo64Fn);

  // Get IO methods from NSPR and const cast the structure so we can modify it.
  PRIOMethods* methods = const_cast<PRIOMethods*>(PR_GetFileMethods());

  // Something is badly wrong if we don't get IO methods... However, we don't
  // want to crash over that in non-debug builds. This is unlikely to happen
  // so an assert is enough, no need to report it to the caller.
  MOZ_ASSERT(methods);
  if (!methods) {
    return;
  }

  // Restore original functions
  methods->close = sCloseFn;
  methods->read = sReadFn;
  methods->write = sWriteFn;
  methods->fsync = sFSyncFn;
  methods->fileInfo = sFileInfoFn;
  methods->fileInfo64 = sFileInfo64Fn;

  // Forget about original functions
  sCloseFn = nullptr;
  sReadFn = nullptr;
  sWriteFn = nullptr;
  sFSyncFn = nullptr;
  sFileInfoFn = nullptr;
  sFileInfo64Fn = nullptr;
}

}  // namespace mozilla