1 /*
2 * REmbeddedPosix.cpp
3 *
4 * Copyright (C) 2021 by RStudio, PBC
5 *
6 * Unless you have received this program directly from RStudio pursuant
7 * to the terms of a commercial license agreement with RStudio, then
8 * this program is licensed to you under the terms of version 3 of the
9 * GNU Affero General Public License. This program is distributed WITHOUT
10 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
11 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
12 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
13 *
14 */
15
16 #include <Rversion.h>
17
18 #include <r/RExec.hpp>
19
20 #include <shared_core/FilePath.hpp>
21
22 #include <boost/date_time/posix_time/posix_time_duration.hpp>
23
24 // after boost stuff to prevent length (Rf_length) symbol conflict issues
25 #include "REmbedded.hpp"
26 #include <r/RInterface.hpp>
27 #include <r/RErrorCategory.hpp>
28 #include <r/RUtil.hpp>
29
30 #include <R_ext/eventloop.h>
31
32 #include <Rembedded.h>
33
34 #ifdef __APPLE__
35 #include <dlfcn.h>
36 extern "C" void R_ProcessEvents(void);
37 extern "C" void (*ptr_R_ProcessEvents)(void);
38 #define QCF_SET_PEPTR 1 /* set ProcessEvents function pointer */
39 #define QCF_SET_FRONT 2 /* set application mode to front */
40 extern "C" typedef void (*ptr_QuartzCocoa_SetupEventLoop)(int, unsigned long);
41 #endif
42
43 extern int R_running_as_main_program; // from unix/system.c
44
45 using namespace rstudio::core;
46
47 namespace rstudio {
48 namespace r {
49 namespace session {
50
runEmbeddedR(const core::FilePath &,const core::FilePath &,bool quiet,bool loadInitFile,SA_TYPE defaultSaveAction,const Callbacks & callbacks,InternalCallbacks * pInternal)51 void runEmbeddedR(const core::FilePath& /*rHome*/, // ignored on posix
52 const core::FilePath& /*userHome*/, // ignored on posix
53 bool quiet,
54 bool loadInitFile,
55 SA_TYPE defaultSaveAction,
56 const Callbacks& callbacks,
57 InternalCallbacks* pInternal)
58 {
59 // disable R signal handlers. see src/main/main.c for the default
60 // implementations. in our case ignore them for the following reasons:
61 //
62 // INT - no concept of Ctrl-C based interruption (use flag directly)
63 //
64 // SEGV, ILL, & BUS: unsupported due to prompt invoking networking
65 // code (unsupported from within a signal handler)
66 //
67 // USR1 & USR2: same as above SEGV, etc. + we use them for other purposes
68 //
69 // PIPE: we ignore this globally in SessionMain. before doing this we
70 // confirmed that asio wasn't in some way manipulating it -- on linux
71 // boost passes MSG_NOSIGNAL to sendmsg and on OSX sets the SO_NOSIGPIPE
72 // option on all sockets created. note that on other platforms including
73 // solaris, hpux, etc. boost uses detail/signal_init to ignore SIGPIPE
74
75 // globally (this is done in io_service.hpp).
76 R_SignalHandlers = 0;
77
78 // set message callback early so we can see initialization error messages
79 ptr_R_ShowMessage = callbacks.showMessage;
80
81 // running as main program (affects location of R_CStackStart on platforms
82 // without HAVE_LIBC_STACK_END or HAVE_KERN_USRSTACK). see also discussion
83 // on R_CStackStart in 8.1.5 Threading issues
84 R_running_as_main_program = 1;
85
86 // initialize R
87 const char *args[]= {"RStudio", "--interactive"};
88 Rf_initialize_R(sizeof(args)/sizeof(args[0]), (char**)args);
89
90 // For newSession = false we need to do a few things:
91 //
92 // 1) set R_Quiet so we startup without a banner
93 //
94 // 2) set LoadInitFile to supress execution of .Rprofile
95 //
96 // 3) we also need to make sure that .First is not executed. this is
97 // taken care of via the fact that we set RestoreAction to SA_NORESTORE
98 // which means that when setup_Rmainloop there is no .First function
99 // available to it because we haven't restored the environment yet.
100 // Note that .First is executed in the case of new sessions because
101 // it is read from .Rprofile as part of setup_Rmainloop. This implies
102 // that in our version of R the .First function must be defined in
103 // .Rprofile rather than simply saved into the global environment
104 // of the default workspace
105 //
106 structRstart rp;
107 Rstart Rp = &rp;
108 R_DefParams(Rp);
109 #if R_VERSION < R_Version(4, 0, 0)
110 Rp->R_Slave = FALSE;
111 #else
112 Rp->R_NoEcho = FALSE;
113 #endif
114 Rp->R_Quiet = quiet ? TRUE : FALSE;
115 Rp->R_Interactive = TRUE;
116 Rp->SaveAction = defaultSaveAction;
117 Rp->RestoreAction = SA_NORESTORE; // handled within initialize()
118 Rp->LoadInitFile = loadInitFile ? TRUE : FALSE;
119 R_SetParams(Rp);
120
121 // redirect console
122 R_Interactive = TRUE; // should have also been set by call to Rf_initialize_R
123 R_Consolefile = nullptr;
124 R_Outputfile = nullptr;
125 ptr_R_ReadConsole = callbacks.readConsole;
126 ptr_R_WriteConsole = nullptr; // must set this to NULL for Ex to be called
127 ptr_R_WriteConsoleEx = callbacks.writeConsoleEx;
128 ptr_R_EditFile = callbacks.editFile;
129 ptr_R_Busy = callbacks.busy;
130
131 // hook messages (in case Rf_initialize_R overwrites previously set hook)
132 ptr_R_ShowMessage = callbacks.showMessage;
133
134 // hook file handling
135 ptr_R_ChooseFile = callbacks.chooseFile;
136 ptr_R_ShowFiles = callbacks.showFiles;
137
138 // hook history
139 ptr_R_loadhistory = callbacks.loadhistory;
140 ptr_R_savehistory = callbacks.savehistory;
141 ptr_R_addhistory = callbacks.addhistory;
142
143 // hook suicide, but save reference to internal suicide so we can forward
144 pInternal->suicide = ptr_R_Suicide;
145 ptr_R_Suicide = callbacks.suicide;
146
147 // hook clean up, but save reference to internal clean up so can forward
148 pInternal->cleanUp = ptr_R_CleanUp;
149 ptr_R_CleanUp = callbacks.cleanUp;
150
151 // NOTE: we do not hook the following callbacks because they are targeted
152 // at clients that have a stdio-based console
153 // ptr_R_ResetConsole
154 // ptr_R_FlushConsole
155 // ptr_R_ClearerrConsole
156
157 // run main loop (does not return)
158 Rf_mainloop();
159 }
160
completeEmbeddedRInitialization(bool useInternet2)161 Error completeEmbeddedRInitialization(bool useInternet2)
162 {
163 return Success();
164 }
165
166 namespace event_loop {
167
168 namespace {
169
170 // currently installed polled event handler
171 void (*s_polledEventHandler)(void) = nullptr;
172
173 // previously existing polled event handler
174 void (*s_oldPolledEventHandler)(void) = nullptr;
175
176 // function we register with R to implement polled event handler
polledEventHandler()177 void polledEventHandler()
178 {
179 if (s_polledEventHandler != nullptr)
180 s_polledEventHandler();
181
182 if (s_oldPolledEventHandler != nullptr)
183 s_oldPolledEventHandler();
184 }
185
186
187 #ifdef __APPLE__
188
logDLError(const std::string & message,const ErrorLocation & location)189 void logDLError(const std::string& message, const ErrorLocation& location)
190 {
191 std::string errmsg(message);
192 char* dlError = ::dlerror();
193 if (dlError)
194 errmsg += ": " + std::string(dlError);
195 core::log::logErrorMessage(errmsg, location);
196 }
197
198 // Note that when we passed QCF_SET_FRONT to QuartzCocoa_SetupEventLoop
199 // sometimes this resulted in our application having a "bouncing"
200 // state which we couldn't rid ourselves of.
201 //
202 // Note that in researching the way R implements QCF_SET_FRONT I discovered
203 // that a depricated API is called AND an explicit call to SetFront. Another
204 // way to go would be to call the TransformProcessType API:
205 //
206 // http://www.cocoadev.com/index.pl?TransformProcessType
207 // http://developer.apple.com/library/mac/#documentation/Carbon/Reference/Process_Manager/Reference/reference.html%23//apple_ref/c/func/TransformProcessType
208 //
209 // Note this would look something like (cmake and includes for completeness):
210 /*
211 find_library(CARBON_LIBRARY NAMES Carbon)
212 set(LINK_FLAGS ${CARBON_LIBRARY})
213
214 #include <Carbon/Carbon.h>
215 #undef TRUE
216 #undef FALSE
217
218 static const ProcessSerialNumber thePSN = { 0, kCurrentProcess };
219 ::TransformProcessType(&thePSN, kProcessTransformToForegroundApplication);
220 */
221
222 // attempt to setup quartz event loop, if this fails then log and
223 // return false (as a result we'll have to disable the quartz R
224 // function so the user doesn't get in trouble)
setupQuartzEventLoop()225 bool setupQuartzEventLoop()
226 {
227 // first make sure that the gdDevices pacakage is loaded
228 Error error = r::exec::executeString("library(grDevices)");
229 if (error)
230 {
231 LOG_ERROR(error);
232 return false;
233 }
234
235 // get a reference to the grDevices library
236 void* pGrDevices = ::dlopen("grDevices.so",
237 RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
238 if (pGrDevices)
239 {
240 ptr_QuartzCocoa_SetupEventLoop pSetupEventLoop =
241 (ptr_QuartzCocoa_SetupEventLoop)::dlsym(
242 pGrDevices,
243 "QuartzCocoa_SetupEventLoop");
244 if (pSetupEventLoop)
245 {
246 // attempt to setup event loop
247 pSetupEventLoop(QCF_SET_PEPTR, 100);
248
249 // check that we got the ptr_R_ProcessEvents initialized
250 if (ptr_R_ProcessEvents != nullptr)
251 {
252 return true;
253 }
254 else
255 {
256 LOG_ERROR_MESSAGE("ptr_R_ProcessEvents not initialized");
257 return false;
258 }
259 }
260 else
261 {
262 logDLError("Error looking up QuartzCocoa_SetupEventLoop",
263 ERROR_LOCATION);
264 return false;
265 }
266 }
267 else
268 {
269 logDLError("Error loading grDevices.so", ERROR_LOCATION);
270 return false;
271 }
272 }
273
274 // On versions prior to R 2.12 the event pump is handled by R_ProcessEvents
275 // rather than by the expected R_PolledEvents mechanism. On the Mac
276 // R_ProcessEvents includes a hook (ptr_R_ProcessEvents) but this is
277 // taken by the quartz module. We therefore need a way to hook it but
278 // still delegate to quartz so the quartz device works. do this by
279 // ensuring quartz is loaded then calling QuartzCocoa_SetupEventLoop
installAppleR_2_11_Workaround(void (* newPolledEventHandler)(void))280 void installAppleR_2_11_Workaround(void (*newPolledEventHandler)(void))
281 {
282 // attempt to initialize the quartz event loop (init ptr_R_ProcessEvents
283 // so that we can delegate to it after we override it)
284 if (!setupQuartzEventLoop())
285 {
286 Error error = r::exec::RFunction(".rs.disableQuartz").call();
287 if (error)
288 LOG_ERROR(error);
289 }
290
291 // copy handler function
292 s_polledEventHandler = newPolledEventHandler;
293
294 // preserve old handler and set new one (note that ptr_R_ProcessEvents
295 // might be NULL if we didn't succeed in setting up the quartz
296 // event loop above. in this case the polled event handler will
297 // ignore it
298 s_oldPolledEventHandler = ptr_R_ProcessEvents;
299 ptr_R_ProcessEvents = polledEventHandler;
300 }
301
302 #endif
303
304 } // anonymous namespace
305
306
initializePolledEventHandler(void (* newPolledEventHandler)(void))307 void initializePolledEventHandler(void (*newPolledEventHandler)(void))
308 {
309 // can only call this once
310 BOOST_ASSERT(!s_polledEventHandler);
311
312 // special hack for R 2.11.1 on OSX
313 #ifdef __APPLE__
314 if (!r::util::hasRequiredVersion("2.12"))
315 {
316 installAppleR_2_11_Workaround(newPolledEventHandler);
317 return;
318 }
319 #endif
320
321 // implementation based on addTcl() in tcltk_unix.c
322
323 // copy handler function
324 s_polledEventHandler = newPolledEventHandler;
325
326 // preserve old handler and set new one
327 s_oldPolledEventHandler = R_PolledEvents;
328 R_PolledEvents = polledEventHandler;
329
330 // set R_wait_usec
331 if (R_wait_usec > 10000 || R_wait_usec == 0)
332 R_wait_usec = 10000;
333 }
334
335 // NOTE: this call is used in child process after multicore forks
336 // to make sure all subsequent R code is executed without any
337 // event handlers (appropriate since the forked child is headless).
338 // the prefix "permanently" is used because we explicitly don't
339 // handle the abilty to restore event handling by calling
340 // initializePolledEventHandler -- this is because we overwrite
341 // s_oldPolledEventHandler with NULL, thus losing any reference
342 // we have to a R_PolledEvents value that existed before our
343 // initialization (it would be possible to implement a temporary
344 // disable with a bit more complex control flow)
permanentlyDisablePolledEventHandler()345 void permanentlyDisablePolledEventHandler()
346 {
347 s_polledEventHandler = nullptr;
348 s_oldPolledEventHandler = nullptr;
349 }
350
polledEventHandlerInitialized()351 bool polledEventHandlerInitialized()
352 {
353 return s_polledEventHandler != nullptr;
354 }
355
processEvents()356 void processEvents()
357 {
358 #ifdef __APPLE__
359 R_ProcessEvents();
360
361 // pickup X11 graphics device events (if any) via X11 input handler
362 fd_set* what = R_checkActivity(0,1);
363 if (what != nullptr)
364 R_runHandlers(R_InputHandlers, what);
365 #else
366 // check for activity on standard input handlers (but ignore stdin).
367 // return immediately if there is no input currently available
368 fd_set* what = R_checkActivity(0,1);
369
370 // run handlers on the input (or run the polled event handler if there
371 // is no input currently available)
372 R_runHandlers(R_InputHandlers, what);
373 #endif
374 }
375
376 } // namespace event_loop
377 } // namespace session
378 } // namespace r
379 } // namespace rstudio
380
381
382
383