1 /*
2 * SessionMain.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 <session/SessionMain.hpp>
17
18 // required to avoid Win64 winsock order of include
19 // compilation problem
20 #include <boost/asio/io_service.hpp>
21 #include <boost/scope_exit.hpp>
22
23 #ifndef _WIN32
24 #include <sys/types.h>
25 #include <unistd.h>
26 #endif
27
28 #include <string>
29 #include <vector>
30 #include <queue>
31 #include <map>
32 #include <algorithm>
33 #include <cstdlib>
34 #include <csignal>
35 #include <limits>
36
37 #include <boost/shared_ptr.hpp>
38 #include <boost/function.hpp>
39 #include <boost/lexical_cast.hpp>
40 #include <boost/format.hpp>
41
42 #include <boost/date_time/posix_time/posix_time.hpp>
43 #include <boost/algorithm/string/predicate.hpp>
44 #include <boost/algorithm/string/join.hpp>
45
46 #include <core/CrashHandler.hpp>
47 #include <core/BoostSignals.hpp>
48 #include <core/BoostThread.hpp>
49 #include <core/ConfigUtils.hpp>
50 #include <core/FileLock.hpp>
51 #include <core/Exec.hpp>
52 #include <core/Scope.hpp>
53 #include <core/Settings.hpp>
54 #include <core/Thread.hpp>
55 #include <core/Log.hpp>
56 #include <core/system/System.hpp>
57 #include <core/ProgramStatus.hpp>
58 #include <core/FileSerializer.hpp>
59 #include <core/http/URL.hpp>
60 #include <core/http/Request.hpp>
61 #include <core/http/Response.hpp>
62 #include <core/http/UriHandler.hpp>
63 #include <core/json/JsonRpc.hpp>
64 #include <core/system/Crypto.hpp>
65 #include <core/system/Process.hpp>
66 #include <core/system/Environment.hpp>
67 #include <core/system/ParentProcessMonitor.hpp>
68 #include <core/system/Xdg.hpp>
69
70 #include <core/system/FileMonitor.hpp>
71 #include <core/text/TemplateFilter.hpp>
72 #include <core/r_util/RSessionContext.hpp>
73 #include <core/r_util/REnvironment.hpp>
74 #include <core/WaitUtils.hpp>
75
76 #include <r/RJsonRpc.hpp>
77 #include <r/RExec.hpp>
78 #include <r/ROptions.hpp>
79 #include <r/RFunctionHook.hpp>
80 #include <r/RInterface.hpp>
81 #include <r/session/RSession.hpp>
82 #include <r/session/RSessionState.hpp>
83 #include <r/session/RClientState.hpp>
84 #include <r/session/RConsoleActions.hpp>
85 #include <r/session/RConsoleHistory.hpp>
86 #include <r/session/RGraphics.hpp>
87 #include <r/session/REventLoop.hpp>
88 #include <r/RUtil.hpp>
89
90 #include <monitor/MonitorClient.hpp>
91
92 #include <session/SessionConstants.hpp>
93 #include <session/SessionOptions.hpp>
94 #include <session/SessionSourceDatabase.hpp>
95 #include <session/SessionPersistentState.hpp>
96 #include <session/SessionContentUrls.hpp>
97 #include <session/SessionScopes.hpp>
98 #include <session/SessionClientEventService.hpp>
99 #include <session/SessionUrlPorts.hpp>
100 #include <session/RVersionSettings.hpp>
101
102 #include <shared_core/Error.hpp>
103 #include <shared_core/FilePath.hpp>
104 #include <shared_core/StderrLogDestination.hpp>
105
106 #include "SessionAddins.hpp"
107
108 #include "SessionModuleContextInternal.hpp"
109
110 #include "SessionClientEventQueue.hpp"
111 #include "SessionClientInit.hpp"
112 #include "SessionConsoleInput.hpp"
113 #include "SessionDirs.hpp"
114 #include "SessionHttpMethods.hpp"
115 #include "SessionInit.hpp"
116 #include "SessionMainProcess.hpp"
117 #include "SessionRpc.hpp"
118 #include "SessionSuspend.hpp"
119 #include "SessionOfflineService.hpp"
120
121 #include <session/SessionRUtil.hpp>
122 #include <session/SessionPackageProvidedExtension.hpp>
123
124 #include "modules/RStudioAPI.hpp"
125 #include "modules/SessionAbout.hpp"
126 #include "modules/SessionAskPass.hpp"
127 #include "modules/SessionAskSecret.hpp"
128 #include "modules/SessionAuthoring.hpp"
129 #include "modules/SessionBreakpoints.hpp"
130 #include "modules/SessionHTMLPreview.hpp"
131 #include "modules/SessionCodeSearch.hpp"
132 #include "modules/SessionConfigFile.hpp"
133 #include "modules/SessionConsole.hpp"
134 #include "modules/SessionCRANMirrors.hpp"
135 #include "modules/SessionCrypto.hpp"
136 #include "modules/SessionErrors.hpp"
137 #include "modules/SessionFiles.hpp"
138 #include "modules/SessionFind.hpp"
139 #include "modules/SessionGraphics.hpp"
140 #include "modules/SessionDependencies.hpp"
141 #include "modules/SessionDependencyList.hpp"
142 #include "modules/SessionDirty.hpp"
143 #include "modules/SessionWorkbench.hpp"
144 #include "modules/SessionHelp.hpp"
145 #include "modules/SessionPlots.hpp"
146 #include "modules/SessionPath.hpp"
147 #include "modules/SessionPackages.hpp"
148 #include "modules/SessionPackrat.hpp"
149 #include "modules/SessionPlumberViewer.hpp"
150 #include "modules/SessionProfiler.hpp"
151 #include "modules/SessionRAddins.hpp"
152 #include "modules/SessionRCompletions.hpp"
153 #include "modules/SessionRenv.hpp"
154 #include "modules/SessionRPubs.hpp"
155 #include "modules/SessionRHooks.hpp"
156 #include "modules/SessionRSConnect.hpp"
157 #include "modules/SessionShinyViewer.hpp"
158 #include "modules/SessionSpelling.hpp"
159 #include "modules/SessionSource.hpp"
160 #include "modules/SessionTests.hpp"
161 #include "modules/SessionThemes.hpp"
162 #include "modules/SessionTutorial.hpp"
163 #include "modules/SessionUpdates.hpp"
164 #include "modules/SessionVCS.hpp"
165 #include "modules/SessionHistory.hpp"
166 #include "modules/SessionLimits.hpp"
167 #include "modules/SessionLists.hpp"
168 #include "modules/SessionUserPrefs.hpp"
169 #include "modules/build/SessionBuild.hpp"
170 #include "modules/clang/SessionClang.hpp"
171 #include "modules/connections/SessionConnections.hpp"
172 #include "modules/customsource/SessionCustomSource.hpp"
173 #include "modules/data/SessionData.hpp"
174 #include "modules/environment/SessionEnvironment.hpp"
175 #include "modules/jobs/SessionJobs.hpp"
176 #include "modules/overlay/SessionOverlay.hpp"
177 #include "modules/plumber/SessionPlumber.hpp"
178 #include "modules/presentation/SessionPresentation.hpp"
179 #include "modules/preview/SessionPreview.hpp"
180 #include "modules/rmarkdown/RMarkdownTemplates.hpp"
181 #include "modules/rmarkdown/SessionRMarkdown.hpp"
182 #include "modules/rmarkdown/SessionRmdNotebook.hpp"
183 #include "modules/rmarkdown/SessionBookdown.hpp"
184 #include "modules/quarto/SessionQuarto.hpp"
185 #include "modules/shiny/SessionShiny.hpp"
186 #include "modules/sql/SessionSql.hpp"
187 #include "modules/stan/SessionStan.hpp"
188 #include "modules/viewer/SessionViewer.hpp"
189 #include "modules/SessionDiagnostics.hpp"
190 #include "modules/SessionMarkers.hpp"
191 #include "modules/SessionSnippets.hpp"
192 #include "modules/SessionUserCommands.hpp"
193 #include "modules/SessionRAddins.hpp"
194 #include "modules/mathjax/SessionMathJax.hpp"
195 #include "modules/panmirror/SessionPanmirror.hpp"
196 #include "modules/zotero/SessionZotero.hpp"
197 #include "modules/SessionLibPathsIndexer.hpp"
198 #include "modules/SessionObjectExplorer.hpp"
199 #include "modules/SessionReticulate.hpp"
200 #include "modules/SessionPythonEnvironments.hpp"
201 #include "modules/SessionCrashHandler.hpp"
202 #include "modules/SessionRVersions.hpp"
203 #include "modules/SessionTerminal.hpp"
204 #include "modules/SessionFonts.hpp"
205 #include "modules/SessionSystemResources.hpp"
206
207 #include <session/SessionProjectTemplate.hpp>
208
209 #include "modules/SessionGit.hpp"
210 #include "modules/SessionSVN.hpp"
211
212 #include <session/SessionConsoleProcess.hpp>
213
214 #include <session/projects/ProjectsSettings.hpp>
215 #include <session/projects/SessionProjects.hpp>
216 #include "projects/SessionProjectsInternal.hpp"
217
218 #include <session/prefs/UserPrefs.hpp>
219 #include <session/prefs/UserState.hpp>
220
221 #include "workers/SessionWebRequestWorker.hpp"
222
223 #include <session/SessionHttpConnectionListener.hpp>
224
225 #include "session-config.h"
226
227 #include <tests/TestRunner.hpp>
228
229 using namespace rstudio;
230 using namespace rstudio::core;
231
232 // Use rsession alias to avoid collision with 'session'
233 // object brought in by Catch
234 namespace rsession = rstudio::session;
235 using namespace rsession;
236 using namespace rsession::client_events;
237
238 // forward-declare overlay methods
239 namespace rstudio {
240 namespace session {
241
242 namespace {
243
244 std::string s_fallbackLibraryPath;
245
246 } // end anonymous namespace
247
disableExecuteRprofile()248 bool disableExecuteRprofile()
249 {
250 // check for session-specific override
251 if (options().rRunRprofile() == kRunRprofileNo)
252 return true;
253 if (options().rRunRprofile() == kRunRprofileYes)
254 return false;
255
256 bool disableExecuteRprofile = false;
257
258 const projects::ProjectContext& projContext = projects::projectContext();
259 if (projContext.hasProject())
260 {
261 disableExecuteRprofile = projContext.config().disableExecuteRprofile;
262 }
263
264 return disableExecuteRprofile;
265 }
266
quitChildProcesses()267 bool quitChildProcesses()
268 {
269 // allow project override
270 const projects::ProjectContext& projContext = projects::projectContext();
271 if (projContext.hasProject())
272 {
273 switch(projContext.config().quitChildProcessesOnExit)
274 {
275 case r_util::YesValue:
276 return true;
277 case r_util::NoValue:
278 return false;
279 default:
280 // fall through
281 break;
282 }
283 }
284
285 // no project override
286 return rsession::options().quitChildProcessesOnExit();
287 }
288
289 // terminates all child processes, including those unknown to us
290 // no waiting is done to ensure the children shutdown
291 // this is merely a best effort to stop children
terminateAllChildProcesses()292 void terminateAllChildProcesses()
293 {
294 if (!quitChildProcesses())
295 return;
296
297 Error error = system::terminateChildProcesses();
298 if (error)
299 LOG_ERROR(error);
300 }
301
302 namespace overlay {
303 Error initialize();
304 Error initializeSessionProxy();
305 } // namespace overlay
306 } // namespace session
307 } // namespace rstudio
308
309 namespace {
310
311 // R browseUrl handlers
312 std::vector<module_context::RBrowseUrlHandler> s_rBrowseUrlHandlers;
313
314 // R browseFile handlers
315 std::vector<module_context::RBrowseFileHandler> s_rBrowseFileHandlers;
316
317 // indicates whether we should destroy the session at cleanup time
318 // (true if the user does a full quit)
319 bool s_destroySession = false;
320
321 // did we fail to coerce the charset to UTF-8
322 bool s_printCharsetWarning = false;
323
handleINT(int)324 void handleINT(int)
325 {
326 rstudio::r::exec::setInterruptsPending(true);
327 }
328
detectChanges(module_context::ChangeSource source)329 void detectChanges(module_context::ChangeSource source)
330 {
331 module_context::events().onDetectChanges(source);
332 }
333
334 // allow console_input requests to come in when we aren't explicitly waiting
335 // on them (i.e. waitForMethod("console_input")). place them into into a buffer
336 // which is then checked by rConsoleRead prior to it calling waitForMethod
bufferConsoleInput(const core::json::JsonRpcRequest & request,json::JsonRpcResponse * pResponse)337 Error bufferConsoleInput(const core::json::JsonRpcRequest& request,
338 json::JsonRpcResponse* pResponse)
339 {
340 // extract the input
341 return console_input::extractConsoleInput(request);
342 }
343
doSuspendForRestart(const rstudio::r::session::RSuspendOptions & options)344 void doSuspendForRestart(const rstudio::r::session::RSuspendOptions& options)
345 {
346 module_context::consoleWriteOutput("\nRestarting R session...\n\n");
347
348 rstudio::r::session::suspendForRestart(options);
349 }
350
suspendForRestart(const core::json::JsonRpcRequest & request,json::JsonRpcResponse * pResponse)351 Error suspendForRestart(const core::json::JsonRpcRequest& request,
352 json::JsonRpcResponse* pResponse)
353 {
354 // when launcher sessions restart, they need to set a special exit code
355 // to ensure that the rsession-run script restarts the rsession process
356 // instead of having to submit an entirely new launcher session
357 int exitStatus = options().getBoolOverlayOption(kLauncherSessionOption) ?
358 EX_SUSPEND_RESTART_LAUNCHER_SESSION :
359 EX_CONTINUE;
360
361 rstudio::r::session::RSuspendOptions options(exitStatus);
362 Error error = json::readObjectParam(
363 request.params, 0,
364 "save_minimal", &(options.saveMinimal),
365 "save_workspace", &(options.saveWorkspace),
366 "exclude_packages", &(options.excludePackages));
367 if (error)
368 return error;
369
370 pResponse->setAfterResponse(boost::bind(doSuspendForRestart, options));
371 return Success();
372 }
373
374
ping(const core::json::JsonRpcRequest & request,json::JsonRpcResponse * pResponse)375 Error ping(const core::json::JsonRpcRequest& request,
376 json::JsonRpcResponse* pResponse)
377 {
378 return Success();
379 }
380
startClientEventService()381 Error startClientEventService()
382 {
383 return clientEventService().start(rsession::persistentState().activeClientId());
384 }
385
startOfflineService()386 Error startOfflineService()
387 {
388 return offlineService().start();
389 }
390
registerSignalHandlers()391 Error registerSignalHandlers()
392 {
393 using boost::bind;
394 using namespace rstudio::core::system;
395
396 ExecBlock registerBlock;
397
398 module_context::initializeConsoleCtrlHandler();
399
400 // SIGINT: set interrupt flag on R session
401 registerBlock.addFunctions()
402 (bind(handleSignal, SigInt, handleINT));
403
404 // USR1 and USR2: perform suspend in server mode
405 if (rsession::options().programMode() == kSessionProgramModeServer)
406 {
407 registerBlock.addFunctions()
408 (bind(handleSignal, SigUsr1, suspend::handleUSR1))
409 (bind(handleSignal, SigUsr2, suspend::handleUSR2));
410 }
411 // USR1 and USR2: ignore in desktop mode
412 else
413 {
414 registerBlock.addFunctions()
415 (bind(ignoreSignal, SigUsr1))
416 (bind(ignoreSignal, SigUsr2));
417 }
418
419 return registerBlock.execute();
420 }
421
422
runPreflightScript()423 Error runPreflightScript()
424 {
425 // alias options
426 Options& options = rsession::options();
427
428 // run the preflight script (if specified)
429 if (rsession::options().programMode() == kSessionProgramModeServer)
430 {
431 FilePath preflightScriptPath = options.preflightScriptPath();
432 if (!preflightScriptPath.isEmpty())
433 {
434 if (preflightScriptPath.exists())
435 {
436 // run the script (ignore errors and continue no matter what
437 // the outcome of the script is)
438 std::string script = preflightScriptPath.getAbsolutePath();
439 core::system::ProcessResult result;
440 Error error = runCommand(script,
441 core::system::ProcessOptions(),
442 &result);
443 if (error)
444 {
445 error.addProperty("preflight-script", script);
446 LOG_ERROR(error);
447 }
448 }
449 else
450 {
451 LOG_WARNING_MESSAGE("preflight script does not exist: " +
452 preflightScriptPath.getAbsolutePath());
453 }
454 }
455 }
456
457 // always return success
458 return Success();
459 }
460
461 // implemented below
462 void stopMonitorWorkerThread();
463
exitEarly(int status)464 void exitEarly(int status)
465 {
466 stopMonitorWorkerThread();
467 FileLock::cleanUp();
468 FilePath(s_fallbackLibraryPath).removeIfExists();
469 ::exit(status);
470 }
471
rInit(const rstudio::r::session::RInitInfo & rInitInfo)472 Error rInit(const rstudio::r::session::RInitInfo& rInitInfo)
473 {
474 // save state we need to reference later
475 suspend::setSessionResumed(rInitInfo.resumed);
476
477 // record built-in waitForMethod handlers
478 module_context::registerWaitForMethod(kLocatorCompleted);
479 module_context::registerWaitForMethod(kEditCompleted);
480 module_context::registerWaitForMethod(kChooseFileCompleted);
481 module_context::registerWaitForMethod(kUserPromptCompleted);
482 module_context::registerWaitForMethod(kHandleUnsavedChangesCompleted);
483 module_context::registerWaitForMethod(kRStudioAPIShowDialogMethod);
484
485 // execute core initialization functions
486 using boost::bind;
487 using namespace rstudio::core::system;
488 using namespace rsession::module_context;
489 ExecBlock initialize;
490 initialize.addFunctions()
491
492 // client event service
493 (startClientEventService)
494
495 // rpc methods
496 (rpc::initialize)
497
498 // json-rpc listeners
499 (bind(registerRpcMethod, kConsoleInput, bufferConsoleInput))
500 (bind(registerRpcMethod, "suspend_for_restart", suspendForRestart))
501 (bind(registerRpcMethod, "ping", ping))
502
503 // signal handlers
504 (registerSignalHandlers)
505
506 // main module context
507 (module_context::initialize)
508
509 // prefs (early init required -- many modules including projects below require
510 // preference access)
511 (modules::prefs::initialize)
512
513 // projects (early project init required -- module inits below
514 // can then depend on e.g. computed defaultEncoding)
515 (projects::initialize)
516
517 // source database
518 (source_database::initialize)
519
520 // content urls
521 (content_urls::initialize)
522
523 // URL port transformations
524 (url_ports::initialize)
525
526 // overlay R
527 (bind(sourceModuleRFile, "SessionOverlay.R"))
528
529 // addins
530 (addins::initialize)
531
532 // console processes
533 (console_process::initialize)
534
535 (http_methods::initialize)
536
537 // r utils
538 (r_utils::initialize)
539
540 // modules with c++ implementations
541 (modules::spelling::initialize)
542 (modules::lists::initialize)
543 (modules::path::initialize)
544 (modules::limits::initialize)
545 (modules::ppe::initialize)
546 (modules::ask_pass::initialize)
547 (modules::console::initialize)
548 #ifdef RSTUDIO_SERVER
549 (modules::crypto::initialize)
550 #endif
551 (modules::code_search::initialize)
552 (modules::clang::initialize)
553 (modules::connections::initialize)
554 (modules::files::initialize)
555 (modules::find::initialize)
556 (modules::environment::initialize)
557 (modules::dependencies::initialize)
558 (modules::dependency_list::initialize)
559 (modules::dirty::initialize)
560 (modules::workbench::initialize)
561 (modules::data::initialize)
562 (modules::help::initialize)
563 (modules::presentation::initialize)
564 (modules::preview::initialize)
565 (modules::plots::initialize)
566 (modules::packages::initialize)
567 (modules::cran_mirrors::initialize)
568 (modules::profiler::initialize)
569 (modules::viewer::initialize)
570 (modules::quarto::initialize)
571 (modules::rmarkdown::initialize)
572 (modules::rmarkdown::notebook::initialize)
573 (modules::rmarkdown::templates::initialize)
574 (modules::rmarkdown::bookdown::initialize)
575 (modules::rpubs::initialize)
576 (modules::shiny::initialize)
577 (modules::sql::initialize)
578 (modules::stan::initialize)
579 (modules::plumber::initialize)
580 (modules::source::initialize)
581 (modules::source_control::initialize)
582 (modules::authoring::initialize)
583 (modules::html_preview::initialize)
584 (modules::history::initialize)
585 (modules::build::initialize)
586 (modules::overlay::initialize)
587 (modules::breakpoints::initialize)
588 (modules::errors::initialize)
589 (modules::updates::initialize)
590 (modules::about::initialize)
591 (modules::shiny_viewer::initialize)
592 (modules::plumber_viewer::initialize)
593 (modules::rsconnect::initialize)
594 (modules::packrat::initialize)
595 (modules::renv::initialize)
596 (modules::rhooks::initialize)
597 (modules::r_packages::initialize)
598 (modules::diagnostics::initialize)
599 (modules::markers::initialize)
600 (modules::snippets::initialize)
601 (modules::user_commands::initialize)
602 (modules::r_addins::initialize)
603 (modules::projects::templates::initialize)
604 (modules::mathjax::initialize)
605 (modules::panmirror::initialize)
606 (modules::zotero::initialize)
607 (modules::rstudioapi::initialize)
608 (modules::libpaths::initialize)
609 (modules::explorer::initialize)
610 (modules::ask_secret::initialize)
611 (modules::reticulate::initialize)
612 (modules::python_environments::initialize)
613 (modules::tests::initialize)
614 (modules::jobs::initialize)
615 (modules::themes::initialize)
616 (modules::customsource::initialize)
617 (modules::crash_handler::initialize)
618 (modules::r_versions::initialize)
619 (modules::terminal::initialize)
620 (modules::config_file::initialize)
621 (modules::tutorial::initialize)
622 (modules::graphics::initialize)
623 (modules::fonts::initialize)
624 (modules::system_resources::initialize)
625
626 // workers
627 (workers::web_request::initialize)
628
629 // R code
630 (bind(sourceModuleRFile, "SessionCodeTools.R"))
631 (bind(sourceModuleRFile, "SessionPatches.R"))
632
633 (startOfflineService)
634
635 // unsupported functions
636 (bind(rstudio::r::function_hook::registerUnsupported, "bug.report", "utils"))
637 (bind(rstudio::r::function_hook::registerUnsupported, "help.request", "utils"))
638 ;
639
640 Error error = initialize.execute();
641 if (error)
642 return error;
643
644 // if we are in verify installation mode then we should exit (successfully) now
645 if (rsession::options().verifyInstallation())
646 {
647 // in desktop mode we write a success message and execute diagnostics
648 if (rsession::options().programMode() == kSessionProgramModeDesktop)
649 {
650 std::cout << "Successfully initialized R session."
651 << std::endl << std::endl;
652 FilePath diagFile = module_context::sourceDiagnostics();
653 if (!diagFile.isEmpty())
654 {
655 std::cout << "Diagnostics report written to: "
656 << diagFile << std::endl
657 << "Please audit the report and remove any sensitive information "
658 << "before submitting." << std::endl << std::endl;
659
660 Error error = rstudio::r::exec::RFunction(".rs.showDiagnostics").call();
661 if (error)
662 LOG_ERROR(error);
663 }
664 }
665 rsession::options().verifyInstallationHomeDir().removeIfExists();
666
667 int exitCode = modules::overlay::verifyInstallation();
668 exitEarly(exitCode);
669 }
670
671 // register all of the json rpc methods implemented in R
672 json::JsonRpcMethods rMethods;
673 error = rstudio::r::json::getRpcMethods(&rMethods);
674 if (error)
675 return error;
676
677 for (json::JsonRpcMethod method : rMethods)
678 {
679 registerRpcMethod(json::adaptMethodToAsync(method));
680 }
681
682 // add gwt handlers if we are running desktop mode
683 if ((rsession::options().programMode() == kSessionProgramModeDesktop) ||
684 rsession::options().standalone())
685 {
686 http_methods::registerGwtHandlers();
687 }
688
689 // enque abend warning event if necessary (but not in standalone
690 // mode since those processes are often aborted unceremoniously)
691 using namespace rsession::client_events;
692 if (rsession::persistentState().hadAbend() && !options().standalone())
693 {
694 LOG_ERROR_MESSAGE("The previous R session terminated abnormally");
695 ClientEvent abendWarningEvent(kAbendWarning);
696 rsession::clientEventQueue().add(abendWarningEvent);
697 }
698
699 if (s_printCharsetWarning)
700 rstudio::r::exec::warning("Character set is not UTF-8; please change your locale");
701
702 // propagate console history options
703 rstudio::r::session::consoleHistory().setRemoveDuplicates(
704 prefs::userPrefs().removeHistoryDuplicates());
705
706
707 // register function editor on windows
708 #ifdef _WIN32
709 error = rstudio::r::exec::RFunction(".rs.registerFunctionEditor").call();
710 if (error)
711 LOG_ERROR(error);
712 #endif
713
714 // clear out stale uploaded tmp files
715 if (module_context::userUploadedFilesScratchPath().exists())
716 {
717 std::vector<FilePath> childPaths;
718 error = module_context::userUploadedFilesScratchPath().getChildren(childPaths);
719 if (error)
720 LOG_ERROR(error);
721
722 constexpr double secondsPerDay = 60*60*24;
723 for (const FilePath& childPath : childPaths)
724 {
725 double diffTime = std::difftime(std::time(nullptr),
726 childPath.getLastWriteTime());
727 if (diffTime > secondsPerDay)
728 {
729 Error error = childPath.remove();
730 if (error)
731 LOG_ERROR(error);
732 }
733 }
734 }
735
736 // set flag indicating we had an abnormal end (if this doesn't get
737 // unset by the time we launch again then we didn't terminate normally
738 // i.e. either the process dying unexpectedly or a call to R_Suicide)
739 if (!rsession::options().runTests())
740 {
741 rsession::persistentState().setAbend(true);
742 }
743
744 // begin session
745 using namespace module_context;
746 activeSession().beginSession(rVersion(), rHomeDir(), rVersionLabel());
747
748 // setup fork handlers
749 main_process::setupForkHandlers();
750
751 // success!
752 return Success();
753 }
754
rInitComplete()755 void rInitComplete()
756 {
757 module_context::events().onInitComplete();
758 }
759
notifyIfRVersionChanged()760 void notifyIfRVersionChanged()
761 {
762 using namespace rstudio::r::session::state;
763
764 SessionStateInfo info = getSessionStateInfo();
765
766 if (info.activeRVersion != info.suspendedRVersion)
767 {
768 const char* fmt =
769 "R version change [%1% -> %2%] detected when restoring session; "
770 "search path not restored";
771
772 boost::format formatter(fmt);
773 formatter
774 % std::string(info.suspendedRVersion)
775 % std::string(info.activeRVersion);
776
777 std::string msg = formatter.str();
778 ::REprintf("%s\n", msg.c_str());
779 }
780 }
781
rSessionInitHook(bool newSession)782 void rSessionInitHook(bool newSession)
783 {
784 // allow any packages listening to complete initialization
785 modules::rhooks::invokeHook(kSessionInitHook, newSession);
786
787 // finish off initialization
788 module_context::events().afterSessionInitHook(newSession);
789
790 // notify the user if the R version has changed
791 notifyIfRVersionChanged();
792
793 // fire an event to the client
794 ClientEvent event(client_events::kDeferredInitCompleted);
795 module_context::enqueClientEvent(event);
796 }
797
rDeferredInit(bool newSession)798 void rDeferredInit(bool newSession)
799 {
800 module_context::events().onDeferredInit(newSession);
801
802 // schedule execution of the session init hook
803 module_context::scheduleDelayedWork(
804 boost::posix_time::seconds(1),
805 boost::bind(rSessionInitHook, newSession));
806 }
807
rEditFile(const std::string & file)808 int rEditFile(const std::string& file)
809 {
810 // read file contents
811 FilePath filePath(file);
812 std::string fileContents;
813 if (filePath.exists())
814 {
815 Error readError = core::readStringFromFile(filePath, &fileContents);
816 if (readError)
817 {
818 LOG_ERROR(readError);
819 return 1; // r will raise/report an error indicating edit failed
820 }
821 }
822
823 // fire edit event
824 ClientEvent editEvent = rsession::showEditorEvent(fileContents, true, false);
825 rsession::clientEventQueue().add(editEvent);
826
827 // wait for edit_completed
828 json::JsonRpcRequest request;
829 bool succeeded = http_methods::waitForMethod(kEditCompleted,
830 editEvent,
831 suspend::disallowSuspend,
832 &request);
833
834 if (!succeeded)
835 return false;
836
837 // user cancelled edit
838 if (request.params[0].isNull())
839 {
840 return 0; // no-op, object will be re-parsed from original content
841 }
842
843 // user confirmed edit
844 else
845 {
846 // extract the content
847 std::string editedFileContents;
848 Error error = json::readParam(request.params, 0, &editedFileContents);
849 if (error)
850 {
851 LOG_ERROR(error);
852 return 1; // error (r will notify user via the console)
853 }
854
855 // write the content back to the file (append newline expected by R)
856 editedFileContents += "\n";
857 Error writeError = core::writeStringToFile(filePath, editedFileContents);
858 if (writeError)
859 {
860 LOG_ERROR(writeError);
861 return 1 ; // error (r will notify user via the console)
862 }
863
864 // success!
865 return 0;
866 }
867 }
868
869
rChooseFile(bool newFile)870 FilePath rChooseFile(bool newFile)
871 {
872 // fire choose file event
873 ClientEvent chooseFileEvent(kChooseFile, newFile);
874 rsession::clientEventQueue().add(chooseFileEvent);
875
876 // wait for choose_file_completed
877 json::JsonRpcRequest request;
878 bool succeeded = http_methods::waitForMethod(kChooseFileCompleted,
879 chooseFileEvent,
880 suspend::disallowSuspend,
881 &request);
882
883 if (!succeeded)
884 return FilePath();
885
886 // extract the file name
887 std::string fileName;
888 if (!request.params[0].isNull())
889 {
890 Error error = json::readParam(request.params, 0, &fileName);
891 if (error)
892 LOG_ERROR(error);
893
894 // resolve aliases and return it
895 return module_context::resolveAliasedPath(fileName);
896 }
897 else
898 {
899 return FilePath();
900 }
901 }
902
903
rBusy(bool busy)904 void rBusy(bool busy)
905 {
906 if (main_process::wasForked())
907 return;
908
909 // screen out busy = true events that occur when R isn't busy
910 if (busy && !console_input::executing())
911 return;
912
913 ClientEvent busyEvent(kBusy, busy);
914 rsession::clientEventQueue().add(busyEvent);
915 }
916
rConsoleWrite(const std::string & output,int otype)917 void rConsoleWrite(const std::string& output, int otype)
918 {
919 if (main_process::wasForked())
920 return;
921
922 int event = otype == 1 ? kConsoleWriteError : kConsoleWriteOutput;
923 ClientEvent writeEvent(event, output);
924 rsession::clientEventQueue().add(writeEvent);
925
926 // fire event
927 module_context::events().onConsoleOutput(
928 otype == 1 ? module_context::ConsoleOutputError :
929 module_context::ConsoleOutputNormal,
930 output);
931
932 }
933
rConsoleHistoryReset()934 void rConsoleHistoryReset()
935 {
936 json::Array historyJson;
937 rstudio::r::session::consoleHistory().asJson(&historyJson);
938 json::Object resetJson;
939 resetJson["history"] = historyJson;
940 resetJson["preserve_ui_context"] = false;
941 ClientEvent event(kConsoleResetHistory, resetJson);
942 rsession::clientEventQueue().add(event);
943 }
944
rLocator(double * x,double * y)945 bool rLocator(double* x, double* y)
946 {
947 // since locator can be called in a loop we need to checkForChanges
948 // here (because we'll never get back to the REPL). this enables
949 // identify() to correctly update the plot after each click
950 detectChanges(module_context::ChangeSourceREPL);
951
952 // fire locator event
953 ClientEvent locatorEvent(kLocator);
954 rsession::clientEventQueue().add(locatorEvent);
955
956 // wait for locator_completed
957 json::JsonRpcRequest request;
958 bool succeeded = http_methods::waitForMethod(kLocatorCompleted,
959 locatorEvent,
960 suspend::disallowSuspend,
961 &request);
962
963 if (!succeeded)
964 return false;
965
966 // see if we got a point
967 if ((request.params.getSize() > 0) && !request.params[0].isNull())
968 {
969 // read the x and y
970 Error error = json::readObjectParam(request.params, 0,
971 "x", x,
972 "y", y);
973 if (error)
974 {
975 LOG_ERROR(error);
976 return false;
977 }
978
979 // return true
980 return true;
981 }
982 else
983 {
984 return false;
985 }
986 }
987
rShowFile(const std::string & title,const FilePath & filePath,bool del)988 void rShowFile(const std::string& title, const FilePath& filePath, bool del)
989 {
990 if (rsession::options().programMode() == kSessionProgramModeServer)
991 {
992 // for files in the temporary directory, show as content
993 //
994 // (perform this check first to handle case where
995 // tempdir lives within the user's home directory)
996 if (filePath.isWithin(module_context::tempDir()))
997 {
998 module_context::showContent(title, filePath);
999 }
1000
1001 // for files in the user's home directory and pdfs use an external browser
1002 else if (module_context::isVisibleUserFile(filePath) ||
1003 (filePath.getExtensionLowerCase() == ".pdf"))
1004 {
1005 module_context::showFile(filePath);
1006 }
1007
1008 // otherwise, show as content
1009 else
1010 {
1011 module_context::showContent(title, filePath);
1012 }
1013 }
1014 else // (rsession::options().programMode() == kSessionProgramModeDesktop
1015 {
1016 #ifdef _WIN32
1017 if (!filePath.getExtension().empty())
1018 {
1019 module_context::showFile(filePath);
1020 del = false;
1021 }
1022 else
1023 {
1024 module_context::showContent(title, filePath);
1025 }
1026 #else
1027 module_context::showContent(title, filePath);
1028 #endif
1029 }
1030
1031 if (del)
1032 {
1033 Error error = filePath.removeIfExists();
1034 if (error)
1035 LOG_ERROR(error);
1036 }
1037 }
1038
rBrowseURL(const std::string & url)1039 void rBrowseURL(const std::string& url)
1040 {
1041 // first see if any of our handlers want to take it
1042 for (std::vector<module_context::RBrowseUrlHandler>::const_iterator
1043 it = s_rBrowseUrlHandlers.begin();
1044 it != s_rBrowseUrlHandlers.end();
1045 ++it)
1046 {
1047 if ((*it)(url))
1048 return;
1049 }
1050
1051 // raise event to client
1052 rsession::clientEventQueue().add(browseUrlEvent(url));
1053 }
1054
rBrowseFile(const core::FilePath & filePath)1055 void rBrowseFile(const core::FilePath& filePath)
1056 {
1057 // see if any of our handlers want to take it
1058 for (std::vector<module_context::RBrowseFileHandler>::const_iterator
1059 it = s_rBrowseFileHandlers.begin();
1060 it != s_rBrowseFileHandlers.end();
1061 ++it)
1062 {
1063 if ((*it)(filePath))
1064 return;
1065 }
1066
1067 // see if this is an html file in the session temporary directory (in which
1068 // case we can serve it over http)
1069 if ((filePath.getMimeContentType() == "text/html") &&
1070 filePath.isWithin(module_context::tempDir()) &&
1071 rstudio::r::util::hasRequiredVersion("2.14"))
1072 {
1073 std::string path = filePath.getRelativePath(module_context::tempDir());
1074 std::string url = module_context::sessionTempDirUrl(path);
1075 rsession::clientEventQueue().add(browseUrlEvent(url));
1076 }
1077 // otherwise just show the file
1078 else
1079 {
1080 module_context::showFile(filePath);
1081 }
1082 }
1083
rShowHelp(const std::string & helpURL)1084 void rShowHelp(const std::string& helpURL)
1085 {
1086 ClientEvent showHelpEvent(kShowHelp, helpURL);
1087 rsession::clientEventQueue().add(showHelpEvent);
1088 }
1089
rShowMessage(const std::string & message)1090 void rShowMessage(const std::string& message)
1091 {
1092 ClientEvent event = showErrorMessageEvent("R Error", message);
1093 rsession::clientEventQueue().add(event);
1094 }
1095
logExitEvent(const monitor::Event & precipitatingEvent)1096 void logExitEvent(const monitor::Event& precipitatingEvent)
1097 {
1098 using namespace monitor;
1099 client().logEvent(precipitatingEvent);
1100 client().logEvent(Event(kSessionScope, kSessionExitEvent));
1101 }
1102
rSuspended(const rstudio::r::session::RSuspendOptions & options)1103 void rSuspended(const rstudio::r::session::RSuspendOptions& options)
1104 {
1105 // log to monitor
1106 using namespace monitor;
1107 std::string data;
1108 if (suspend::suspendedFromTimeout())
1109 data = safe_convert::numberToString(rsession::options().timeoutMinutes());
1110 logExitEvent(Event(kSessionScope, kSessionSuspendEvent, data));
1111
1112 // fire event
1113 module_context::onSuspended(options, &(persistentState().settings()));
1114 }
1115
rResumed()1116 void rResumed()
1117 {
1118 module_context::onResumed(persistentState().settings());
1119 }
1120
rHandleUnsavedChanges()1121 bool rHandleUnsavedChanges()
1122 {
1123 // enque the event
1124 ClientEvent event(client_events::kHandleUnsavedChanges);
1125 module_context::enqueClientEvent(event);
1126
1127 // wait for method
1128 json::JsonRpcRequest request;
1129 bool succeeded = http_methods::waitForMethod(
1130 kHandleUnsavedChangesCompleted,
1131 boost::bind(http_methods::waitForMethodInitFunction,
1132 event),
1133 suspend::disallowSuspend,
1134 &request);
1135
1136 if (!succeeded)
1137 return false;
1138
1139 // read response and return it
1140 bool handled = false;
1141 Error error = json::readParam(request.params, 0, &handled);
1142 if (error)
1143 LOG_ERROR(error);
1144 return handled;
1145 }
1146
rQuit()1147 void rQuit()
1148 {
1149 if (main_process::wasForked())
1150 return;
1151
1152 // log to monitor
1153 using namespace monitor;
1154 logExitEvent(Event(kSessionScope, kSessionQuitEvent));
1155
1156 // notify modules
1157 module_context::events().onQuit();
1158
1159 // enque a quit event
1160 bool switchProjects;
1161 if (options().switchProjectsWithUrl())
1162 {
1163 switchProjects = !http_methods::nextSessionUrl().empty();
1164 }
1165 else
1166 {
1167 switchProjects = !projects::ProjectsSettings(options().userScratchPath())
1168 .switchToProjectPath().empty();
1169 }
1170
1171 // if we aren't switching projects then destroy the active session at cleanup
1172 s_destroySession = !switchProjects;
1173
1174 json::Object jsonData;
1175 jsonData["switch_projects"] = switchProjects;
1176 jsonData["next_session_url"] = http_methods::nextSessionUrl();
1177 ClientEvent quitEvent(kQuit, jsonData);
1178 rsession::clientEventQueue().add(quitEvent);
1179 }
1180
1181 // NOTE: this event is never received on windows (because we can't
1182 // override suicide on windows)
rSuicide(const std::string & message)1183 void rSuicide(const std::string& message)
1184 {
1185 if (main_process::wasForked())
1186 return;
1187
1188 // log to monitor
1189 using namespace monitor;
1190 logExitEvent(Event(kSessionScope, kSessionSuicideEvent));
1191
1192 // log the error if it was unexpected
1193 if (!message.empty())
1194 LOG_ERROR_MESSAGE("R SUICIDE: " + message);
1195
1196 // enque suicide event so the client knows
1197 ClientEvent suicideEvent(kSuicide, message);
1198 rsession::clientEventQueue().add(suicideEvent);
1199 }
1200
1201 // terminate all children of the provided process supervisor
1202 // and then wait a brief period to attempt to reap the child
terminateAllChildren(core::system::ProcessSupervisor * pSupervisor,const ErrorLocation & location)1203 void terminateAllChildren(core::system::ProcessSupervisor* pSupervisor,
1204 const ErrorLocation& location)
1205 {
1206 // send kill signal
1207 pSupervisor->terminateAll();
1208
1209 // wait and reap children (but for no longer than 1 second)
1210 if (!pSupervisor->wait(boost::posix_time::milliseconds(10),
1211 boost::posix_time::milliseconds(1000)))
1212 {
1213 core::log::logWarningMessage(
1214 "Process supervisor did not terminate within 1 second",
1215 location);
1216 }
1217 }
1218
rCleanup(bool terminatedNormally)1219 void rCleanup(bool terminatedNormally)
1220 {
1221 try
1222 {
1223 // bail completely if we were forked
1224 if (main_process::wasForked())
1225 return;
1226
1227 // note that we didn't abend
1228 if (terminatedNormally)
1229 rsession::persistentState().setAbend(false);
1230
1231 // set active session flag indicating we are no longer running
1232 module_context::activeSession().endSession();
1233
1234 // fire shutdown event to modules
1235 module_context::events().onShutdown(terminatedNormally);
1236
1237 // destroy session if requested
1238 if (s_destroySession)
1239 {
1240 Error error = module_context::activeSession().destroy();
1241 if (error)
1242 LOG_ERROR(error);
1243
1244 // fire destroy event to modules
1245 module_context::events().onDestroyed();
1246 }
1247
1248 // clean up locks
1249 FileLock::cleanUp();
1250
1251 // stop file monitor (need to do this explicitly as otherwise we can
1252 // run into issues during close where the runtime attempts to clean
1253 // up its data structures at same time that monitor wants to exit)
1254 //
1255 // https://github.com/rstudio/rstudio/issues/5222
1256 system::file_monitor::stop();
1257
1258 // stop the monitor thread
1259 stopMonitorWorkerThread();
1260
1261 // cause graceful exit of clientEventService (ensures delivery
1262 // of any pending events prior to process termination). wait a
1263 // very brief interval first to allow the quit or other termination
1264 // related events to get into the queue
1265 boost::this_thread::sleep(boost::posix_time::milliseconds(100));
1266
1267 // only stop the http services if we are in server mode. in desktop
1268 // mode we had issues with both OSX crashing and with Windows taking
1269 // the full 3 seconds to terminate. the cleanup is kind of a nice
1270 // to have and most important on the server where we delete the
1271 // unix domain socket file so it is no big deal to bypass it
1272 if (rsession::options().programMode() == kSessionProgramModeServer)
1273 {
1274 clientEventService().stop();
1275 httpConnectionListener().stop();
1276 }
1277
1278 // terminate known child processes
1279 terminateAllChildren(&module_context::processSupervisor(),
1280 ERROR_LOCATION);
1281
1282 // terminate unknown child processes
1283 // processes launched by means we do not control
1284 terminateAllChildProcesses();
1285 }
1286 CATCH_UNEXPECTED_EXCEPTION
1287
1288 }
1289
rSerialization(int action,const FilePath & targetPath)1290 void rSerialization(int action, const FilePath& targetPath)
1291 {
1292 json::Object serializationActionObject;
1293 serializationActionObject["type"] = action;
1294 if (!targetPath.isEmpty())
1295 {
1296 serializationActionObject["targetPath"] =
1297 module_context::createAliasedPath(targetPath);
1298 }
1299
1300 ClientEvent event(kSessionSerialization, serializationActionObject);
1301 rsession::clientEventQueue().add(event);
1302 }
1303
rRunTests()1304 void rRunTests()
1305 {
1306 // run tests
1307 int status = tests::run();
1308
1309 // try to clean up session
1310 rCleanup(true);
1311
1312 // exit if we haven't already
1313 exitEarly(status);
1314 }
1315
ensureRProfile()1316 void ensureRProfile()
1317 {
1318 // check if we need to create the profile (bail if we don't)
1319 Options& options = rsession::options();
1320 if (!options.createProfile())
1321 return;
1322
1323 FilePath rProfilePath = options.userHomePath().completePath(".Rprofile");
1324 if (!rProfilePath.exists() && !prefs::userState().autoCreatedProfile())
1325 {
1326 prefs::userState().setAutoCreatedProfile(true);
1327
1328 std::string p;
1329 p = "# .Rprofile -- commands to execute at the beginning of each R session\n"
1330 "#\n"
1331 "# You can use this file to load packages, set options, etc.\n"
1332 "#\n"
1333 "# NOTE: changes in this file won't be reflected until after you quit\n"
1334 "# and start a new session\n"
1335 "#\n\n";
1336
1337 Error error = writeStringToFile(rProfilePath, p);
1338 if (error)
1339 LOG_ERROR(error);
1340 }
1341 }
1342
ensurePublicFolder()1343 void ensurePublicFolder()
1344 {
1345 // check if we need to create the public folder (bail if we don't)
1346 Options& options = rsession::options();
1347 if (!options.createPublicFolder())
1348 return;
1349
1350 FilePath publicPath = options.userHomePath().completePath("Public");
1351 if (!publicPath.exists())
1352 {
1353 // create directory
1354 Error error = publicPath.ensureDirectory();
1355 if (error)
1356 {
1357 LOG_ERROR(error);
1358 return;
1359 }
1360
1361 // write notice
1362 boost::format fmt(
1363 "\n"
1364 "Files within your public folder are readable (but not writeable)\n"
1365 "by others. The path for other users to access your public folder is:\n"
1366 "\n"
1367 " /shared/%1%/\n"
1368 "\n"
1369 "For example, to source a file named \"Utils.R\" from your public\n"
1370 "folder another user would enter the command:\n"
1371 "\n"
1372 " source(\"/shared/%1%/Utils.R\")\n"
1373 "\n"
1374 "To load a dataset named \"Data.csv\" they would enter the command:\n"
1375 "\n"
1376 " read.csv(\"/shared/%1%/Data.csv\")\n"
1377 "\n"
1378 "Other users can also browse and open the files available in your\n"
1379 "public folder by:\n"
1380 "\n"
1381 " 1) Selecting the Open File... menu item\n"
1382 " 2) Entering /shared/%1%/ as the file name\n"
1383 " 3) Clicking the Open button (or pressing the Enter key)\n"
1384 "\n"
1385 );
1386 std::string notice = boost::str(fmt % options.userIdentity());
1387
1388 FilePath noticePath = publicPath.completePath("AboutPublic.txt");
1389 error = writeStringToFile(noticePath, notice);
1390 if (error)
1391 LOG_ERROR(error);
1392 }
1393 }
1394
ensureRLibsUser(const core::FilePath & userHomePath,const std::string & rLibsUser)1395 void ensureRLibsUser(const core::FilePath& userHomePath,
1396 const std::string& rLibsUser)
1397 {
1398 FilePath rLibsUserPath = FilePath::resolveAliasedPath(rLibsUser,
1399 userHomePath);
1400 Error error = rLibsUserPath.ensureDirectory();
1401 if (error)
1402 LOG_ERROR(error);
1403 }
1404
1405 #ifdef __APPLE__
1406 // we now launch our child processes from the desktop using our standard
1407 // process management code which closes all file descriptors thereby
1408 // breaking parent_process_monitor. So on the Mac we use the more simplistic
1409 // approach of polling for ppid == 1. This is fine because we expect that
1410 // the Desktop will _always_ outlive us (it waits for us to exit before
1411 // closing) so anytime it exits before we do it must be a crash). we don't
1412 // call abort() however because we don't want a crash report to occur
detectParentTermination()1413 void detectParentTermination()
1414 {
1415 while(true)
1416 {
1417 boost::this_thread::sleep(boost::posix_time::milliseconds(500));
1418 if (::getppid() == 1)
1419 {
1420 exitEarly(EXIT_FAILURE);
1421 }
1422 }
1423 }
1424 #else
detectParentTermination()1425 void detectParentTermination()
1426 {
1427 using namespace parent_process_monitor;
1428 ParentTermination result = waitForParentTermination();
1429 if (result == ParentTerminationAbnormal)
1430 {
1431 LOG_ERROR_MESSAGE("Parent terminated");
1432
1433 // we no longer exit with ::abort because it generated unwanted exceptions
1434 // ::_Exit should perform the same functionality (not running destructors and exiting process)
1435 // without generating an exception
1436 std::_Exit(EXIT_FAILURE);
1437 }
1438 else if (result == ParentTerminationNormal)
1439 {
1440 //LOG_ERROR_MESSAGE("Normal terminate");
1441 }
1442 else if (result == ParentTerminationWaitFailure)
1443 {
1444 LOG_ERROR_MESSAGE("waitForParentTermination failed");
1445 }
1446 }
1447 #endif
1448
saveWorkspaceOption()1449 SA_TYPE saveWorkspaceOption()
1450 {
1451 // convert from internal type to R type
1452 int saveAction = module_context::saveWorkspaceAction();
1453 if (saveAction == rstudio::r::session::kSaveActionSave)
1454 return SA_SAVE;
1455 else if (saveAction == rstudio::r::session::kSaveActionNoSave)
1456 return SA_NOSAVE;
1457 else
1458 return SA_SAVEASK;
1459 }
1460
restoreWorkspaceOption()1461 bool restoreWorkspaceOption()
1462 {
1463 // check options for session-specific override
1464 if (options().rRestoreWorkspace() == kRestoreWorkspaceNo)
1465 return false;
1466 else if (options().rRestoreWorkspace() == kRestoreWorkspaceYes)
1467 return true;
1468
1469 // allow project override
1470 const projects::ProjectContext& projContext = projects::projectContext();
1471 if (projContext.hasProject())
1472 {
1473 switch(projContext.config().restoreWorkspace)
1474 {
1475 case r_util::YesValue:
1476 return true;
1477 case r_util::NoValue:
1478 return false;
1479 default:
1480 // fall through
1481 break;
1482 }
1483 }
1484
1485 // no project override
1486 return prefs::userPrefs().loadWorkspace() ||
1487 !rsession::options().initialEnvironmentFileOverride().isEmpty();
1488 }
1489
alwaysSaveHistoryOption()1490 bool alwaysSaveHistoryOption()
1491 {
1492 return prefs::userPrefs().alwaysSaveHistory();
1493 }
1494
getStartupEnvironmentFilePath()1495 FilePath getStartupEnvironmentFilePath()
1496 {
1497 FilePath envFile = rsession::options().initialEnvironmentFileOverride();
1498 if (!envFile.isEmpty())
1499 return envFile;
1500 else
1501 return dirs::rEnvironmentDir().completePath(".RData");
1502 }
1503
loadCranRepos(const std::string & repos,rstudio::r::session::ROptions * pROptions)1504 void loadCranRepos(const std::string& repos,
1505 rstudio::r::session::ROptions* pROptions)
1506 {
1507 std::vector<std::string> parts;
1508 boost::split(parts, repos, boost::is_any_of("|"));
1509 pROptions->rCRANSecondary = "";
1510
1511 std::vector<std::string> secondary;
1512 for (size_t idxParts = 0; idxParts < parts.size() - 1; idxParts += 2)
1513 {
1514 if (string_utils::toLower(parts[idxParts]) == "cran")
1515 pROptions->rCRANUrl = parts[idxParts + 1];
1516 else
1517 secondary.push_back(std::string(parts[idxParts]) + "|" + parts[idxParts + 1]);
1518 }
1519 pROptions->rCRANSecondary = algorithm::join(secondary, "|");
1520 }
1521
1522 } // anonymous namespace
1523
1524
1525 // provide definition methods for rsession::module_context
1526 namespace rstudio {
1527 namespace session {
1528 namespace module_context {
1529
registerRBrowseUrlHandler(const RBrowseUrlHandler & handler)1530 Error registerRBrowseUrlHandler(const RBrowseUrlHandler& handler)
1531 {
1532 s_rBrowseUrlHandlers.push_back(handler);
1533 return Success();
1534 }
1535
registerRBrowseFileHandler(const RBrowseFileHandler & handler)1536 Error registerRBrowseFileHandler(const RBrowseFileHandler& handler)
1537 {
1538 s_rBrowseFileHandlers.push_back(handler);
1539 return Success();
1540 }
1541
showUserPrompt(const UserPrompt & userPrompt)1542 UserPrompt::Response showUserPrompt(const UserPrompt& userPrompt)
1543 {
1544 // enque user prompt event
1545 json::Object obj;
1546 obj["type"] = static_cast<int>(userPrompt.type);
1547 obj["caption"] = userPrompt.caption;
1548 obj["message"] = userPrompt.message;
1549 obj["yesLabel"] = userPrompt.yesLabel;
1550 obj["noLabel"] = userPrompt.noLabel;
1551 obj["yesIsDefault"] = userPrompt.yesIsDefault;
1552 obj["includeCancel"] = userPrompt.includeCancel;
1553 ClientEvent userPromptEvent(client_events::kUserPrompt, obj);
1554 rsession::clientEventQueue().add(userPromptEvent);
1555
1556 // wait for user_prompt_completed
1557 json::JsonRpcRequest request;
1558 http_methods::waitForMethod(kUserPromptCompleted,
1559 userPromptEvent,
1560 suspend::disallowSuspend,
1561 &request);
1562
1563 // read the response param
1564 int response;
1565 Error error = json::readParam(request.params, 0, &response);
1566 if (error)
1567 {
1568 LOG_ERROR(error);
1569 return UserPrompt::ResponseCancel;
1570 }
1571
1572 // return response (don't cast so that we can make sure the integer
1573 // returned matches one of enumerated type values and warn otherwise)
1574 switch (response)
1575 {
1576 case UserPrompt::ResponseYes:
1577 return UserPrompt::ResponseYes;
1578
1579 case UserPrompt::ResponseNo:
1580 return UserPrompt::ResponseNo;
1581
1582 case UserPrompt::ResponseCancel:
1583 return UserPrompt::ResponseCancel;
1584
1585 default:
1586 LOG_WARNING_MESSAGE("Unexpected user prompt response: " +
1587 boost::lexical_cast<std::string>(response));
1588
1589 return UserPrompt::ResponseCancel;
1590 };
1591 }
1592
saveWorkspaceAction()1593 int saveWorkspaceAction()
1594 {
1595 // allow project override
1596 const projects::ProjectContext& projContext = projects::projectContext();
1597 if (projContext.hasProject())
1598 {
1599 switch(projContext.config().saveWorkspace)
1600 {
1601 case r_util::YesValue:
1602 return rstudio::r::session::kSaveActionSave;
1603 case r_util::NoValue:
1604 return rstudio::r::session::kSaveActionNoSave;
1605 case r_util::AskValue:
1606 return rstudio::r::session::kSaveActionAsk;
1607 default:
1608 // fall through
1609 break;
1610 }
1611 }
1612
1613 // no project override, read from settings
1614 std::string action = prefs::userPrefs().saveWorkspace();
1615 if (action == kSaveWorkspaceAlways)
1616 return rstudio::r::session::kSaveActionSave;
1617 else if (action == kSaveWorkspaceNever)
1618 return rstudio::r::session::kSaveActionNoSave;
1619 else if (action == kSaveWorkspaceAsk)
1620 return rstudio::r::session::kSaveActionAsk;
1621
1622 return rstudio::r::session::kSaveActionAsk;
1623 }
1624
syncRSaveAction()1625 void syncRSaveAction()
1626 {
1627 rstudio::r::session::setSaveAction(saveWorkspaceOption());
1628 }
1629
1630 } // namespace module_context
1631 } // namespace session
1632 } // namespace rstudio
1633
1634 namespace {
1635
sessionExitFailure(const core::Error & error,const core::ErrorLocation & location)1636 int sessionExitFailure(const core::Error& error,
1637 const core::ErrorLocation& location)
1638 {
1639 if (error)
1640 core::log::logError(error, location);
1641 return EXIT_FAILURE;
1642 }
1643
1644
ctypeEnvName()1645 std::string ctypeEnvName()
1646 {
1647 if (!core::system::getenv("LC_ALL").empty())
1648 return "LC_ALL";
1649 if (!core::system::getenv("LC_CTYPE").empty())
1650 return "LC_CTYPE";
1651 if (!core::system::getenv("LANG").empty())
1652 return "LANG";
1653 return "LC_CTYPE";
1654 }
1655
1656 /*
1657 If possible, we want to coerce the character set to UTF-8.
1658 We can't do this by directly calling setlocale because R
1659 will override those settings when it starts up. Instead we
1660 set the corresponding environment variables, which R will
1661 use.
1662
1663 The exception is Win32, which doesn't allow UTF-8 to be used
1664 as an ANSI codepage.
1665
1666 Returns false if we tried and failed to set the charset to
1667 UTF-8, either because we didn't recognize the locale string
1668 format or because the system didn't accept our new locale
1669 string.
1670 */
ensureUtf8Charset()1671 bool ensureUtf8Charset()
1672 {
1673 #if _WIN32
1674 return true;
1675 #else
1676 std::string name = ctypeEnvName();
1677 std::string ctype = core::system::getenv(name);
1678
1679 if (regex_utils::search(ctype, boost::regex("UTF-8$")))
1680 return true;
1681
1682 #if __APPLE__
1683 // For Mac, we attempt to do the fixup in DesktopMain.cpp. If it didn't
1684 // work, let's not do anything rash here--just let the UTF-8 warning show.
1685 return false;
1686 #else
1687
1688 std::string newCType;
1689 if (ctype.empty() || ctype == "C" || ctype == "POSIX")
1690 {
1691 newCType = "en_US.UTF-8";
1692 }
1693 else
1694 {
1695 using namespace boost;
1696
1697 smatch match;
1698 if (regex_utils::match(ctype, match, regex("(\\w+_\\w+)(\\.[^@]+)?(@.+)?")))
1699 {
1700 // Try to replace the charset while keeping everything else the same.
1701 newCType = match[1] + ".UTF-8" + match[3];
1702 }
1703 }
1704
1705 if (!newCType.empty())
1706 {
1707 if (setlocale(LC_CTYPE, newCType.c_str()))
1708 {
1709 core::system::setenv(name, newCType);
1710 setlocale(LC_CTYPE, "");
1711 return true;
1712 }
1713 }
1714
1715 return false;
1716 #endif
1717 #endif
1718 }
1719
1720 // io_service for performing monitor work on the thread
1721 boost::asio::io_service s_monitorIoService;
1722
monitorWorkerThreadFunc()1723 void monitorWorkerThreadFunc()
1724 {
1725 boost::asio::io_service::work work(s_monitorIoService);
1726 s_monitorIoService.run();
1727 }
1728
stopMonitorWorkerThread()1729 void stopMonitorWorkerThread()
1730 {
1731 s_monitorIoService.stop();
1732 }
1733
initMonitorClient()1734 void initMonitorClient()
1735 {
1736 if (!options().getBoolOverlayOption(kLauncherSessionOption))
1737 {
1738 monitor::initializeMonitorClient(core::system::getenv(kMonitorSocketPathEnvVar),
1739 options().monitorSharedSecret(),
1740 s_monitorIoService);
1741 }
1742 else
1743 {
1744 modules::overlay::initMonitorClient(s_monitorIoService);
1745 }
1746
1747 // start the monitor work thread
1748 // we handle monitor calls in a separate thread to ensure that calls
1749 // to the monitor (which are likely across machines and thus very expensive)
1750 // do not hamper the liveliness of the session as a whole
1751 core::thread::safeLaunchThread(monitorWorkerThreadFunc);
1752 }
1753
1754 } // anonymous namespace
1755
1756 // run session
main(int argc,char * const argv[])1757 int main(int argc, char * const argv[])
1758 {
1759 try
1760 {
1761 // save fallback library path
1762 s_fallbackLibraryPath = core::system::getenv("RSTUDIO_FALLBACK_LIBRARY_PATH");
1763
1764 // sleep on startup if requested (mainly for debugging)
1765 std::string sleepOnStartup = core::system::getenv("RSTUDIO_SESSION_SLEEP_ON_STARTUP");
1766 if (!sleepOnStartup.empty())
1767 {
1768 int sleepDuration = core::safe_convert::stringTo<int>(sleepOnStartup, 0);
1769 if (sleepDuration > 0)
1770 {
1771 boost::this_thread::sleep(boost::posix_time::seconds(sleepDuration));
1772 }
1773 }
1774
1775 // terminate immediately with given exit code (for testing/debugging)
1776 std::string exitOnStartup = core::system::getenv("RSTUDIO_SESSION_EXIT_ON_STARTUP");
1777 if (!exitOnStartup.empty())
1778 {
1779 int exitCode = core::safe_convert::stringTo<int>(exitOnStartup, EXIT_FAILURE);
1780
1781 std::cerr << "RSession terminating with exit code " << exitCode << " as requested.\n";
1782 std::cout << "RSession will now exit.\n";
1783 return core::safe_convert::stringTo<int>(exitOnStartup, EXIT_FAILURE);
1784 }
1785
1786 // initialize log so we capture all errors including ones which occur
1787 // reading the config file (if we are in desktop mode then the log
1788 // will get re-initialized below)
1789 std::string programId = "rsession-" + core::system::username();
1790 core::log::setProgramId(programId);
1791 core::system::initializeLog(programId, core::log::LogLevel::WARN);
1792
1793 // ignore SIGPIPE
1794 Error error = core::system::ignoreSignal(core::system::SigPipe);
1795 if (error)
1796 LOG_ERROR(error);
1797
1798 // move to own process group
1799 #ifndef _WIN32
1800 #if defined(__FreeBSD__)
1801 ::setpgrp(0, 0);
1802 #else
1803 ::setpgrp();
1804 #endif
1805 #endif
1806
1807 // get main thread id (used to distinguish forks which occur
1808 // from the main thread vs. child threads)
1809 main_process::initThreadId();
1810
1811 // ensure LANG and UTF-8 character set
1812 #ifndef _WIN32
1813 r_util::ensureLang();
1814 #endif
1815 s_printCharsetWarning = !ensureUtf8Charset();
1816
1817 // remove DYLD_INSERT_LIBRARIES variable (injected on macOS Desktop
1818 // to support restrictions with hardened runtime)
1819 #ifdef __APPLE__
1820 core::system::unsetenv("DYLD_INSERT_LIBRARIES");
1821 #endif
1822
1823 // fix up HOME / R_USER environment variables
1824 // (some users on Windows report these having trailing
1825 // slashes, which confuses a number of RStudio routines)
1826 boost::regex reTrailing("[/\\\\]+$");
1827 for (const std::string& envvar : {"HOME", "R_USER"})
1828 {
1829 std::string oldVal = core::system::getenv(envvar);
1830 if (!oldVal.empty())
1831 {
1832 std::string newVal = boost::regex_replace(oldVal, reTrailing, "");
1833 if (oldVal != newVal)
1834 core::system::setenv(envvar, newVal);
1835 }
1836 }
1837
1838 // read program options
1839 std::ostringstream osWarnings;
1840 Options& options = rsession::options();
1841 ProgramStatus status = options.read(argc, argv, osWarnings);
1842 std::string optionsWarnings = osWarnings.str();
1843
1844 if (!optionsWarnings.empty())
1845 program_options::reportWarnings(optionsWarnings, ERROR_LOCATION);
1846
1847 if (status.exit())
1848 return status.exitCode();
1849
1850 // print version if requested
1851 if (options.version())
1852 {
1853 std::string gitCommit(RSTUDIO_GIT_COMMIT);
1854 std::cout << RSTUDIO_VERSION ", \"" RSTUDIO_RELEASE_NAME "\" "
1855 "(" << gitCommit.substr(0, 8) << ", " RSTUDIO_BUILD_DATE ") "
1856 "for " RSTUDIO_PACKAGE_OS << std::endl;
1857 return 0;
1858 }
1859
1860 // convenience flags for server and desktop mode
1861 bool desktopMode = options.programMode() == kSessionProgramModeDesktop;
1862 bool serverMode = options.programMode() == kSessionProgramModeServer;
1863
1864 // catch unhandled exceptions
1865 core::crash_handler::ProgramMode mode = serverMode ?
1866 core::crash_handler::ProgramMode::Server :
1867 core::crash_handler::ProgramMode::Desktop;
1868 error = core::crash_handler::initialize(mode);
1869 if (error)
1870 LOG_ERROR(error);
1871
1872 // set program mode environment variable so any child processes
1873 // (like rpostback) can determine what the program mode is
1874 core::system::setenv(kRStudioProgramMode, options.programMode());
1875
1876 // reflect stderr logging
1877 if (options.logStderr())
1878 log::addLogDestination(
1879 std::shared_ptr<log::ILogDestination>(new log::StderrLogDestination(
1880 core::system::generateShortenedUuid(),
1881 log::LogLevel::WARN,
1882 log::LogMessageFormatType::PRETTY)));
1883
1884 // initialize monitor but stop its thread on exit
1885 initMonitorClient();
1886 BOOST_SCOPE_EXIT(void)
1887 {
1888 stopMonitorWorkerThread();
1889 }
1890 BOOST_SCOPE_EXIT_END
1891
1892 // register monitor log writer (but not in standalone or verify installation mode)
1893 if (!options.standalone() && !options.verifyInstallation())
1894 {
1895 core::log::addLogDestination(
1896 monitor::client().createLogDestination(core::system::generateShortenedUuid(), log::LogLevel::WARN, options.programIdentity()));
1897 }
1898
1899 // initialize file lock config
1900 FileLock::initialize(desktopMode ? FileLock::LOCKTYPE_ADVISORY : FileLock::LOCKTYPE_LINKBASED);
1901
1902 // re-initialize log for desktop mode
1903 if (desktopMode)
1904 {
1905 if (options.verifyInstallation())
1906 {
1907 core::system::initializeStderrLog(options.programIdentity(),
1908 core::log::LogLevel::WARN);
1909 }
1910 else
1911 {
1912 core::system::initializeLog(options.programIdentity(),
1913 core::log::LogLevel::WARN,
1914 options.userLogPath());
1915 }
1916 }
1917
1918 // initialize overlay
1919 error = rsession::overlay::initialize();
1920 if (error)
1921 return sessionExitFailure(error, ERROR_LOCATION);
1922
1923 // set the rstudio environment variable so code can check for
1924 // whether rstudio is running
1925 core::system::setenv("RSTUDIO", "1");
1926
1927 // Mirror the R getOptions("width") value in an environment variable
1928 core::system::setenv("RSTUDIO_CONSOLE_WIDTH",
1929 safe_convert::numberToString(rstudio::r::options::kDefaultWidth));
1930
1931 // set the rstudio user identity environment variable (can differ from
1932 // username in debug configurations). this is provided so that
1933 // rpostback knows what local stream to connect back to
1934 core::system::setenv(kRStudioUserIdentity, options.userIdentity());
1935 if (desktopMode)
1936 {
1937 // do the same for port number, for rpostback in rdesktop configs
1938 core::system::setenv(kRSessionPortNumber, options.wwwPort());
1939 }
1940
1941 // provide session stream for postback in server mode
1942 if (serverMode)
1943 {
1944 r_util::SessionContext context = options.sessionContext();
1945 std::string stream = r_util::sessionContextFile(context);
1946 core::system::setenv(kRStudioSessionStream, stream);
1947 }
1948
1949 // set the standalone port if we are running in standalone mode
1950 if (options.standalone())
1951 {
1952 core::system::setenv(kRSessionStandalonePortNumber, options.wwwPort());
1953 }
1954
1955 // ensure we aren't being started as a low (priviliged) account
1956 if (serverMode &&
1957 !options.verifyInstallation() &&
1958 core::system::currentUserIsPrivilleged(options.authMinimumUserId()))
1959 {
1960 Error error = systemError(boost::system::errc::permission_denied,
1961 ERROR_LOCATION);
1962 boost::format fmt("User '%1%' has id %2%, which is lower than the "
1963 "minimum user id of %3% (this is controlled by "
1964 "the the auth-minimum-user-id rserver option)");
1965 std::string msg = boost::str(fmt % core::system::username()
1966 % core::system::effectiveUserId()
1967 % options.authMinimumUserId());
1968 error.addProperty("message", msg);
1969 return sessionExitFailure(error, ERROR_LOCATION);
1970 }
1971
1972 #ifdef RSTUDIO_SERVER
1973 if (serverMode)
1974 {
1975 Error error = core::system::crypto::rsaInit();
1976 if (error)
1977 LOG_ERROR(error);
1978 }
1979 #endif
1980
1981 // start the file monitor
1982 core::system::file_monitor::initialize();
1983
1984 // initialize client event queue. this must be done very early
1985 // in main so that any other code which needs to enque an event
1986 // has access to the queue
1987 rsession::initializeClientEventQueue();
1988
1989 // detect parent termination
1990 if (desktopMode)
1991 core::thread::safeLaunchThread(detectParentTermination);
1992
1993 // set the rpostback absolute path
1994 FilePath rpostback = options.rpostbackPath()
1995 .getParent().getParent()
1996 .completeChildPath("rpostback");
1997 core::system::setenv(
1998 "RS_RPOSTBACK_PATH",
1999 string_utils::utf8ToSystem(rpostback.getAbsolutePath()));
2000
2001 std::string firstProjectPath = "";
2002 if (!options.verifyInstallation())
2003 {
2004 // Validate the config and data directories.
2005 core::system::xdg::verifyUserDirs();
2006
2007 // determine if this is a new user and get the first project path if so
2008 bool newUser = false;
2009
2010 FilePath userScratchPath = options.userScratchPath();
2011 if (userScratchPath.exists())
2012 {
2013 // if the lists directory has not yet been created,
2014 // this is a new user
2015 FilePath listsPath = userScratchPath.completeChildPath("monitored/lists");
2016 if (!listsPath.exists())
2017 newUser = true;
2018 }
2019 else
2020 {
2021 // create the scratch path
2022 error = userScratchPath.ensureDirectory();
2023 if (error)
2024 return sessionExitFailure(error, ERROR_LOCATION);
2025
2026 newUser = true;
2027 }
2028
2029 if (newUser)
2030 {
2031 // this is a brand new user
2032 // check to see if there is a first project template
2033 if (!options.firstProjectTemplatePath().empty())
2034 {
2035 // copy the project template to the user's home dir
2036 FilePath templatePath = FilePath(options.firstProjectTemplatePath());
2037 if (templatePath.exists())
2038 {
2039 error = templatePath.copyDirectoryRecursive(
2040 options.userHomePath().completeChildPath(
2041 templatePath.getFilename()));
2042 if (error)
2043 LOG_ERROR(error);
2044 else
2045 {
2046 FilePath firstProjPath = options.userHomePath().completeChildPath(templatePath.getFilename())
2047 .completeChildPath(templatePath.getFilename() + ".Rproj");
2048 if (firstProjPath.exists())
2049 firstProjectPath = firstProjPath.getAbsolutePath();
2050 else
2051 LOG_WARNING_MESSAGE("Could not find first project path " + firstProjPath.getAbsolutePath() +
2052 ". Please ensure the template contains an Rproj file.");
2053 }
2054 }
2055 }
2056 }
2057 }
2058
2059 // initialize user preferences and state
2060 error = prefs::initializePrefs();
2061 if (error)
2062 return sessionExitFailure(error, ERROR_LOCATION);
2063 error = prefs::initializeState();
2064 if (error)
2065 return sessionExitFailure(error, ERROR_LOCATION);
2066
2067 // startup projects -- must be after userSettings is initialized
2068 // but before persistentState and setting working directory
2069 projects::startup(firstProjectPath);
2070
2071 // initialize persistent state
2072 error = rsession::persistentState().initialize();
2073 if (error)
2074 return sessionExitFailure(error, ERROR_LOCATION);
2075
2076 // set working directory
2077 FilePath workingDir = dirs::getInitialWorkingDirectory();
2078 error = workingDir.makeCurrentPath();
2079 if (error)
2080 return sessionExitFailure(error, ERROR_LOCATION);
2081
2082 // override the active session's working directory
2083 // it is created with the default value of ~, so if our session options
2084 // have specified that a different directory should be used, we should
2085 // persist the value to the session state as soon as possible
2086 module_context::activeSession().setWorkingDir(workingDir.getAbsolutePath());
2087
2088 // start http connection listener
2089 error = waitWithTimeout(
2090 http_methods::startHttpConnectionListenerWithTimeout, 0, 100, 1);
2091 if (error)
2092 return sessionExitFailure(error, ERROR_LOCATION);
2093
2094 // start session proxy to route traffic to localhost-listening applications (like Shiny)
2095 // this has to come after regular overlay initialization as it depends on persistent state
2096 error = overlay::initializeSessionProxy();
2097 if (error)
2098 return sessionExitFailure(error, ERROR_LOCATION);
2099
2100 // run optional preflight script -- needs to be after the http listeners
2101 // so the proxy server sees that we have startup up
2102 error = runPreflightScript();
2103 if (error)
2104 return sessionExitFailure(error, ERROR_LOCATION);
2105
2106 // server-only user file/directory initialization
2107 if (serverMode)
2108 {
2109 // r profile file
2110 ensureRProfile();
2111
2112 // public folder
2113 ensurePublicFolder();
2114
2115 // ensure the user has an R library directory
2116 if (!options.rLibsUser().empty())
2117 ensureRLibsUser(options.userHomePath(), options.rLibsUser());
2118 }
2119
2120 // we've gotten through startup so let's log a start event
2121 using namespace monitor;
2122 client().logEvent(Event(kSessionScope, kSessionStartEvent));
2123
2124 // install home and doc dir overrides if requested (for debugger mode)
2125 if (!options.rHomeDirOverride().empty())
2126 core::system::setenv("R_HOME", options.rHomeDirOverride());
2127 if (!options.rDocDirOverride().empty())
2128 core::system::setenv("R_DOC_DIR", options.rDocDirOverride());
2129
2130 // set ANSI support environment variable before running code from .Rprofile
2131 modules::console::syncConsoleColorEnv();
2132
2133 // r options
2134 rstudio::r::session::ROptions rOptions;
2135 rOptions.userHomePath = options.userHomePath();
2136 rOptions.userScratchPath = options.userScratchPath();
2137 rOptions.scopedScratchPath = module_context::scopedScratchPath();
2138 rOptions.sessionScratchPath = module_context::sessionScratchPath();
2139 rOptions.logPath = options.userLogPath();
2140 rOptions.sessionPort = options.wwwPort();
2141 rOptions.startupEnvironmentFilePath = getStartupEnvironmentFilePath();
2142 rOptions.rEnvironmentDir = boost::bind(dirs::rEnvironmentDir);
2143 rOptions.rHistoryDir = boost::bind(dirs::rHistoryDir);
2144 rOptions.alwaysSaveHistory = boost::bind(alwaysSaveHistoryOption);
2145 rOptions.rSourcePath = options.coreRSourcePath();
2146 if (!desktopMode) // ignore r-libs-user in desktop mode
2147 rOptions.rLibsUser = options.rLibsUser();
2148
2149 // name of the source of the CRAN repo value (for logging)
2150 std::string source;
2151
2152 // CRAN repos configuration follows; order of precedence is:
2153 //
2154 // 1. The user's personal preferences file (rstudio-prefs.json), if CRAN repo editing is
2155 // permitted by policy
2156 // 2. The system-wide preferences file (rstudio-prefs.json)
2157 // 3. The repo settings specified in the loaded version of R
2158 // 4. The session's repo settings (in rsession.conf/repos.conf)
2159 // 5. The server's repo settings
2160 // 6. The default repo settings from the preferences schema (user-prefs-schema.json)
2161 // 7. If all else fails, cran.rstudio.com
2162 std::string layerName;
2163 auto val = prefs::userPrefs().readValue(kCranMirror, &layerName);
2164 if (val && ((options.allowCRANReposEdit() && layerName == kUserPrefsUserLayer) ||
2165 layerName == kUserPrefsSystemLayer))
2166 {
2167 // If we got here, either (a) there is a user value and the user is allowed to set their
2168 // own CRAN repos, or (b) there's a system value, which we always respect
2169 rOptions.rCRANUrl = prefs::userPrefs().getCRANMirror().url;
2170 rOptions.rCRANSecondary = prefs::userPrefs().getCRANMirror().secondary;
2171 source = layerName + "-level preference file";
2172 }
2173 else if (!core::system::getenv("RSTUDIO_R_REPO").empty())
2174 {
2175 // repo was specified in the r version
2176 std::string repo = core::system::getenv("RSTUDIO_R_REPO");
2177
2178 // the repo can either be a repos.conf-style file, or a URL
2179 FilePath reposFile(repo);
2180 if (reposFile.exists())
2181 {
2182 std::string reposConfig = Options::parseReposConfig(reposFile);
2183 loadCranRepos(reposConfig, &rOptions);
2184 source = reposFile.getAbsolutePath() + " via RSTUDIO_R_REPO environment variable";
2185 }
2186 else
2187 {
2188 rOptions.rCRANUrl = repo;
2189 source = "RSTUDIO_R_REPO environment variable";
2190 }
2191 }
2192 else if (!options.rCRANMultipleRepos().empty())
2193 {
2194 // repo was specified in a repos file
2195 loadCranRepos(options.rCRANMultipleRepos(), &rOptions);
2196 source = "repos file";
2197 }
2198 else if (!options.rCRANUrl().empty())
2199 {
2200 // Global server option
2201 rOptions.rCRANUrl = options.rCRANUrl();
2202 source = "global session option";
2203 }
2204 else if (val && layerName == kUserPrefsDefaultLayer)
2205 {
2206 // If we found defaults in the prefs schema, use them.
2207 rOptions.rCRANUrl = prefs::userPrefs().getCRANMirror().url;
2208 rOptions.rCRANSecondary = prefs::userPrefs().getCRANMirror().secondary;
2209 source = "preference defaults";
2210 }
2211 else
2212 {
2213 // Hard-coded repo of last resort so we don't wind up without a repo setting (users will
2214 // not be able to install packages without one)
2215 rOptions.rCRANUrl = "https://cran.rstudio.com/";
2216 source = "hard-coded default";
2217 }
2218
2219 LOG_INFO_MESSAGE("Set CRAN URL for session to '" + rOptions.rCRANUrl + "' (source: " +
2220 source + ")");
2221
2222 rOptions.useInternet2 = prefs::userPrefs().useInternet2();
2223 rOptions.rCompatibleGraphicsEngineVersion =
2224 options.rCompatibleGraphicsEngineVersion();
2225 rOptions.serverMode = serverMode;
2226 rOptions.autoReloadSource = options.autoReloadSource();
2227 rOptions.restoreWorkspace = restoreWorkspaceOption();
2228 rOptions.saveWorkspace = saveWorkspaceOption();
2229 if (options.rRestoreWorkspace() != kRestoreWorkspaceDefault)
2230 {
2231 // if workspace restore is set to a non-default option, apply it to
2232 // environment restoration as well (the intent of the option is usually
2233 // to recover a session with an overhelming or problematic environment)
2234 rOptions.restoreEnvironmentOnResume =
2235 options.rRestoreWorkspace() == kRestoreWorkspaceYes;
2236 }
2237 rOptions.disableRProfileOnStart = disableExecuteRprofile();
2238 rOptions.rProfileOnResume = serverMode &&
2239 prefs::userPrefs().runRprofileOnResume();
2240 rOptions.packratEnabled = persistentState().settings().getBool("packratEnabled");
2241 rOptions.sessionScope = options.sessionScope();
2242 rOptions.runScript = options.runScript();
2243 rOptions.suspendOnIncompleteStatement = options.suspendOnIncompleteStatement();
2244
2245 // r callbacks
2246 rstudio::r::session::RCallbacks rCallbacks;
2247 rCallbacks.init = rInit;
2248 rCallbacks.initComplete = rInitComplete;
2249 rCallbacks.consoleRead = console_input::rConsoleRead;
2250 rCallbacks.editFile = rEditFile;
2251 rCallbacks.showFile = rShowFile;
2252 rCallbacks.chooseFile = rChooseFile;
2253 rCallbacks.busy = rBusy;
2254 rCallbacks.consoleWrite = rConsoleWrite;
2255 rCallbacks.consoleHistoryReset = rConsoleHistoryReset;
2256 rCallbacks.locator = rLocator;
2257 rCallbacks.deferredInit = rDeferredInit;
2258 rCallbacks.suspended = rSuspended;
2259 rCallbacks.resumed = rResumed;
2260 rCallbacks.handleUnsavedChanges = rHandleUnsavedChanges;
2261 rCallbacks.quit = rQuit;
2262 rCallbacks.suicide = rSuicide;
2263 rCallbacks.cleanup = rCleanup;
2264 rCallbacks.browseURL = rBrowseURL;
2265 rCallbacks.browseFile = rBrowseFile;
2266 rCallbacks.showHelp = rShowHelp;
2267 rCallbacks.showMessage = rShowMessage;
2268 rCallbacks.serialization = rSerialization;
2269
2270 // set test callback if enabled
2271 if (options.runTests())
2272 rCallbacks.runTests = rRunTests;
2273
2274 // run r (does not return, terminates process using exit)
2275 error = rstudio::r::session::run(rOptions, rCallbacks);
2276 if (error)
2277 {
2278 // this is logically equivilant to R_Suicide
2279 logExitEvent(Event(kSessionScope, kSessionSuicideEvent));
2280
2281 // return failure
2282 return sessionExitFailure(error, ERROR_LOCATION);
2283 }
2284
2285 // return success for good form
2286 return EXIT_SUCCESS;
2287 }
2288 CATCH_UNEXPECTED_EXCEPTION
2289
2290 // if we got this far we had an unexpected exception
2291 return EXIT_FAILURE;
2292 }
2293