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