1 /*
2  * SessionModuleContext.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 "SessionModuleContextInternal.hpp"
17 
18 #include <vector>
19 
20 #include <boost/assert.hpp>
21 #include <boost/utility.hpp>
22 #include <boost/format.hpp>
23 #include <boost/numeric/conversion/cast.hpp>
24 
25 #include <core/BoostSignals.hpp>
26 #include <core/BoostThread.hpp>
27 #include <shared_core/Error.hpp>
28 #include <shared_core/FilePath.hpp>
29 #include <core/FileInfo.hpp>
30 #include <core/Log.hpp>
31 #include <core/Base64.hpp>
32 #include <shared_core/Hash.hpp>
33 #include <core/Settings.hpp>
34 #include <core/DateTime.hpp>
35 #include <core/FileSerializer.hpp>
36 #include <core/markdown/Markdown.hpp>
37 #include <core/system/FileScanner.hpp>
38 #include <core/IncrementalCommand.hpp>
39 #include <core/PeriodicCommand.hpp>
40 #include <core/collection/Tree.hpp>
41 
42 #include <core/http/Util.hpp>
43 
44 #include <core/system/Process.hpp>
45 #include <core/system/FileMonitor.hpp>
46 #include <core/system/FileChangeEvent.hpp>
47 #include <core/system/Environment.hpp>
48 #include <core/system/ShellUtils.hpp>
49 #include <core/system/Xdg.hpp>
50 
51 #include <core/r_util/RPackageInfo.hpp>
52 
53 #include <r/RSexp.hpp>
54 #include <r/RUtil.hpp>
55 #include <r/RExec.hpp>
56 #include <r/RRoutines.hpp>
57 #include <r/RJson.hpp>
58 #include <r/RJsonRpc.hpp>
59 #include <r/RSourceManager.hpp>
60 #include <r/RErrorCategory.hpp>
61 #include <r/RFunctionHook.hpp>
62 #include <r/session/RSession.hpp>
63 #include <r/session/RConsoleActions.hpp>
64 
65 #include <session/SessionOptions.hpp>
66 #include <session/SessionPersistentState.hpp>
67 #include <session/SessionClientEvent.hpp>
68 #include <session/SessionClientEventService.hpp>
69 
70 #include <session/http/SessionRequest.hpp>
71 
72 #include "SessionRpc.hpp"
73 #include "SessionClientEventQueue.hpp"
74 #include "SessionMainProcess.hpp"
75 
76 #include <session/projects/SessionProjects.hpp>
77 
78 #include <session/SessionConstants.hpp>
79 #include <session/SessionContentUrls.hpp>
80 #include <session/SessionQuarto.hpp>
81 
82 #include <session/prefs/UserPrefs.hpp>
83 #include <session/prefs/UserState.hpp>
84 
85 #include <shared_core/system/User.hpp>
86 
87 #include "modules/SessionBreakpoints.hpp"
88 #include "modules/SessionFiles.hpp"
89 #include "modules/SessionReticulate.hpp"
90 #include "modules/SessionVCS.hpp"
91 
92 #include "session-config.h"
93 
94 using namespace rstudio::core;
95 
96 namespace rstudio {
97 namespace session {
98 namespace module_context {
99 
100 namespace {
101 
102 // simple service for handling console_input rpc requests
103 class ConsoleInputService : boost::noncopyable
104 {
105 public:
106 
ConsoleInputService()107    ConsoleInputService()
108    {
109       core::thread::safeLaunchThread(
110                boost::bind(&ConsoleInputService::run, this),
111                &thread_);
112    }
113 
~ConsoleInputService()114    ~ConsoleInputService()
115    {
116       enqueue("!");
117    }
118 
enqueue(const std::string & input)119    void enqueue(const std::string& input)
120    {
121       requests_.enque(input);
122    }
123 
124 private:
125 
run()126    void run()
127    {
128       while (true)
129       {
130          std::string input;
131          while (requests_.deque(&input))
132          {
133             if (input == "!")
134                return;
135 
136             core::http::Response response;
137             Error error = session::http::sendSessionRequest(
138                      "/rpc/console_input",
139                      input,
140                      &response);
141             if (error)
142                LOG_ERROR(error);
143          }
144 
145          requests_.wait();
146       }
147    }
148 
149    boost::thread thread_;
150    core::thread::ThreadsafeQueue<std::string> requests_;
151 };
152 
consoleInputService()153 ConsoleInputService& consoleInputService()
154 {
155    static ConsoleInputService instance;
156    return instance;
157 }
158 
159 // enqueClientEvent from R
rs_enqueClientEvent(SEXP nameSEXP,SEXP dataSEXP)160 SEXP rs_enqueClientEvent(SEXP nameSEXP, SEXP dataSEXP)
161 {
162    try
163    {
164       // ignore forked sessions
165       if (main_process::wasForked())
166          return R_NilValue;
167 
168       // extract name
169       std::string name = r::sexp::asString(nameSEXP);
170 
171       // extract json value (for primitive types we only support scalars
172       // since this is the most common type of event data). to return an
173       // array of primitives you need to wrap them in a list/object
174       Error extractError;
175       json::Value data;
176       switch(TYPEOF(dataSEXP))
177       {
178          case NILSXP:
179          {
180             // do nothing, data will be a null json value
181             break;
182          }
183          case VECSXP:
184          {
185             extractError = r::json::jsonValueFromList(dataSEXP, &data);
186             break;
187          }
188          default:
189          {
190             extractError = r::json::jsonValueFromScalar(dataSEXP, &data);
191             break;
192          }
193       }
194 
195       // check for error
196       if (extractError)
197       {
198          LOG_ERROR(extractError);
199          throw r::exec::RErrorException(
200                                         "Couldn't extract json value from event data");
201       }
202 
203       // determine the event type from the event name
204       int type = -1;
205       if (name == "package_status_changed")
206          type = session::client_events::kPackageStatusChanged;
207       else if (name == "unhandled_error")
208          type = session::client_events::kUnhandledError;
209       else if (name == "enable_rstudio_connect")
210          type = session::client_events::kEnableRStudioConnect;
211       else if (name == "shiny_gadget_dialog")
212          type = session::client_events::kShinyGadgetDialog;
213       else if (name == "rmd_params_ready")
214          type = session::client_events::kRmdParamsReady;
215       else if (name == "jump_to_function")
216          type = session::client_events::kJumpToFunction;
217       else if (name == "send_to_console")
218          type = session::client_events::kSendToConsole;
219       else if (name == "rprof_started")
220         type = session::client_events::kRprofStarted;
221       else if (name == "rprof_stopped")
222         type = session::client_events::kRprofStopped;
223       else if (name == "rprof_created")
224         type = session::client_events::kRprofCreated;
225       else if (name == "editor_command")
226          type = session::client_events::kEditorCommand;
227       else if (name == "navigate_shiny_frame")
228          type = session::client_events::kNavigateShinyFrame;
229       else if (name == "update_new_connection_dialog")
230          type = session::client_events::kUpdateNewConnectionDialog;
231       else if (name == "terminal_subprocs")
232          type = session::client_events::kTerminalSubprocs;
233       else if (name == "rstudioapi_show_dialog")
234          type = session::client_events::kRStudioAPIShowDialog;
235       else if (name == "object_explorer_event")
236          type = session::client_events::kObjectExplorerEvent;
237       else if (name == "send_to_terminal")
238          type = session::client_events::kSendToTerminal;
239       else if (name == "clear_terminal")
240          type = session::client_events::kClearTerminal;
241       else if (name == "add_terminal")
242          type = session::client_events::kAddTerminal;
243       else if (name == "activate_terminal")
244          type = session::client_events::kActivateTerminal;
245       else if (name == "terminal_cwd")
246          type = session::client_events::kTerminalCwd;
247       else if (name == "remove_terminal")
248          type = session::client_events::kRemoveTerminal;
249       else if (name == "show_page_viewer")
250          type = session::client_events::kShowPageViewerEvent;
251       else if (name == "data_output_completed")
252          type = session::client_events::kDataOutputCompleted;
253       else if (name == "new_document_with_code")
254          type = session::client_events::kNewDocumentWithCode;
255       else if (name == "available_packages_ready")
256          type = session::client_events::kAvailablePackagesReady;
257       else if (name == "compute_theme_colors")
258          type = session::client_events::kComputeThemeColors;
259       else if (name == "tutorial_command")
260          type = session::client_events::kTutorialCommand;
261       else if (name == "tutorial_launch")
262          type = session::client_events::kTutorialLaunch;
263       else if (name == "reticulate_event")
264          type = session::client_events::kReticulateEvent;
265       else if (name == "environment_assigned")
266          type = session::client_events::kEnvironmentAssigned;
267       else if (name == "environment_refresh")
268          type = session::client_events::kEnvironmentRefresh;
269       else if (name == "environment_removed")
270          type = session::client_events::kEnvironmentRemoved;
271       else if (name == "environment_changed")
272          type = session::client_events::kEnvironmentChanged;
273       else if (name == "command_callbacks_changed")
274          type = session::client_events::kCommandCallbacksChanged;
275       else if (name == "console_activate")
276          type = session::client_events::kConsoleActivate;
277 
278       if (type != -1)
279       {
280          ClientEvent event(type, data);
281          session::clientEventQueue().add(event);
282       }
283       else
284       {
285          LOG_ERROR_MESSAGE("Unexpected event name from R: " + name);
286       }
287    }
288    catch (r::exec::RErrorException& e)
289    {
290       r::exec::error(e.message());
291    }
292    CATCH_UNEXPECTED_EXCEPTION
293 
294    return R_NilValue;
295 }
296 
rs_activatePane(SEXP paneSEXP)297 SEXP rs_activatePane(SEXP paneSEXP)
298 {
299    module_context::activatePane(r::sexp::safeAsString(paneSEXP));
300    return R_NilValue;
301 }
302 
303 // show error message from R
rs_showErrorMessage(SEXP titleSEXP,SEXP messageSEXP)304 SEXP rs_showErrorMessage(SEXP titleSEXP, SEXP messageSEXP)
305 {
306    std::string title = r::sexp::asString(titleSEXP);
307    std::string message = r::sexp::asString(messageSEXP);
308    module_context::showErrorMessage(title, message);
309    return R_NilValue;
310 }
311 
312 // log error message from R
rs_logErrorMessage(SEXP messageSEXP)313 SEXP rs_logErrorMessage(SEXP messageSEXP)
314 {
315    std::string message = r::sexp::asString(messageSEXP);
316    LOG_ERROR_MESSAGE(message);
317    return R_NilValue;
318 }
319 
320 // log warning message from R
rs_logWarningMessage(SEXP messageSEXP)321 SEXP rs_logWarningMessage(SEXP messageSEXP)
322 {
323    std::string message = r::sexp::asString(messageSEXP);
324    LOG_WARNING_MESSAGE(message);
325    return R_NilValue;
326 }
327 
328 // sleep the main thread (debugging function used to test rpc/abort)
rs_threadSleep(SEXP secondsSEXP)329 SEXP rs_threadSleep(SEXP secondsSEXP)
330 {
331    int seconds = r::sexp::asInteger(secondsSEXP);
332    boost::this_thread::sleep(boost::posix_time::seconds(seconds));
333    return R_NilValue;
334 }
335 
336 // get rstudio mode
rs_rstudioProgramMode()337 SEXP rs_rstudioProgramMode()
338 {
339    r::sexp::Protect rProtect;
340    return r::sexp::create(session::options().programMode(), &rProtect);
341 }
342 
343 // get rstudio edition
rs_rstudioEdition()344 SEXP rs_rstudioEdition()
345 {
346    return R_NilValue;
347 }
348 
349 // get version
rs_rstudioVersion()350 SEXP rs_rstudioVersion()
351 {
352    std::string numericVersion(RSTUDIO_VERSION_MAJOR);
353    numericVersion.append(".")
354       .append(RSTUDIO_VERSION_MINOR).append(".")
355       .append(RSTUDIO_VERSION_PATCH).append(".")
356       .append(boost::regex_replace(
357          std::string(RSTUDIO_VERSION_SUFFIX),
358          boost::regex("[a-zA-Z\\-+]"),
359          ""));
360 
361    r::sexp::Protect rProtect;
362    return r::sexp::create(numericVersion, &rProtect);
363 }
364 
365 // get long form version
rs_rstudioLongVersion()366 SEXP rs_rstudioLongVersion()
367 {
368    r::sexp::Protect rProtect;
369    return r::sexp::create(std::string(RSTUDIO_VERSION), &rProtect);
370 }
371 
372 // get release name
rs_rstudioReleaseName()373 SEXP rs_rstudioReleaseName()
374 {
375    r::sexp::Protect rProtect;
376    return r::sexp::create(std::string(RSTUDIO_RELEASE_NAME), &rProtect);
377 }
378 
379 // get citation
rs_rstudioCitation()380 SEXP rs_rstudioCitation()
381 {
382    FilePath resPath = session::options().rResourcesPath();
383    FilePath citationPath = resPath.completeChildPath("CITATION");
384 
385    // the citation file may not exist when working in e.g.
386    // development configurations so just ignore if it's missing
387    if (!citationPath.exists())
388       return R_NilValue;
389 
390    SEXP citationSEXP;
391    r::sexp::Protect rProtect;
392    Error error = r::exec::RFunction("utils:::readCitationFile",
393                                     citationPath.getAbsolutePath())
394                                                    .call(&citationSEXP,
395                                                          &rProtect);
396 
397    if (error)
398    {
399       LOG_ERROR(error);
400       return R_NilValue;
401    }
402    else
403    {
404       return citationSEXP;
405    }
406 }
407 
rs_setUsingMingwGcc49(SEXP usingSEXP)408 SEXP rs_setUsingMingwGcc49(SEXP usingSEXP)
409 {
410    bool usingMingwGcc49 = r::sexp::asLogical(usingSEXP);
411    prefs::userState().setUsingMingwGcc49(usingMingwGcc49);
412    return R_NilValue;
413 }
414 
415 // ensure file hidden
rs_ensureFileHidden(SEXP fileSEXP)416 SEXP rs_ensureFileHidden(SEXP fileSEXP)
417 {
418 #ifdef _WIN32
419    std::string file = r::sexp::asString(fileSEXP);
420    if (!file.empty())
421    {
422       FilePath filePath = module_context::resolveAliasedPath(file);
423       Error error = core::system::makeFileHidden(filePath);
424       if (error)
425          LOG_ERROR(error);
426    }
427 #endif
428 
429    return R_NilValue;
430 }
431 
rs_sourceDiagnostics()432 SEXP rs_sourceDiagnostics()
433 {
434    module_context::sourceDiagnostics();
435    return R_NilValue;
436 }
437 
rs_packageLoaded(SEXP pkgnameSEXP)438 SEXP rs_packageLoaded(SEXP pkgnameSEXP)
439 {
440    if (main_process::wasForked())
441       return R_NilValue;
442 
443    std::string pkgname = r::sexp::safeAsString(pkgnameSEXP);
444 
445    // fire server event
446    events().onPackageLoaded(pkgname);
447 
448    // fire client event
449    ClientEvent packageLoadedEvent(
450             client_events::kPackageLoaded,
451             json::Value(pkgname));
452    enqueClientEvent(packageLoadedEvent);
453 
454    return R_NilValue;
455 }
456 
rs_packageUnloaded(SEXP pkgnameSEXP)457 SEXP rs_packageUnloaded(SEXP pkgnameSEXP)
458 {
459    if (main_process::wasForked())
460       return R_NilValue;
461 
462    std::string pkgname = r::sexp::safeAsString(pkgnameSEXP);
463    ClientEvent packageUnloadedEvent(
464             client_events::kPackageUnloaded,
465             json::Value(pkgname));
466    enqueClientEvent(packageUnloadedEvent);
467 
468    return R_NilValue;
469 }
470 
rs_userPrompt(SEXP typeSEXP,SEXP captionSEXP,SEXP messageSEXP,SEXP yesLabelSEXP,SEXP noLabelSEXP,SEXP includeCancelSEXP,SEXP yesIsDefaultSEXP)471 SEXP rs_userPrompt(SEXP typeSEXP,
472                    SEXP captionSEXP,
473                    SEXP messageSEXP,
474                    SEXP yesLabelSEXP,
475                    SEXP noLabelSEXP,
476                    SEXP includeCancelSEXP,
477                    SEXP yesIsDefaultSEXP)
478 {
479    UserPrompt prompt(r::sexp::asInteger(typeSEXP),
480                      r::sexp::safeAsString(captionSEXP),
481                      r::sexp::safeAsString(messageSEXP),
482                      r::sexp::safeAsString(yesLabelSEXP),
483                      r::sexp::safeAsString(noLabelSEXP),
484                      r::sexp::asLogical(includeCancelSEXP),
485                      r::sexp::asLogical(yesIsDefaultSEXP));
486 
487    UserPrompt::Response response = showUserPrompt(prompt);
488 
489    r::sexp::Protect rProtect;
490    return r::sexp::create(response, &rProtect);
491 }
492 
rs_restartR(SEXP afterRestartSEXP)493 SEXP rs_restartR(SEXP afterRestartSEXP)
494 {
495    std::string afterRestart = r::sexp::safeAsString(afterRestartSEXP);
496    json::Object dataJson;
497    dataJson["after_restart"] = afterRestart;
498    ClientEvent event(client_events::kSuspendAndRestart, dataJson);
499    module_context::enqueClientEvent(event);
500    return R_NilValue;
501 }
502 
rs_generateShortUuid()503 SEXP rs_generateShortUuid()
504 {
505    // generate a short uuid -- we make this available in R code so that it's
506    // possible to create random identifiers without perturbing the state of the
507    // RNG that R uses
508    std::string uuid = core::system::generateShortenedUuid();
509    r::sexp::Protect rProtect;
510    return r::sexp::create(uuid, &rProtect);
511 }
512 
rs_markdownToHTML(SEXP contentSEXP)513 SEXP rs_markdownToHTML(SEXP contentSEXP)
514 {
515    std::string content = r::sexp::safeAsString(contentSEXP);
516    std::string htmlContent;
517    Error error = markdown::markdownToHTML(content,
518                                           markdown::Extensions(),
519                                           markdown::HTMLOptions(),
520                                           &htmlContent);
521    if (error)
522    {
523       LOG_ERROR(error);
524       htmlContent = content;
525    }
526 
527    r::sexp::Protect rProtect;
528    return r::sexp::create(htmlContent, &rProtect);
529 }
530 
persistantValueName(SEXP nameSEXP)531 inline std::string persistantValueName(SEXP nameSEXP)
532 {
533    return "rstudioapi_persistent_values_" + r::sexp::safeAsString(nameSEXP);
534 }
535 
rs_setPersistentValue(SEXP nameSEXP,SEXP valueSEXP)536 SEXP rs_setPersistentValue(SEXP nameSEXP, SEXP valueSEXP)
537 {
538    std::string name = persistantValueName(nameSEXP);
539    std::string value = r::sexp::safeAsString(valueSEXP);
540    persistentState().settings().set(name, value);
541    return R_NilValue;
542 }
543 
rs_getPersistentValue(SEXP nameSEXP)544 SEXP rs_getPersistentValue(SEXP nameSEXP)
545 {
546    std::string name = persistantValueName(nameSEXP);
547    if (persistentState().settings().contains(name))
548    {
549       std::string value = persistentState().settings().get(name);
550       r::sexp::Protect rProtect;
551       return r::sexp::create(value, &rProtect);
552    }
553    else
554    {
555       return R_NilValue;
556    }
557 }
558 
rs_setRpcDelay(SEXP delayMsSEXP)559 SEXP rs_setRpcDelay(SEXP delayMsSEXP)
560 {
561    int delayMs = r::sexp::asInteger(delayMsSEXP);
562    rstudio::session::rpc::setRpcDelay(delayMs);
563    return delayMsSEXP;
564 }
565 
566 // Force background processing to occur; used during testing since the user is never idle during
567 // headless test execution
rs_performBackgroundProcessing(SEXP isIdle)568 SEXP rs_performBackgroundProcessing(SEXP isIdle)
569 {
570    onBackgroundProcessing(r::sexp::asLogical(isIdle));
571    return R_NilValue;
572 }
573 
574 } // anonymous namespace
575 
576 
577 // register a scratch path which is monitored
578 
579 namespace {
580 
581 typedef std::map<FilePath,OnFileChange> MonitoredScratchPaths;
582 MonitoredScratchPaths s_monitoredScratchPaths;
583 bool s_monitorByScanning = false;
584 
monitoredParentPath()585 FilePath monitoredParentPath()
586 {
587    FilePath monitoredPath = userScratchPath().completePath(kMonitoredPath);
588    Error error = monitoredPath.ensureDirectory();
589    if (error)
590       LOG_ERROR(error);
591    return monitoredPath;
592 }
593 
monitoredScratchFilter(const FileInfo & fileInfo)594 bool monitoredScratchFilter(const FileInfo& fileInfo)
595 {
596    return true;
597 }
598 
599 
onFilesChanged(const std::vector<core::system::FileChangeEvent> & changes)600 void onFilesChanged(const std::vector<core::system::FileChangeEvent>& changes)
601 {
602    for (const core::system::FileChangeEvent& fileChange : changes)
603    {
604       FilePath changedFilePath(fileChange.fileInfo().absolutePath());
605       for (MonitoredScratchPaths::const_iterator
606                it = s_monitoredScratchPaths.begin();
607                it != s_monitoredScratchPaths.end();
608                ++it)
609       {
610          if (changedFilePath.isWithin(it->first))
611          {
612             it->second(fileChange);
613             break;
614          }
615       }
616 
617    }
618 }
619 
monitoredPathTree()620 boost::shared_ptr<tree<FileInfo> > monitoredPathTree()
621 {
622    boost::shared_ptr<tree<FileInfo> > pMonitoredTree(new tree<FileInfo>());
623    core::system::FileScannerOptions options;
624    options.recursive = true;
625    options.filter = monitoredScratchFilter;
626    Error scanError = scanFiles(FileInfo(monitoredParentPath()),
627                                options,
628                                pMonitoredTree.get());
629    if (scanError)
630       LOG_ERROR(scanError);
631 
632    return pMonitoredTree;
633 }
634 
scanForMonitoredPathChanges(boost::shared_ptr<tree<FileInfo>> pPrevTree)635 bool scanForMonitoredPathChanges(boost::shared_ptr<tree<FileInfo> > pPrevTree)
636 {
637    // check for changes
638    std::vector<core::system::FileChangeEvent> changes;
639    boost::shared_ptr<tree<FileInfo> > pCurrentTree = monitoredPathTree();
640    core::system::collectFileChangeEvents(pPrevTree->begin(),
641                                          pPrevTree->end(),
642                                          pCurrentTree->begin(),
643                                          pCurrentTree->end(),
644                                          &changes);
645 
646    // fire events
647    onFilesChanged(changes);
648 
649    // reset the tree
650    *pPrevTree = *pCurrentTree;
651 
652    // scan again after interval
653    return true;
654 }
655 
onMonitoringError(const Error & error)656 void onMonitoringError(const Error& error)
657 {
658    // log the error
659    LOG_ERROR(error);
660 
661    // fallback to periodically scanning for changes
662    if (!s_monitorByScanning)
663    {
664       s_monitorByScanning = true;
665       module_context::schedulePeriodicWork(
666          boost::posix_time::seconds(3),
667          boost::bind(scanForMonitoredPathChanges, monitoredPathTree()),
668          true);
669    }
670 }
671 
initializeMonitoredUserScratchDir()672 void initializeMonitoredUserScratchDir()
673 {
674    // setup callbacks and register
675    core::system::file_monitor::Callbacks cb;
676    cb.onRegistrationError = onMonitoringError;
677    cb.onMonitoringError = onMonitoringError;
678    cb.onFilesChanged = onFilesChanged;
679    core::system::file_monitor::registerMonitor(
680                                     monitoredParentPath(),
681                                     true,
682                                     monitoredScratchFilter,
683                                     cb);
684 }
685 
686 
687 } // anonymous namespace
688 
registerMonitoredUserScratchDir(const std::string & dirName,const OnFileChange & onFileChange)689 FilePath registerMonitoredUserScratchDir(const std::string& dirName,
690                                          const OnFileChange& onFileChange)
691 {
692    // create the subdir
693    FilePath dirPath = monitoredParentPath().completePath(dirName);
694    Error error = dirPath.ensureDirectory();
695    if (error)
696       LOG_ERROR(error);
697 
698    // register the path
699    s_monitoredScratchPaths[dirPath] = onFileChange;
700 
701    // return it
702    return dirPath;
703 }
704 
705 
706 namespace {
707 
708 // manage signals used for custom save and restore
709 class SuspendHandlers : boost::noncopyable
710 {
711 public:
SuspendHandlers()712    SuspendHandlers() : nextGroup_(0) {}
713 
714 public:
add(const SuspendHandler & handler)715    void add(const SuspendHandler& handler)
716    {
717       int group = nextGroup_++;
718       suspendSignal_.connect(group, handler.suspend());
719       resumeSignal_.connect(group, handler.resume());
720    }
721 
suspend(const r::session::RSuspendOptions & options,Settings * pSettings)722    void suspend(const r::session::RSuspendOptions& options,
723                 Settings* pSettings)
724    {
725       suspendSignal_(options, pSettings);
726    }
727 
resume(const Settings & settings)728    void resume(const Settings& settings)
729    {
730       resumeSignal_(settings);
731    }
732 
733 private:
734 
735    // use groups to ensure signal order. call suspend handlers in order
736    // of subscription and call resume handlers in reverse order of
737    // subscription.
738 
739    int nextGroup_;
740 
741    RSTUDIO_BOOST_SIGNAL<void(const r::session::RSuspendOptions&,Settings*),
742                  RSTUDIO_BOOST_LAST_VALUE<void>,
743                  int,
744                  std::less<int> > suspendSignal_;
745 
746    RSTUDIO_BOOST_SIGNAL<void(const Settings&),
747                  RSTUDIO_BOOST_LAST_VALUE<void>,
748                  int,
749                  std::greater<int> > resumeSignal_;
750 };
751 
752 // handlers instance
suspendHandlers()753 SuspendHandlers& suspendHandlers()
754 {
755    static SuspendHandlers instance;
756    return instance;
757 }
758 
759 } // anonymous namespace
760 
addSuspendHandler(const SuspendHandler & handler)761 void addSuspendHandler(const SuspendHandler& handler)
762 {
763    suspendHandlers().add(handler);
764 }
765 
onSuspended(const r::session::RSuspendOptions & options,Settings * pPersistentState)766 void onSuspended(const r::session::RSuspendOptions& options,
767                  Settings* pPersistentState)
768 {
769    pPersistentState->beginUpdate();
770    suspendHandlers().suspend(options, pPersistentState);
771    pPersistentState->endUpdate();
772 
773 }
774 
onResumed(const Settings & persistentState)775 void onResumed(const Settings& persistentState)
776 {
777    suspendHandlers().resume(persistentState);
778 }
779 
780 // idle work
781 
782 namespace {
783 
784 typedef std::vector<boost::shared_ptr<ScheduledCommand> >
785                                                       ScheduledCommands;
786 ScheduledCommands s_scheduledCommands;
787 ScheduledCommands s_idleScheduledCommands;
788 
addScheduledCommand(boost::shared_ptr<ScheduledCommand> pCommand,bool idleOnly)789 void addScheduledCommand(boost::shared_ptr<ScheduledCommand> pCommand,
790                          bool idleOnly)
791 {
792    if (idleOnly)
793       s_idleScheduledCommands.push_back(pCommand);
794    else
795       s_scheduledCommands.push_back(pCommand);
796 }
797 
executeScheduledCommands(ScheduledCommands * pCommands)798 void executeScheduledCommands(ScheduledCommands* pCommands)
799 {
800    // make a copy of scheduled commands before executing them
801    // (this is because a scheduled command could result in
802    // R code executing which in turn could cause the list of
803    // scheduled commands to be mutated and these iterators
804    // invalidated)
805    ScheduledCommands commands = *pCommands;
806 
807    // execute all commands
808    std::for_each(commands.begin(),
809                  commands.end(),
810                  boost::bind(&ScheduledCommand::execute, _1));
811 
812    // remove any commands which are finished
813    pCommands->erase(
814                  std::remove_if(
815                     pCommands->begin(),
816                     pCommands->end(),
817                     boost::bind(&ScheduledCommand::finished, _1)),
818                  pCommands->end());
819 }
820 
821 
822 } // anonymous namespace
823 
scheduleIncrementalWork(const boost::posix_time::time_duration & incrementalDuration,const boost::function<bool ()> & execute,bool idleOnly)824 void scheduleIncrementalWork(
825          const boost::posix_time::time_duration& incrementalDuration,
826          const boost::function<bool()>& execute,
827          bool idleOnly)
828 {
829    addScheduledCommand(boost::shared_ptr<ScheduledCommand>(
830                            new IncrementalCommand(incrementalDuration,
831                                                   execute)),
832                          idleOnly);
833 }
834 
scheduleIncrementalWork(const boost::posix_time::time_duration & initialDuration,const boost::posix_time::time_duration & incrementalDuration,const boost::function<bool ()> & execute,bool idleOnly)835 void scheduleIncrementalWork(
836          const boost::posix_time::time_duration& initialDuration,
837          const boost::posix_time::time_duration& incrementalDuration,
838          const boost::function<bool()>& execute,
839          bool idleOnly)
840 {
841    addScheduledCommand(boost::shared_ptr<ScheduledCommand>(
842                            new IncrementalCommand(initialDuration,
843                                                   incrementalDuration,
844                                                   execute)),
845                            idleOnly);
846 }
847 
848 
schedulePeriodicWork(const boost::posix_time::time_duration & period,const boost::function<bool ()> & execute,bool idleOnly,bool immediate)849 void schedulePeriodicWork(const boost::posix_time::time_duration& period,
850                           const boost::function<bool()> &execute,
851                           bool idleOnly,
852                           bool immediate)
853 {
854    addScheduledCommand(boost::shared_ptr<ScheduledCommand>(
855                            new PeriodicCommand(period, execute, immediate)),
856                        idleOnly);
857 }
858 
859 
860 namespace {
861 
performDelayedWork(const boost::function<void ()> & execute,boost::shared_ptr<bool> pExecuted)862 bool performDelayedWork(const boost::function<void()> &execute,
863                         boost::shared_ptr<bool> pExecuted)
864 {
865    if (*pExecuted)
866       return false;
867 
868    *pExecuted = true;
869 
870    execute();
871 
872    return false;
873 }
874 
isPackagePosixMakefile(const FilePath & srcPath)875 bool isPackagePosixMakefile(const FilePath& srcPath)
876 {
877    if (!srcPath.exists())
878       return false;
879 
880    using namespace projects;
881    ProjectContext& context = session::projects::projectContext();
882    if (!context.hasProject())
883       return false;
884 
885    if (context.config().buildType != r_util::kBuildTypePackage)
886       return false;
887 
888    FilePath parentDir = srcPath.getParent();
889    if (parentDir.getFilename() != "src")
890       return false;
891 
892    FilePath packagePath = context.buildTargetPath();
893    if (parentDir.getParent() != packagePath)
894       return false;
895 
896    std::string filename = srcPath.getFilename();
897    return (filename == "Makevars" ||
898            filename == "Makevars.in" ||
899            filename == "Makefile" ||
900            filename == "Makefile.in");
901 }
902 
performIdleOnlyAsyncRpcMethod(const core::json::JsonRpcRequest & request,const core::json::JsonRpcFunctionContinuation & continuation,const core::json::JsonRpcAsyncFunction & function)903 void performIdleOnlyAsyncRpcMethod(
904       const core::json::JsonRpcRequest& request,
905       const core::json::JsonRpcFunctionContinuation& continuation,
906       const core::json::JsonRpcAsyncFunction& function)
907 {
908    if (request.isBackgroundConnection)
909    {
910       module_context::scheduleDelayedWork(
911           boost::posix_time::milliseconds(100),
912           boost::bind(function, request, continuation),
913           true);
914    }
915    else
916    {
917       function(request, continuation);
918    }
919 }
920 
921 } // anonymous namespeace
922 
scheduleDelayedWork(const boost::posix_time::time_duration & period,const boost::function<void ()> & execute,bool idleOnly)923 void scheduleDelayedWork(const boost::posix_time::time_duration& period,
924                          const boost::function<void()> &execute,
925                          bool idleOnly)
926 {
927    boost::shared_ptr<bool> pExecuted(new bool(false));
928 
929    schedulePeriodicWork(period,
930                         boost::bind(performDelayedWork, execute, pExecuted),
931                         idleOnly,
932                         false);
933 }
934 
935 
onBackgroundProcessing(bool isIdle)936 void onBackgroundProcessing(bool isIdle)
937 {
938    // allow process supervisor to poll for events
939    processSupervisor().poll();
940 
941    // check for file monitor changes
942    core::system::file_monitor::checkForChanges();
943 
944    // fire event
945    events().onBackgroundProcessing(isIdle);
946 
947    // execute incremental commands
948    executeScheduledCommands(&s_scheduledCommands);
949    if (isIdle)
950       executeScheduledCommands(&s_idleScheduledCommands);
951 }
952 
953 #ifdef _WIN32
954 
955 namespace {
956 
consoleCtrlHandler(DWORD type)957 BOOL CALLBACK consoleCtrlHandler(DWORD type)
958 {
959    switch (type)
960    {
961    case CTRL_C_EVENT:
962    case CTRL_BREAK_EVENT:
963       rstudio::r::exec::setInterruptsPending(true);
964       return true;
965    default:
966       return false;
967    }
968 }
969 
970 } // end anonymous namespace
971 
972 #endif
973 
initializeConsoleCtrlHandler()974 void initializeConsoleCtrlHandler()
975 {
976 #ifdef _WIN32
977    // accept Ctrl + C interrupts
978    ::SetConsoleCtrlHandler(nullptr, FALSE);
979 
980    // remove an old registration (if any)
981    ::SetConsoleCtrlHandler(consoleCtrlHandler, FALSE);
982 
983    // register console control handler
984    ::SetConsoleCtrlHandler(consoleCtrlHandler, TRUE);
985 #endif
986 }
987 
registerIdleOnlyAsyncRpcMethod(const std::string & name,const core::json::JsonRpcAsyncFunction & function)988 Error registerIdleOnlyAsyncRpcMethod(
989                              const std::string& name,
990                              const core::json::JsonRpcAsyncFunction& function)
991 {
992    return registerAsyncRpcMethod(name,
993                                  boost::bind(performIdleOnlyAsyncRpcMethod,
994                                                 _1, _2, function));
995 }
996 
lineEndings(const core::FilePath & srcFile)997 core::string_utils::LineEnding lineEndings(const core::FilePath& srcFile)
998 {
999    // potential special case for Makevars
1000    if (prefs::userPrefs().useNewlinesInMakefiles() && isPackagePosixMakefile(srcFile))
1001       return string_utils::LineEndingPosix;
1002 
1003    // get the global default behavior
1004    string_utils::LineEnding lineEndings = prefs::userPrefs().lineEndings();
1005 
1006    // use project-level override if available
1007    using namespace session::projects;
1008    ProjectContext& context = projectContext();
1009    if (context.hasProject())
1010    {
1011       if (context.config().lineEndings != r_util::kLineEndingsUseDefault)
1012          lineEndings = (string_utils::LineEnding)context.config().lineEndings;
1013    }
1014 
1015    // if we are doing no conversion (passthrough) and there is an existing file
1016    // then we need to peek inside it to see what the existing line endings are
1017    if (lineEndings == string_utils::LineEndingPassthrough)
1018       string_utils::detectLineEndings(srcFile, &lineEndings);
1019 
1020    // return computed lineEndings
1021    return lineEndings;
1022 }
1023 
readAndDecodeFile(const FilePath & filePath,const std::string & encoding,bool allowSubstChars,std::string * pContents)1024 Error readAndDecodeFile(const FilePath& filePath,
1025                         const std::string& encoding,
1026                         bool allowSubstChars,
1027                         std::string* pContents)
1028 {
1029    // read contents
1030    std::string encodedContents;
1031    Error error = readStringFromFile(filePath, &encodedContents,
1032                                     options().sourceLineEnding());
1033 
1034    if (error)
1035       return error;
1036 
1037    // convert to UTF-8
1038    return convertToUtf8(encodedContents,
1039                         encoding,
1040                         allowSubstChars,
1041                         pContents);
1042 }
1043 
convertToUtf8(const std::string & encodedContents,const std::string & encoding,bool allowSubstChars,std::string * pContents)1044 Error convertToUtf8(const std::string& encodedContents,
1045                     const std::string& encoding,
1046                     bool allowSubstChars,
1047                     std::string* pContents)
1048 {
1049    Error error;
1050    error = r::util::iconvstr(encodedContents, encoding, "UTF-8",
1051                              allowSubstChars, pContents);
1052    if (error)
1053       return error;
1054 
1055    stripBOM(pContents);
1056 
1057    // Detect invalid UTF-8 sequences and recover
1058    error = string_utils::utf8Clean(pContents->begin(),
1059                                    pContents->end(),
1060                                    '?');
1061    return error;
1062 }
1063 
userHomePath()1064 FilePath userHomePath()
1065 {
1066    return session::options().userHomePath();
1067 }
1068 
createAliasedPath(const FileInfo & fileInfo)1069 std::string createAliasedPath(const FileInfo& fileInfo)
1070 {
1071    return createAliasedPath(FilePath(fileInfo.absolutePath()));
1072 }
1073 
createAliasedPath(const FilePath & path)1074 std::string createAliasedPath(const FilePath& path)
1075 {
1076    return FilePath::createAliasedPath(path, userHomePath());
1077 }
1078 
resolveAliasedPath(const std::string & aliasedPath)1079 FilePath resolveAliasedPath(const std::string& aliasedPath)
1080 {
1081    return FilePath::resolveAliasedPath(aliasedPath, userHomePath());
1082 }
1083 
userScratchPath()1084 FilePath userScratchPath()
1085 {
1086    return session::options().userScratchPath();
1087 }
1088 
userUploadedFilesScratchPath()1089 FilePath userUploadedFilesScratchPath()
1090 {
1091    return session::options().userScratchPath().completeChildPath("uploaded-files");
1092 }
1093 
scopedScratchPath()1094 FilePath scopedScratchPath()
1095 {
1096    if (projects::projectContext().hasProject())
1097       return projects::projectContext().scratchPath();
1098    else
1099       return userScratchPath();
1100 }
1101 
sharedScratchPath()1102 FilePath sharedScratchPath()
1103 {
1104    if (projects::projectContext().hasProject())
1105       return projects::projectContext().sharedScratchPath();
1106    else
1107       return userScratchPath();
1108 }
1109 
sharedProjectScratchPath()1110 FilePath sharedProjectScratchPath()
1111 {
1112    if (projects::projectContext().hasProject())
1113    {
1114       return sharedScratchPath();
1115    }
1116    else
1117    {
1118       return FilePath();
1119    }
1120 }
1121 
sessionScratchPath()1122 FilePath sessionScratchPath()
1123 {
1124    r_util::ActiveSession& active = activeSession();
1125    if (!active.empty())
1126       return active.scratchPath();
1127    else
1128       return scopedScratchPath();
1129 }
1130 
oldScopedScratchPath()1131 FilePath oldScopedScratchPath()
1132 {
1133    if (projects::projectContext().hasProject())
1134       return projects::projectContext().oldScratchPath();
1135    else
1136       return userScratchPath();
1137 }
1138 
rLibsUser()1139 std::string rLibsUser()
1140 {
1141    return core::system::getenv("R_LIBS_USER");
1142 }
1143 
isVisibleUserFile(const FilePath & filePath)1144 bool isVisibleUserFile(const FilePath& filePath)
1145 {
1146    return (filePath.isWithin(module_context::userHomePath()) &&
1147            !filePath.isWithin(module_context::userScratchPath()));
1148 }
1149 
safeCurrentPath()1150 FilePath safeCurrentPath()
1151 {
1152    return FilePath::safeCurrentPath(userHomePath());
1153 }
1154 
tempFile(const std::string & prefix,const std::string & extension)1155 FilePath tempFile(const std::string& prefix, const std::string& extension)
1156 {
1157    return r::session::utils::tempFile(prefix, extension);
1158 }
1159 
tempDir()1160 FilePath tempDir()
1161 {
1162    return r::session::utils::tempDir();
1163 }
1164 
1165 
findProgram(const std::string & name)1166 FilePath findProgram(const std::string& name)
1167 {
1168    // Added isMainThread() test here to allow the console to run offline.
1169    // On starting a terminal, it does 'which svn' and adds that dir to the
1170    // path if it find one
1171    // FUTURE: Can we always use findProgramOnPath? It will be a lot faster since R's
1172    // version seems to fork a process to call the shell's 'which'
1173    // Be careful of Windows there are two copies of the environment so
1174    // R's env might be out of sync with rsession's PATH.
1175    if (!r::exec::isMainThread())
1176    {
1177       FilePath resultPath;
1178       Error error = system::findProgramOnPath(name, &resultPath);
1179       if (error)
1180       {
1181          return FilePath();
1182       }
1183       return resultPath;
1184    }
1185    else
1186    {
1187       std::string which;
1188       FilePath resultPath;
1189 
1190       // For now, going to check both ways and log warnings if they don't match
1191       Error error = r::exec::RFunction("Sys.which", name).call(&which);
1192       Error dbgError = system::findProgramOnPath(name, &resultPath);
1193       if (error)
1194       {
1195          LOG_ERROR(error);
1196          return FilePath();
1197       }
1198       else
1199       {
1200          if (dbgError && which != "")
1201          {
1202             LOG_WARNING_MESSAGE("findProgramOnPath returns error: " + dbgError.asString() + " Sys.which returns: " + resultPath.getAbsolutePath());
1203          }
1204          else if (which != resultPath.getAbsolutePath())
1205          {
1206             LOG_WARNING_MESSAGE("findProgramOnPath returns wrong result: " + which + " != " + resultPath.getAbsolutePath());
1207          }
1208          return FilePath(which);
1209       }
1210    }
1211 }
1212 
addTinytexToPathIfNecessary()1213 bool addTinytexToPathIfNecessary()
1214 {
1215    // avoid some pathological cases where e.g. TinyTeX folder
1216    // exists but doesn't have the pdflatex binary (don't
1217    // attempt to re-add the folder multiple times)
1218    static bool s_added = false;
1219    if (s_added)
1220       return true;
1221 
1222    if (!module_context::findProgram("pdflatex").isEmpty())
1223       return false;
1224 
1225    SEXP binDirSEXP = R_NilValue;
1226    r::sexp::Protect protect;
1227    Error error = r::exec::RFunction(".rs.tinytexBin").call(&binDirSEXP, &protect);
1228    if (error)
1229    {
1230       LOG_ERROR(error);
1231       return false;
1232    }
1233 
1234    if (!r::sexp::isString(binDirSEXP))
1235       return false;
1236 
1237    std::string binDir = r::sexp::asString(binDirSEXP);
1238    FilePath binPath = module_context::resolveAliasedPath(binDir);
1239    if (!binPath.exists())
1240       return false;
1241 
1242    s_added = true;
1243    core::system::addToSystemPath(binPath);
1244    return true;
1245 }
1246 
isPdfLatexInstalled()1247 bool isPdfLatexInstalled()
1248 {
1249    addTinytexToPathIfNecessary();
1250    return !module_context::findProgram("pdflatex").isEmpty();
1251 }
1252 
1253 namespace {
1254 
hasTextMimeType(const FilePath & filePath)1255 bool hasTextMimeType(const FilePath& filePath)
1256 {
1257    std::string mimeType = filePath.getMimeContentType("");
1258    if (mimeType.empty())
1259       return false;
1260 
1261    return boost::algorithm::starts_with(mimeType, "text/") ||
1262           boost::algorithm::ends_with(mimeType, "+xml") ||
1263           boost::algorithm::ends_with(mimeType, "/xml");
1264 }
1265 
hasBinaryMimeType(const FilePath & filePath)1266 bool hasBinaryMimeType(const FilePath& filePath)
1267 {
1268    // screen known text types
1269    if (hasTextMimeType(filePath))
1270       return false;
1271 
1272    std::string mimeType = filePath.getMimeContentType("");
1273    if (mimeType.empty())
1274       return false;
1275 
1276    return boost::algorithm::starts_with(mimeType, "application/") ||
1277           boost::algorithm::starts_with(mimeType, "image/") ||
1278           boost::algorithm::starts_with(mimeType, "audio/") ||
1279           boost::algorithm::starts_with(mimeType, "video/");
1280 }
1281 
isJsonFile(const FilePath & filePath)1282 bool isJsonFile(const FilePath& filePath)
1283 {
1284    std::string mimeType = filePath.getMimeContentType();
1285    return boost::algorithm::ends_with(mimeType, "json");
1286 }
1287 
1288 } // anonymous namespace
1289 
isTextFile(const FilePath & targetPath)1290 bool isTextFile(const FilePath& targetPath)
1291 {
1292    if (hasTextMimeType(targetPath))
1293       return true;
1294 
1295    if (isJsonFile(targetPath))
1296       return true;
1297 
1298    if (hasBinaryMimeType(targetPath))
1299       return false;
1300 
1301    if (targetPath.getSize() == 0)
1302       return true;
1303 
1304 #ifndef _WIN32
1305 
1306    // the behavior of the 'file' command in the macOS High Sierra beta
1307    // changed such that '--mime' no longer ensured that mime-type strings
1308    // were actually emitted. using '-I' instead appears to work around this.
1309 #ifdef __APPLE__
1310    const char * const kMimeTypeArg = "-I";
1311 #else
1312    const char * const kMimeTypeArg = "--mime";
1313 #endif
1314 
1315    core::shell_utils::ShellCommand cmd("file");
1316    cmd << "--dereference";
1317    cmd << kMimeTypeArg;
1318    cmd << "--brief";
1319    cmd << targetPath;
1320    core::system::ProcessResult result;
1321    Error error = core::system::runCommand(cmd,
1322                                           core::system::ProcessOptions(),
1323                                           &result);
1324    if (error)
1325    {
1326       LOG_ERROR(error);
1327       return !!error;
1328    }
1329 
1330    // strip encoding
1331    std::string fileType = boost::algorithm::trim_copy(result.stdOut);
1332    fileType = fileType.substr(0, fileType.find(';'));
1333 
1334    // check value
1335    return boost::algorithm::starts_with(fileType, "text/") ||
1336           boost::algorithm::ends_with(fileType, "+xml") ||
1337           boost::algorithm::ends_with(fileType, "/xml") ||
1338           boost::algorithm::ends_with(fileType, "x-empty") ||
1339           boost::algorithm::equals(fileType, "application/json") ||
1340           boost::algorithm::equals(fileType, "application/postscript");
1341 #else
1342 
1343    // read contents of file
1344    std::string contents;
1345    Error error = core::readStringFromFile(targetPath, &contents);
1346    if (error)
1347    {
1348       LOG_ERROR(error);
1349       return !!error;
1350    }
1351 
1352    // does it have null bytes?
1353    std::string nullBytes;
1354    nullBytes.push_back('\0');
1355    nullBytes.push_back('\0');
1356    return !boost::algorithm::contains(contents, nullBytes);
1357 
1358 #endif
1359 
1360 }
1361 
editFile(const core::FilePath & filePath,int lineNumber)1362 void editFile(const core::FilePath& filePath, int lineNumber)
1363 {
1364    // construct file system item (also tag with mime type) and position
1365    json::Object fileJson = module_context::createFileSystemItem(filePath);
1366    fileJson["mime_type"] = filePath.getMimeContentType();
1367 
1368    json::Value positionJsonValue;
1369    if (lineNumber >= 0)
1370    {
1371       json::Object positionJson;
1372       positionJson["line"] = lineNumber;
1373       positionJson["column"] = 1;
1374       positionJsonValue = positionJson;
1375    }
1376 
1377    // fire event
1378    json::Object eventJson;
1379    eventJson["file"] = fileJson;
1380    eventJson["position"] = positionJsonValue;
1381    ClientEvent event(client_events::kFileEdit, eventJson);
1382    module_context::enqueClientEvent(event);
1383 }
1384 
rBinDir(core::FilePath * pRBinDirPath)1385 Error rBinDir(core::FilePath* pRBinDirPath)
1386 {
1387    std::string rHomeBin;
1388    r::exec::RFunction rHomeBinFunc("R.home", "bin");
1389    Error error = rHomeBinFunc.call(&rHomeBin);
1390    if (error)
1391       return error;
1392 
1393    *pRBinDirPath = FilePath(rHomeBin);
1394    return Success();
1395 }
1396 
1397 
rScriptPath(FilePath * pRScriptPath)1398 Error rScriptPath(FilePath* pRScriptPath)
1399 {
1400    FilePath rHomeBinPath;
1401    Error error = rBinDir(&rHomeBinPath);
1402    if (error)
1403       return error;
1404 
1405 #ifdef _WIN32
1406 *pRScriptPath = rHomeBinPath.completePath("Rterm.exe");
1407 #else
1408 *pRScriptPath = rHomeBinPath.completePath("R");
1409 #endif
1410    return Success();
1411 }
1412 
rCmd(const core::FilePath & rBinDir)1413 shell_utils::ShellCommand rCmd(const core::FilePath& rBinDir)
1414 {
1415 #ifdef _WIN32
1416       return shell_utils::ShellCommand(rBinDir.completeChildPath("Rcmd.exe"));
1417 #else
1418       shell_utils::ShellCommand rCmd(rBinDir.completeChildPath("R"));
1419       rCmd << "CMD";
1420       return rCmd;
1421 #endif
1422 }
1423 
1424 // get the R local help port
rLocalHelpPort()1425 std::string rLocalHelpPort()
1426 {
1427    std::string port;
1428    Error error = r::exec::RFunction(".rs.httpdPort").call(&port);
1429    if (error)
1430       LOG_ERROR(error);
1431    return port;
1432 }
1433 
getLibPaths()1434 std::vector<FilePath> getLibPaths()
1435 {
1436    std::vector<std::string> libPathsString;
1437    r::exec::RFunction rfLibPaths(".libPaths");
1438    Error error = rfLibPaths.call(&libPathsString);
1439    if (error)
1440       LOG_ERROR(error);
1441 
1442    std::vector<FilePath> libPaths;
1443    for (const std::string& path : libPathsString)
1444    {
1445       libPaths.push_back(module_context::resolveAliasedPath(path));
1446    }
1447 
1448    return libPaths;
1449 }
1450 
disablePackages()1451 bool disablePackages()
1452 {
1453    return !core::system::getenv("RSTUDIO_DISABLE_PACKAGES").empty();
1454 }
1455 
1456 // check if a package is installed
isPackageInstalled(const std::string & packageName)1457 bool isPackageInstalled(const std::string& packageName)
1458 {
1459    r::session::utils::SuppressOutputInScope suppressOutput;
1460 
1461    bool installed = false;
1462    r::exec::RFunction func(".rs.isPackageInstalled", packageName);
1463    Error error = func.call(&installed);
1464    return !error ? installed : false;
1465 }
1466 
isPackageVersionInstalled(const std::string & packageName,const std::string & version)1467 bool isPackageVersionInstalled(const std::string& packageName,
1468                                const std::string& version)
1469 {
1470    r::session::utils::SuppressOutputInScope suppressOutput;
1471 
1472    bool installed = false;
1473    r::exec::RFunction func(".rs.isPackageVersionInstalled",
1474                            packageName, version);
1475    Error error = func.call(&installed);
1476    return !error ? installed : false;
1477 }
1478 
isMinimumDevtoolsInstalled()1479 bool isMinimumDevtoolsInstalled()
1480 {
1481    return isPackageVersionInstalled("devtools", "1.4.1");
1482 }
1483 
isMinimumRoxygenInstalled()1484 bool isMinimumRoxygenInstalled()
1485 {
1486    return isPackageVersionInstalled("roxygen2", "4.0");
1487 }
1488 
packageVersion(const std::string & packageName)1489 std::string packageVersion(const std::string& packageName)
1490 {
1491    std::string version;
1492    Error error = r::exec::RFunction(".rs.packageVersionString")
1493          .addParam(packageName)
1494          .call(&version);
1495 
1496    if (error)
1497    {
1498       LOG_ERROR(error);
1499       return "(Unknown)";
1500    }
1501    else
1502    {
1503       return version;
1504    }
1505 }
1506 
packageVersion(const std::string & packageName,core::Version * pVersion)1507 Error packageVersion(const std::string& packageName,
1508                      core::Version* pVersion)
1509 {
1510    std::string version;
1511    Error error = r::exec::RFunction(".rs.packageVersionString")
1512          .addParam(packageName)
1513          .call(&version);
1514 
1515    if (error)
1516       return error;
1517 
1518    *pVersion = Version(version);
1519    return Success();
1520 }
1521 
hasMinimumRVersion(const std::string & version)1522 bool hasMinimumRVersion(const std::string& version)
1523 {
1524    bool hasVersion = false;
1525    boost::format fmt("getRversion() >= '%1%'");
1526    std::string versionTest = boost::str(fmt % version);
1527    Error error = r::exec::evaluateString(versionTest, &hasVersion);
1528    if (error)
1529       LOG_ERROR(error);
1530    return hasVersion;
1531 }
1532 
getPackageCompatStatus(const std::string & packageName,const std::string & packageVersion,int protocolVersion)1533 PackageCompatStatus getPackageCompatStatus(
1534       const std::string& packageName,
1535       const std::string& packageVersion,
1536       int protocolVersion)
1537 {
1538    r::session::utils::SuppressOutputInScope suppressOutput;
1539    int compatStatus = COMPAT_UNKNOWN;
1540    r::exec::RFunction func(".rs.getPackageCompatStatus",
1541                            packageName, packageVersion, protocolVersion);
1542    Error error = func.call(&compatStatus);
1543    if (error)
1544    {
1545       LOG_ERROR(error);
1546       return COMPAT_UNKNOWN;
1547    }
1548    return static_cast<PackageCompatStatus>(compatStatus);
1549 }
1550 
installPackage(const std::string & pkgPath,const std::string & libPath)1551 Error installPackage(const std::string& pkgPath, const std::string& libPath)
1552 {
1553    // get R bin directory
1554    FilePath rBinDir;
1555    Error error = module_context::rBinDir(&rBinDir);
1556    if (error)
1557       return error;
1558 
1559    // setup options and command
1560    core::system::ProcessOptions options;
1561 #ifdef _WIN32
1562    shell_utils::ShellCommand installCommand(rBinDir.completeChildPath("R.exe"));
1563 #else
1564    shell_utils::ShellCommand installCommand(rBinDir.completeChildPath("R"));
1565 #endif
1566 
1567    installCommand << core::shell_utils::EscapeFilesOnly;
1568 
1569    // for packrat projects we execute the profile and set the working
1570    // directory to the project directory; for other contexts we just
1571    // propagate the R_LIBS
1572    if (module_context::packratContext().modeOn)
1573    {
1574       options.workingDir = projects::projectContext().directory();
1575    }
1576    else
1577    {
1578       installCommand << "--vanilla";
1579       core::system::Options env;
1580       core::system::environment(&env);
1581       std::string libPaths = libPathsString();
1582       if (!libPaths.empty())
1583          core::system::setenv(&env, "R_LIBS", libPathsString());
1584       options.environment = env;
1585    }
1586 
1587    installCommand << "CMD" << "INSTALL";
1588 
1589    // if there is a lib path then provide it
1590    if (!libPath.empty())
1591    {
1592       installCommand << "-l";
1593       installCommand << "\"" + libPath + "\"";
1594    }
1595 
1596    // add pakage path
1597    installCommand << "\"" + pkgPath + "\"";
1598    core::system::ProcessResult result;
1599 
1600    // run the command
1601    error = core::system::runCommand(installCommand,
1602                                     options,
1603                                     &result);
1604 
1605    if (error)
1606       return error;
1607 
1608    if ((result.exitStatus != EXIT_SUCCESS) && !result.stdErr.empty())
1609    {
1610       return systemError(boost::system::errc::state_not_recoverable,
1611                          "Error installing package: " + result.stdErr,
1612                          ERROR_LOCATION);
1613    }
1614 
1615    return Success();
1616 }
1617 
1618 
packageNameForSourceFile(const core::FilePath & sourceFilePath)1619 std::string packageNameForSourceFile(const core::FilePath& sourceFilePath)
1620 {
1621    // check whether we are in a package
1622    FilePath sourceDir = sourceFilePath.getParent();
1623    if (sourceDir.getFilename() == "R" &&
1624        r_util::isPackageDirectory(sourceDir.getParent()))
1625    {
1626       r_util::RPackageInfo pkgInfo;
1627       Error error = pkgInfo.read(sourceDir.getParent());
1628       if (error)
1629       {
1630          LOG_ERROR(error);
1631          return std::string();
1632       }
1633 
1634       return pkgInfo.name();
1635    }
1636    else
1637    {
1638       return std::string();
1639    }
1640 }
1641 
isUnmonitoredPackageSourceFile(const FilePath & filePath)1642 bool isUnmonitoredPackageSourceFile(const FilePath& filePath)
1643 {
1644    // if it's in the current package then it's fine
1645    using namespace projects;
1646    if (projectContext().hasProject() &&
1647       (projectContext().config().buildType == r_util::kBuildTypePackage) &&
1648        filePath.isWithin(projectContext().buildTargetPath()))
1649    {
1650       return false;
1651    }
1652 
1653    // ensure we are dealing with a directory
1654    FilePath dir = filePath;
1655    if (!dir.isDirectory())
1656       dir = filePath.getParent();
1657 
1658    // see if one the file's parent directories has a DESCRIPTION
1659    while (!dir.isEmpty())
1660    {
1661       FilePath descPath = dir.completeChildPath("DESCRIPTION");
1662       if (descPath.exists())
1663       {
1664          // get path relative to package dir
1665          std::string relative = filePath.getRelativePath(dir);
1666          if (boost::algorithm::starts_with(relative, "R/") ||
1667              boost::algorithm::starts_with(relative, "src/") ||
1668              boost::algorithm::starts_with(relative, "inst/include/"))
1669          {
1670             return true;
1671          }
1672          else
1673          {
1674             return false;
1675          }
1676       }
1677 
1678       dir = dir.getParent();
1679    }
1680 
1681    return false;
1682 }
1683 
1684 
rs_packageNameForSourceFile(SEXP sourceFilePathSEXP)1685 SEXP rs_packageNameForSourceFile(SEXP sourceFilePathSEXP)
1686 {
1687    r::sexp::Protect protect;
1688    FilePath sourceFilePath = module_context::resolveAliasedPath(r::sexp::asString(sourceFilePathSEXP));
1689    return r::sexp::create(packageNameForSourceFile(sourceFilePath), &protect);
1690 }
1691 
rs_base64encode(SEXP dataSEXP,SEXP binarySEXP)1692 SEXP rs_base64encode(SEXP dataSEXP, SEXP binarySEXP)
1693 {
1694    bool binary = r::sexp::asLogical(binarySEXP);
1695    const char* pData;
1696    std::size_t n;
1697 
1698    if (TYPEOF(dataSEXP) == STRSXP)
1699    {
1700       SEXP charSEXP = STRING_ELT(dataSEXP, 0);
1701       pData = CHAR(charSEXP);
1702       n = r::sexp::length(charSEXP);
1703    }
1704    else if (TYPEOF(dataSEXP) == RAWSXP)
1705    {
1706       pData = reinterpret_cast<const char*>(RAW(dataSEXP));
1707       n = r::sexp::length(dataSEXP);
1708    }
1709    else
1710    {
1711       LOG_ERROR_MESSAGE("Unexpected data type");
1712       return R_NilValue;
1713    }
1714 
1715    std::string output;
1716    Error error = base64::encode(pData, n, &output);
1717    if (error)
1718       LOG_ERROR(error);
1719 
1720    r::sexp::Protect protect;
1721    if (binary)
1722       return r::sexp::createRawVector(output, &protect);
1723    else
1724       return r::sexp::create(output, &protect);
1725 }
1726 
rs_base64encodeFile(SEXP pathSEXP)1727 SEXP rs_base64encodeFile(SEXP pathSEXP)
1728 {
1729    std::string path = r::sexp::asString(pathSEXP);
1730    FilePath filePath = module_context::resolveAliasedPath(path);
1731 
1732    std::string output;
1733    Error error = base64::encode(filePath, &output);
1734    if (error)
1735       LOG_ERROR(error);
1736 
1737    r::sexp::Protect protect;
1738    return r::sexp::create(output, &protect);
1739 }
1740 
rs_base64decode(SEXP dataSEXP,SEXP binarySEXP)1741 SEXP rs_base64decode(SEXP dataSEXP, SEXP binarySEXP)
1742 {
1743    bool binary = r::sexp::asLogical(binarySEXP);
1744    const char* pData;
1745    std::size_t n;
1746 
1747    if (TYPEOF(dataSEXP) == STRSXP)
1748    {
1749       SEXP charSEXP = STRING_ELT(dataSEXP, 0);
1750       pData = CHAR(charSEXP);
1751       n = r::sexp::length(charSEXP);
1752    }
1753    else if (TYPEOF(dataSEXP) == RAWSXP)
1754    {
1755       pData = reinterpret_cast<const char*>(RAW(dataSEXP));
1756       n = r::sexp::length(dataSEXP);
1757    }
1758    else
1759    {
1760       LOG_ERROR_MESSAGE("Unexpected data type");
1761       return R_NilValue;
1762    }
1763 
1764    std::string output;
1765    Error error = base64::decode(pData, n, &output);
1766    if (error)
1767       LOG_ERROR(error);
1768 
1769    r::sexp::Protect protect;
1770    if (binary)
1771       return r::sexp::createRawVector(output, &protect);
1772    else
1773       return r::sexp::create(output, &protect);
1774 }
1775 
rs_htmlEscape(SEXP textSEXP,SEXP attributeSEXP)1776 SEXP rs_htmlEscape(SEXP textSEXP, SEXP attributeSEXP)
1777 {
1778    std::string escaped = string_utils::htmlEscape(r::sexp::safeAsString(textSEXP),
1779          r::sexp::asLogical(attributeSEXP));
1780    r::sexp::Protect protect;
1781    return r::sexp::create(escaped, &protect);
1782 }
1783 
rs_resolveAliasedPath(SEXP pathSEXP)1784 SEXP rs_resolveAliasedPath(SEXP pathSEXP)
1785 {
1786    std::string path = r::sexp::asUtf8String(pathSEXP);
1787    FilePath resolved = module_context::resolveAliasedPath(path);
1788    r::sexp::Protect protect;
1789    return r::sexp::create(resolved.getAbsolutePath(), &protect);
1790 }
1791 
rs_sessionModulePath()1792 SEXP rs_sessionModulePath()
1793 {
1794    r::sexp::Protect protect;
1795    return r::sexp::create(
1796       session::options().modulesRSourcePath().getAbsolutePath(), &protect);
1797 }
1798 
createFileSystemItem(const FileInfo & fileInfo)1799 json::Object createFileSystemItem(const FileInfo& fileInfo)
1800 {
1801    json::Object entry;
1802 
1803    std::string aliasedPath = module_context::createAliasedPath(fileInfo);
1804    std::string rawPath =
1805       module_context::resolveAliasedPath(aliasedPath).getAbsolutePath();
1806 
1807    entry["path"] = aliasedPath;
1808    if (aliasedPath != rawPath)
1809       entry["raw_path"] = rawPath;
1810    entry["dir"] = fileInfo.isDirectory();
1811 
1812    // length requires cast
1813    try
1814    {
1815       entry["length"] = boost::numeric_cast<boost::uint64_t>(fileInfo.size());
1816    }
1817    catch (const boost::bad_numeric_cast& e)
1818    {
1819       LOG_ERROR_MESSAGE(std::string("Error converting file size: ") +
1820                         e.what());
1821       entry["length"] = 0;
1822    }
1823 
1824    entry["exists"] = FilePath(fileInfo.absolutePath()).exists();
1825 
1826    entry["lastModified"] = date_time::millisecondsSinceEpoch(
1827                                                    fileInfo.lastWriteTime());
1828    return entry;
1829 }
1830 
createFileSystemItem(const FilePath & filePath)1831 json::Object createFileSystemItem(const FilePath& filePath)
1832 {
1833    return createFileSystemItem(FileInfo(filePath));
1834 }
1835 
rVersion()1836 std::string rVersion()
1837 {
1838    std::string rVersion;
1839    Error error = rstudio::r::exec::RFunction(".rs.rVersionString").call(
1840                                                                   &rVersion);
1841    if (error)
1842       LOG_ERROR(error);
1843    return rVersion;
1844 }
1845 
rVersionLabel()1846 std::string rVersionLabel()
1847 {
1848    std::string versionLabel = system::getenv("RSTUDIO_R_VERSION_LABEL");
1849    return versionLabel;
1850 }
1851 
rHomeDir()1852 std::string rHomeDir()
1853 {
1854    // get the current R home directory
1855    std::string rVersionHome;
1856    Error error = rstudio::r::exec::RFunction("R.home").call(&rVersionHome);
1857    if (error)
1858       LOG_ERROR(error);
1859    return rVersionHome;
1860 }
1861 
1862 
activeSession()1863 r_util::ActiveSession& activeSession()
1864 {
1865    static boost::shared_ptr<r_util::ActiveSession> pSession;
1866    if (!pSession)
1867    {
1868       std::string id = options().sessionScope().id();
1869       if (!id.empty())
1870          pSession = activeSessions().get(id);
1871       else if (options().programMode() == kSessionProgramModeDesktop)
1872       {
1873          // if no active session, create one and use the launcher token as a
1874          // synthetic session ID
1875          //
1876          // we only do this in desktop mode to preserve backwards compatibility
1877          // with some functionality that depends on session data
1878          // persisting after rstudio has been closed
1879          //
1880          // this entire clause will likely need to be reverted in a future release
1881          // once we ensure that all execution modes have this squared away
1882          pSession = activeSessions().emptySession(options().launcherToken());
1883       }
1884       else
1885       {
1886          // if no scope was specified, we are in singleton session mode
1887          // check to see if there is an existing active session, and use that
1888          std::vector<boost::shared_ptr<r_util::ActiveSession> > sessions =
1889                activeSessions().list(userHomePath(), options().projectSharingEnabled());
1890          if (sessions.size() == 1)
1891          {
1892             // there is only one session, so this must be singleton session mode
1893             // reopen that session
1894             pSession = sessions.front();
1895          }
1896          else
1897          {
1898             // create a new session entry
1899             // this should not really run because in single session mode there will
1900             // only be one session but we'll create one here just in case for safety
1901             std::string sessionId;
1902 
1903             // pass ~ as the default working directory
1904             // this is resolved deeper in the code in SessionClientInit to turn into
1905             // the actual user preference or session default directory that it should be
1906             activeSessions().create(options().sessionScope().project(), "~", &sessionId);
1907             pSession = activeSessions().get(sessionId);
1908          }
1909       }
1910    }
1911    return *pSession;
1912 }
1913 
1914 
activeSessions()1915 r_util::ActiveSessions& activeSessions()
1916 {
1917    static boost::shared_ptr<r_util::ActiveSessions> pSessions;
1918    if (!pSessions)
1919       pSessions.reset(new r_util::ActiveSessions(userScratchPath()));
1920    return *pSessions;
1921 }
1922 
1923 
1924 
libPathsString()1925 std::string libPathsString()
1926 {
1927    // call into R to get the string
1928    std::string libPaths;
1929    Error error = r::exec::RFunction(".rs.libPathsString").call(&libPaths);
1930    if (error)
1931    {
1932       LOG_ERROR(error);
1933       return std::string();
1934    }
1935 
1936    // this is presumably system-encoded, so convert this to utf8 before return
1937    return string_utils::systemToUtf8(libPaths);
1938 }
1939 
sourceModuleRFile(const std::string & rSourceFile)1940 Error sourceModuleRFile(const std::string& rSourceFile)
1941 {
1942    FilePath modulesPath = session::options().modulesRSourcePath();
1943    FilePath srcPath = modulesPath.completePath(rSourceFile);
1944    return r::sourceManager().sourceTools(srcPath);
1945 }
1946 
sourceModuleRFileWithResult(const std::string & rSourceFile,const FilePath & workingDir,core::system::ProcessResult * pResult)1947 Error sourceModuleRFileWithResult(const std::string& rSourceFile,
1948                                   const FilePath& workingDir,
1949                                   core::system::ProcessResult* pResult)
1950 {
1951    // R binary
1952    FilePath rProgramPath;
1953    Error error = module_context::rScriptPath(&rProgramPath);
1954    if (error)
1955       return error;
1956    std::string rBin = string_utils::utf8ToSystem(rProgramPath.getAbsolutePath());
1957 
1958    // vanilla execution of a single expression
1959    std::vector<std::string> args;
1960    args.push_back("--vanilla");
1961    args.push_back("-s");
1962    args.push_back("-e");
1963 
1964    // build source command
1965    boost::format fmt("source('%1%')");
1966    FilePath modulesPath = session::options().modulesRSourcePath();
1967    FilePath srcFilePath = modulesPath.completePath(rSourceFile);
1968    std::string srcPath = core::string_utils::utf8ToSystem(
1969       srcFilePath.getAbsolutePath());
1970    std::string escapedSrcPath = string_utils::jsLiteralEscape(srcPath);
1971    std::string cmd = boost::str(fmt % escapedSrcPath);
1972    args.push_back(cmd);
1973 
1974    // options
1975    core::system::ProcessOptions options;
1976    options.terminateChildren = true;
1977    options.workingDir = workingDir;
1978 
1979    // allow child process to inherit our R_LIBS
1980    core::system::Options childEnv;
1981    core::system::environment(&childEnv);
1982    std::string libPaths = libPathsString();
1983    if (!libPaths.empty())
1984       core::system::setenv(&childEnv, "R_LIBS", libPaths);
1985    options.environment = childEnv;
1986 
1987    // run the child
1988    return core::system::runProgram(rBin, args, "", options, pResult);
1989 }
1990 
1991 
enqueClientEvent(const ClientEvent & event)1992 void enqueClientEvent(const ClientEvent& event)
1993 {
1994    session::clientEventQueue().add(event);
1995 }
1996 
isDirectoryMonitored(const FilePath & directory)1997 bool isDirectoryMonitored(const FilePath& directory)
1998 {
1999    return session::projects::projectContext().isMonitoringDirectory(directory) ||
2000           session::modules::files::isMonitoringDirectory(directory);
2001 }
2002 
isRScriptInPackageBuildTarget(const FilePath & filePath)2003 bool isRScriptInPackageBuildTarget(const FilePath &filePath)
2004 {
2005    using namespace session::projects;
2006 
2007    if (projectContext().config().buildType == r_util::kBuildTypePackage)
2008    {
2009       FilePath pkgPath = projects::projectContext().buildTargetPath();
2010       std::string pkgRelative = filePath.getRelativePath(pkgPath);
2011       return boost::algorithm::starts_with(pkgRelative, "R/");
2012    }
2013    else
2014    {
2015       return false;
2016    }
2017 }
2018 
rs_isRScriptInPackageBuildTarget(SEXP filePathSEXP)2019 SEXP rs_isRScriptInPackageBuildTarget(SEXP filePathSEXP)
2020 {
2021    r::sexp::Protect protect;
2022    FilePath filePath = module_context::resolveAliasedPath(
2023       r::sexp::asUtf8String(filePathSEXP));
2024    return r::sexp::create(isRScriptInPackageBuildTarget(filePath), &protect);
2025 }
2026 
fileListingFilter(const core::FileInfo & fileInfo,bool hideObjectFiles)2027 bool fileListingFilter(const core::FileInfo& fileInfo, bool hideObjectFiles)
2028 {
2029    core::FilePath filePath(fileInfo.absolutePath());
2030 
2031    // Check to see if this is one of the extensions we always show
2032    std::string ext = filePath.getExtensionLowerCase();
2033    core::json::Array exts = prefs::userPrefs().alwaysShownExtensions();
2034    for (json::Value val: exts)
2035    {
2036       if (json::isType<std::string>(val))
2037       {
2038          if (ext == string_utils::toLower(val.getString()))
2039          {
2040             return true;
2041          }
2042       }
2043    }
2044 
2045    // Check to see if this is one of the files we always show
2046    std::string name = filePath.getFilename();
2047    core::json::Array files = prefs::userPrefs().alwaysShownFiles();
2048    for (json::Value val: files)
2049    {
2050       if (json::isType<std::string>(val))
2051       {
2052          if (name == val.getString())
2053          {
2054             return true;
2055          }
2056       }
2057    }
2058 
2059    if (hideObjectFiles &&
2060             (ext == ".o" || ext == ".so" || ext == ".dll") &&
2061             filePath.getParent().getFilename() == "src")
2062    {
2063       return false;
2064    }
2065    else
2066    {
2067       return !filePath.isHidden();
2068    }
2069 }
2070 
2071 namespace {
2072 
2073 // enque file changed event
enqueFileChangedEvent(const core::system::FileChangeEvent & event,boost::shared_ptr<modules::source_control::FileDecorationContext> pCtx)2074 void enqueFileChangedEvent(
2075       const core::system::FileChangeEvent& event,
2076       boost::shared_ptr<modules::source_control::FileDecorationContext> pCtx)
2077 {
2078    // create file change object
2079    json::Object fileChange;
2080    fileChange["type"] = event.type();
2081    json::Object fileSystemItem = createFileSystemItem(event.fileInfo());
2082 
2083    if (prefs::userPrefs().vcsAutorefresh())
2084    {
2085       pCtx->decorateFile(
2086                FilePath(event.fileInfo().absolutePath()),
2087                &fileSystemItem);
2088    }
2089 
2090    fileChange["file"] = fileSystemItem;
2091 
2092    // enque it
2093    ClientEvent clientEvent(client_events::kFileChanged, fileChange);
2094    module_context::enqueClientEvent(clientEvent);
2095 }
2096 
2097 } // namespace
2098 
enqueFileChangedEvent(const core::system::FileChangeEvent & event)2099 void enqueFileChangedEvent(const core::system::FileChangeEvent &event)
2100 {
2101    FilePath filePath = FilePath(event.fileInfo().absolutePath());
2102 
2103    using namespace session::modules::source_control;
2104    auto pCtx = fileDecorationContext(filePath, true);
2105    enqueFileChangedEvent(event, pCtx);
2106 }
2107 
enqueFileChangedEvents(const core::FilePath & vcsStatusRoot,const std::vector<core::system::FileChangeEvent> & events)2108 void enqueFileChangedEvents(const core::FilePath& vcsStatusRoot,
2109                             const std::vector<core::system::FileChangeEvent>& events)
2110 {
2111    using namespace modules::source_control;
2112 
2113    if (events.empty())
2114       return;
2115 
2116    // try to find the common parent of the events
2117    FilePath commonParentPath = FilePath(events.front().fileInfo().absolutePath()).getParent();
2118    for (const core::system::FileChangeEvent& event : events)
2119    {
2120       // if not within the common parent then revert to the vcs status root
2121       if (!FilePath(event.fileInfo().absolutePath()).isWithin(commonParentPath))
2122       {
2123          commonParentPath = vcsStatusRoot;
2124          break;
2125       }
2126    }
2127 
2128    using namespace session::modules::source_control;
2129    auto pCtx = fileDecorationContext(commonParentPath, true);
2130 
2131    // fire client events as necessary
2132    for (const core::system::FileChangeEvent& event : events)
2133    {
2134       enqueFileChangedEvent(event, pCtx);
2135    }
2136 }
2137 
enqueueConsoleInput(const std::string & consoleInput)2138 Error enqueueConsoleInput(const std::string& consoleInput)
2139 {
2140    using namespace r::session;
2141 
2142    // construct our JSON RPC
2143    json::Array jsonParams = RConsoleInput(consoleInput).toJsonArray();
2144 
2145    json::Object jsonRpc;
2146    jsonRpc["method"] = "console_input";
2147    jsonRpc["params"] = jsonParams;
2148    jsonRpc["clientId"] = clientEventService().clientId();
2149 
2150    // serialize for transmission
2151    std::ostringstream oss;
2152    jsonRpc.write(oss);
2153 
2154    // and fire it off
2155    consoleInputService().enqueue(oss.str());
2156 
2157    return Success();
2158 }
2159 
2160 // NOTE: we used to call explicitly back into r::session to write output
2161 // and errors however the fact that these functions are called from
2162 // background threads during std stream capture means that they must
2163 // provide thread safety guarantees. since the r::session module generally
2164 // assumes single-threaded operation it is more straightforward to have
2165 // the code here ape the actions of RWriteConsoleEx with a more explicit
2166 // thread safety constraint
2167 
consoleWriteOutput(const std::string & output)2168 void consoleWriteOutput(const std::string& output)
2169 {
2170    // NOTE: all actions herein must be threadsafe! (see comment above)
2171 
2172    // add console action
2173    r::session::consoleActions().add(kConsoleActionOutput, output);
2174 
2175    // enque write output (same as session::rConsoleWrite)
2176    ClientEvent event(client_events::kConsoleWriteOutput, output);
2177    enqueClientEvent(event);
2178 
2179    // fire event
2180    module_context::events().onConsoleOutput(
2181                                     module_context::ConsoleOutputNormal,
2182                                     output);
2183 }
2184 
consoleWriteError(const std::string & message)2185 void consoleWriteError(const std::string& message)
2186 {
2187    // NOTE: all actions herein must be threadsafe! (see comment above)
2188 
2189    // add console action
2190    r::session::consoleActions().add(kConsoleActionOutputError, message);
2191 
2192    // enque write error (same as session::rConsoleWrite)
2193    ClientEvent event(client_events::kConsoleWriteError, message);
2194    enqueClientEvent(event);
2195 
2196    // fire event
2197    module_context::events().onConsoleOutput(
2198                                     module_context::ConsoleOutputError,
2199                                     message);
2200 }
2201 
showErrorMessage(const std::string & title,const std::string & message)2202 void showErrorMessage(const std::string& title, const std::string& message)
2203 {
2204    session::clientEventQueue().add(showErrorMessageEvent(title, message));
2205 }
2206 
showFile(const FilePath & filePath,const std::string & window)2207 void showFile(const FilePath& filePath, const std::string& window)
2208 {
2209    if (session::options().programMode() == kSessionProgramModeDesktop)
2210    {
2211       // for pdfs handle specially for each platform
2212       if (filePath.getExtensionLowerCase() == ".pdf")
2213       {
2214          std::string path = filePath.getAbsolutePath();
2215          Error error = r::exec::RFunction(".rs.shellViewPdf", path).call();
2216          if (error)
2217             LOG_ERROR(error);
2218       }
2219       else
2220       {
2221          ClientEvent event = browseUrlEvent("file:///" + filePath.getAbsolutePath());
2222          module_context::enqueClientEvent(event);
2223       }
2224    }
2225    else if (session::options().programMode() == kSessionProgramModeServer)
2226    {
2227       if (!isPathViewAllowed(filePath))
2228       {
2229          module_context::showErrorMessage(
2230             "File Download Error",
2231             "This system administrator has not granted you permission "
2232             "to view this file.\n");
2233       }
2234       else if (session::options().allowFileDownloads())
2235       {
2236          std::string url = createFileUrl(filePath);
2237          ClientEvent event = browseUrlEvent(url);
2238          module_context::enqueClientEvent(event);
2239       }
2240       else
2241       {
2242          module_context::showErrorMessage(
2243             "File Download Error",
2244             "Unable to show file because file downloads are restricted "
2245             "on this server.\n");
2246       }
2247    }
2248 }
2249 
createFileUrl(const core::FilePath & filePath)2250 std::string createFileUrl(const core::FilePath& filePath)
2251 {
2252     // determine url based on whether this is in ~ or not
2253     std::string url;
2254     if (isVisibleUserFile(filePath))
2255     {
2256        std::string relPath = filePath.getRelativePath(
2257           module_context::userHomePath());
2258        url = "files/" + relPath;
2259     }
2260     else
2261     {
2262        url = "file_show?path=" + core::http::util::urlEncode(
2263           filePath.getAbsolutePath(), true);
2264     }
2265     return url;
2266 }
2267 
2268 
showContent(const std::string & title,const core::FilePath & filePath)2269 void showContent(const std::string& title, const core::FilePath& filePath)
2270 {
2271    // first provision a content url
2272    std::string contentUrl = content_urls::provision(title, filePath);
2273 
2274    // fire event
2275    json::Object contentItem;
2276    contentItem["title"] = title;
2277    contentItem["contentUrl"] = contentUrl;
2278    ClientEvent event(client_events::kShowContent, contentItem);
2279    module_context::enqueClientEvent(event);
2280 }
2281 
2282 
resourceFileAsString(const std::string & fileName)2283 std::string resourceFileAsString(const std::string& fileName)
2284 {
2285    FilePath resPath = session::options().rResourcesPath();
2286    FilePath filePath = resPath.completePath(fileName);
2287    std::string fileContents;
2288    Error error = readStringFromFile(filePath, &fileContents);
2289    if (error)
2290    {
2291       LOG_ERROR(error);
2292       return std::string();
2293    }
2294 
2295    return fileContents;
2296 }
2297 
2298 // given a pair of paths, return the second in the context of the first
pathRelativeTo(const FilePath & sourcePath,const FilePath & targetPath)2299 std::string pathRelativeTo(const FilePath& sourcePath,
2300                            const FilePath& targetPath)
2301 {
2302    std::string relative;
2303    if (targetPath == sourcePath)
2304    {
2305       relative = ".";
2306    }
2307    else if (targetPath.isWithin(sourcePath))
2308    {
2309       relative = targetPath.getRelativePath(sourcePath);
2310    }
2311    else
2312    {
2313       relative = createAliasedPath(targetPath);
2314    }
2315    return relative;
2316 }
2317 
activatePane(const std::string & pane)2318 void activatePane(const std::string& pane)
2319 {
2320    ClientEvent event(client_events::kActivatePane, pane);
2321    module_context::enqueClientEvent(event);
2322 }
2323 
shellWorkingDirectory()2324 FilePath shellWorkingDirectory()
2325 {
2326    std::string initialDirSetting = prefs::userPrefs().terminalInitialDirectory();
2327    if (initialDirSetting == kTerminalInitialDirectoryProject)
2328    {
2329       if (projects::projectContext().hasProject())
2330          return projects::projectContext().directory();
2331       else
2332          return module_context::safeCurrentPath();
2333    }
2334    else if (initialDirSetting == kTerminalInitialDirectoryCurrent)
2335       return module_context::safeCurrentPath();
2336    else if (initialDirSetting == kTerminalInitialDirectoryHome)
2337       return system::User::getUserHomePath();
2338    else
2339       return module_context::safeCurrentPath();
2340 }
2341 
events()2342 Events& events()
2343 {
2344    static Events instance;
2345    return instance;
2346 }
2347 
processSupervisor()2348 core::system::ProcessSupervisor& processSupervisor()
2349 {
2350    static core::system::ProcessSupervisor instance;
2351    return instance;
2352 }
2353 
sourceDiagnostics()2354 FilePath sourceDiagnostics()
2355 {
2356    FilePath diagnosticsPath =
2357          options().coreRSourcePath().completeChildPath("Diagnostics.R");
2358 
2359    Error error = r::exec::RFunction("source")
2360          .addParam(string_utils::utf8ToSystem(diagnosticsPath.getAbsolutePath()))
2361          .addParam("chdir", true)
2362          .call();
2363 
2364    if (error)
2365    {
2366       LOG_ERROR(error);
2367       return FilePath();
2368    }
2369    else
2370    {
2371       // note this path is also in Diagnostics.R so changes to the path
2372       // need to be synchronized there
2373       std::string reportPath = core::system::getenv("RSTUDIO_DIAGNOSTICS_REPORT");
2374       if (reportPath.empty())
2375          reportPath = "~/rstudio-diagnostics/diagnostics-report.txt";
2376       return module_context::resolveAliasedPath(reportPath);
2377    }
2378 }
2379 
2380 namespace {
2381 
beginRpcHandler(json::JsonRpcFunction function,json::JsonRpcRequest request,std::string asyncHandle)2382 void beginRpcHandler(json::JsonRpcFunction function,
2383                      json::JsonRpcRequest request,
2384                      std::string asyncHandle)
2385 {
2386    try
2387    {
2388       json::JsonRpcResponse response;
2389       Error error = function(request, &response);
2390       BOOST_ASSERT(!response.hasAfterResponse());
2391       if (error)
2392          response.setError(error);
2393 
2394       if (!response.hasField(kEventsPending))
2395          response.setField(kEventsPending, "false");
2396 
2397       json::Object value;
2398       value["handle"] = asyncHandle;
2399       value["response"] = response.getRawResponse();
2400       ClientEvent evt(client_events::kAsyncCompletion, value);
2401       enqueClientEvent(evt);
2402    }
2403    CATCH_UNEXPECTED_EXCEPTION
2404 
2405 }
2406 
2407 } // anonymous namespace
2408 
executeAsync(const json::JsonRpcFunction & function,const json::JsonRpcRequest & request,json::JsonRpcResponse * pResponse)2409 core::Error executeAsync(const json::JsonRpcFunction& function,
2410                          const json::JsonRpcRequest& request,
2411                          json::JsonRpcResponse* pResponse)
2412 {
2413    // Immediately return a response to the server with a handle that
2414    // identifies this invocation. In the meantime, kick off the actual
2415    // operation on a new thread.
2416 
2417    std::string handle = core::system::generateUuid(true);
2418    core::thread::safeLaunchThread(bind(beginRpcHandler,
2419                                        function,
2420                                        request,
2421                                        handle));
2422    pResponse->setAsyncHandle(handle);
2423    return Success();
2424 }
2425 
compileOutputAsJson(const CompileOutput & compileOutput)2426 core::json::Object compileOutputAsJson(const CompileOutput& compileOutput)
2427 {
2428    json::Object compileOutputJson;
2429    compileOutputJson["type"] = compileOutput.type;
2430    compileOutputJson["output"] = compileOutput.output;
2431    return compileOutputJson;
2432 }
2433 
CRANReposURL()2434 std::string CRANReposURL()
2435 {
2436    std::string url;
2437    r::exec::evaluateString("getOption('repos')[['CRAN']]", &url);
2438    if (url.empty())
2439       url = prefs::userPrefs().getCRANMirror().url;
2440    return url;
2441 }
2442 
rstudioCRANReposURL()2443 std::string rstudioCRANReposURL()
2444 {
2445    std::string protocol = prefs::userPrefs().useSecureDownload() ?
2446                                                            "https" : "http";
2447    return protocol + "://cran.rstudio.com/";
2448 }
2449 
rs_rstudioCRANReposUrl()2450 SEXP rs_rstudioCRANReposUrl()
2451 {
2452    r::sexp::Protect rProtect;
2453    return r::sexp::create(rstudioCRANReposURL(), &rProtect);
2454 }
2455 
downloadFileMethod(const std::string & defaultMethod)2456 std::string downloadFileMethod(const std::string& defaultMethod)
2457 {
2458    std::string method;
2459    Error error = r::exec::evaluateString(
2460                            "getOption('download.file.method', '" +
2461                             defaultMethod + "')", &method);
2462    if (error)
2463       LOG_ERROR(error);
2464    return method;
2465 }
2466 
CRANDownloadOptions()2467 std::string CRANDownloadOptions()
2468 {
2469    std::string options;
2470    Error error = r::exec::RFunction(".rs.CRANDownloadOptionsString").call(&options);
2471    if (error)
2472       LOG_ERROR(error);
2473    return options;
2474 }
2475 
haveSecureDownloadFileMethod()2476 bool haveSecureDownloadFileMethod()
2477 {
2478    bool secure = false;
2479    Error error = r::exec::RFunction(".rs.haveSecureDownloadFileMethod").call(
2480                                                                       &secure);
2481    if (error)
2482       LOG_ERROR(error);
2483    return secure;
2484 }
2485 
buildRCmd(const core::FilePath & rBinDir)2486 shell_utils::ShellCommand RCommand::buildRCmd(const core::FilePath& rBinDir)
2487 {
2488 #if defined(_WIN32)
2489    shell_utils::ShellCommand rCmd(rBinDir.completeChildPath("Rcmd.exe"));
2490 #else
2491    shell_utils::ShellCommand rCmd(rBinDir.completeChildPath("R"));
2492    rCmd << "CMD";
2493 #endif
2494    return rCmd;
2495 }
2496 
recursiveCopyDirectory(const core::FilePath & fromDir,const core::FilePath & toDir)2497 core::Error recursiveCopyDirectory(const core::FilePath& fromDir,
2498                                    const core::FilePath& toDir)
2499 {
2500    using namespace string_utils;
2501    r::exec::RFunction fileCopy("file.copy");
2502    fileCopy.addParam("from", utf8ToSystem(fromDir.getAbsolutePath()));
2503    fileCopy.addParam("to", utf8ToSystem(toDir.getAbsolutePath()));
2504    fileCopy.addParam("recursive", true);
2505    return fileCopy.call();
2506 }
2507 
isSessionTempPath(FilePath filePath)2508 bool isSessionTempPath(FilePath filePath)
2509 {
2510    // get the real path
2511    Error error = core::system::realPath(filePath, &filePath);
2512    if (error)
2513       LOG_ERROR(error);
2514 
2515    // get the session temp dir real path; needed since the file path above is
2516    // also a real path--e.g. on OS X, it refers to /private/tmp rather than
2517    // /tmp
2518    FilePath tempDir;
2519    error = core::system::realPath(module_context::tempDir(), &tempDir);
2520    if (error)
2521       LOG_ERROR(error);
2522 
2523    return filePath.isWithin(tempDir);
2524 }
2525 
2526 namespace {
2527 
isUsingRHelpServer()2528 bool isUsingRHelpServer()
2529 {
2530    // don't use R help server in server mode
2531    if (session::options().programMode() == kSessionProgramModeServer)
2532    {
2533       return false;
2534    }
2535 
2536 #ifdef _WIN32
2537    // There is a known issue serving content from the session temporary folder
2538    // on R 4.0.0 for Windows:
2539    //
2540    //    https://github.com/rstudio/rstudio/issues/6737
2541    //
2542    // so we avoid using the help server in that case.
2543    if (r::util::hasExactVersion("4.0.0"))
2544    {
2545       return false;
2546    }
2547 #endif
2548 
2549    // we're running on desktop with a suitable version of R;
2550    // okay to use the help server
2551    return true;
2552 }
2553 
2554 } // end anonymous namespace
2555 
sessionTempDirUrl(const std::string & sessionTempPath)2556 std::string sessionTempDirUrl(const std::string& sessionTempPath)
2557 {
2558    static bool useRHelpServer = isUsingRHelpServer();
2559 
2560    if (useRHelpServer)
2561    {
2562       boost::format fmt("http://localhost:%1%/session/%2%");
2563       return boost::str(fmt % rLocalHelpPort() % sessionTempPath);
2564    }
2565    else
2566    {
2567       boost::format fmt("session/%1%");
2568       return boost::str(fmt % sessionTempPath);
2569    }
2570 }
2571 
isPathViewAllowed(const FilePath & filePath)2572 bool isPathViewAllowed(const FilePath& filePath)
2573 {
2574    // Check to see if restrictions are in place
2575    if (!options().restrictDirectoryView())
2576       return true;
2577 
2578    // No paths are restricted in desktop mode
2579    if (options().programMode() != kSessionProgramModeServer)
2580       return true;
2581 
2582    // Viewing content in the home directory is always allowed
2583    if (filePath.isWithin(userHomePath().getParent()))
2584       return true;
2585 
2586    // Viewing content in the session temporary files path is always allowed
2587    if (isSessionTempPath(filePath))
2588       return true;
2589 
2590    // Allow users to view the system's configuration
2591    if (filePath.isWithin(core::system::xdg::systemConfigDir()))
2592       return true;
2593 
2594    // Viewing content in R libraries is always allowed
2595    std::vector<FilePath> libPaths = getLibPaths();
2596    for (const auto& dir: libPaths)
2597    {
2598       if (filePath.isWithin(dir))
2599       {
2600          return true;
2601       }
2602    }
2603 
2604    // Check session option for explicitly allowed directories
2605    std::string allowDirs = session::options().directoryViewAllowList();
2606    if (!allowDirs.empty())
2607    {
2608       std::vector<std::string> dirs = core::algorithm::split(allowDirs, ":");
2609       for (const auto& dir: dirs)
2610       {
2611          if (filePath.isWithin(FilePath(dir)))
2612          {
2613             return true;
2614          }
2615       }
2616    }
2617 
2618    // All other paths are implicitly disallowed
2619    return false;
2620 }
2621 
2622 namespace {
2623 
hasStem(const FilePath & filePath,const std::string & stem)2624 bool hasStem(const FilePath& filePath, const std::string& stem)
2625 {
2626    return filePath.getStem() == stem;
2627 }
2628 
2629 } // anonymous namespace
2630 
uniqueSaveStem(const core::FilePath & directoryPath,const std::string & base,std::string * pStem)2631 Error uniqueSaveStem(const core::FilePath& directoryPath,
2632                      const std::string& base,
2633                      std::string* pStem)
2634 {
2635    return uniqueSaveStem(directoryPath, base, "", pStem);
2636 }
2637 
uniqueSaveStem(const FilePath & directoryPath,const std::string & base,const std::string & delimiter,std::string * pStem)2638 Error uniqueSaveStem(const FilePath& directoryPath,
2639                      const std::string& base,
2640                      const std::string& delimiter,
2641                      std::string* pStem)
2642 {
2643    // determine unique file name
2644    std::vector<FilePath> children;
2645    Error error = directoryPath.getChildren(children);
2646    if (error)
2647       return error;
2648 
2649    // search for unique stem
2650    int i = 0;
2651    *pStem = base;
2652    while(true)
2653    {
2654       // seek stem
2655       std::vector<FilePath>::const_iterator it = std::find_if(
2656                                                 children.begin(),
2657                                                 children.end(),
2658                                                 boost::bind(hasStem, _1, *pStem));
2659       // break if not found
2660       if (it == children.end())
2661          break;
2662 
2663       // update stem and search again
2664       boost::format fmt(base + delimiter + "%1%");
2665       *pStem = boost::str(fmt % boost::io::group(std::setfill('0'),
2666                                                  std::setw(2),
2667                                                  ++i));
2668    }
2669 
2670    return Success();
2671 }
2672 
plotExportFormat(const std::string & name,const std::string & extension)2673 json::Object plotExportFormat(const std::string& name,
2674                               const std::string& extension)
2675 {
2676    json::Object formatJson;
2677    formatJson["name"] = name;
2678    formatJson["extension"] = extension;
2679    return formatJson;
2680 }
2681 
createSelfContainedHtml(const FilePath & sourceFilePath,const FilePath & targetFilePath)2682 Error createSelfContainedHtml(const FilePath& sourceFilePath,
2683                               const FilePath& targetFilePath)
2684 {
2685    r::exec::RFunction func(".rs.pandocSelfContainedHtml");
2686    func.addParam(string_utils::utf8ToSystem(sourceFilePath.getAbsolutePath()));
2687    func.addParam(string_utils::utf8ToSystem(
2688       session::options().rResourcesPath().completePath("pandoc_template.html").getAbsolutePath()));
2689    func.addParam(string_utils::utf8ToSystem(targetFilePath.getAbsolutePath()));
2690    return func.call();
2691 }
2692 
isUserFile(const FilePath & filePath)2693 bool isUserFile(const FilePath& filePath)
2694 {
2695    if (projects::projectContext().hasProject())
2696    {
2697       // if we are in a package project then screen our src- files
2698       if (projects::projectContext().config().buildType ==
2699                                               r_util::kBuildTypePackage)
2700       {
2701           FilePath pkgPath = projects::projectContext().buildTargetPath();
2702           std::string pkgRelative = filePath.getRelativePath(pkgPath);
2703           if (boost::algorithm::starts_with(pkgRelative, "src-"))
2704              return false;
2705       }
2706 
2707       // screen our various virtual environment directories + standard content ignores
2708       FilePath projPath = projects::projectContext().directory();
2709       std::vector<std::string> dirs({"packrat/", "renv/"});
2710       const std::vector<FilePath>& ignoreDirs = module_context::ignoreContentDirs();
2711       std::transform(ignoreDirs.begin(), ignoreDirs.end(), std::back_inserter(dirs),
2712                      [&projPath](const FilePath& ignorePath) {
2713          return ignorePath.getRelativePath(projPath) + "/";
2714       });
2715       std::string pkgRelative = filePath.getRelativePath(projPath);
2716       for (const auto& dir : dirs) {
2717          if (boost::algorithm::starts_with(pkgRelative, dir))
2718             return false;
2719       }
2720    }
2721 
2722    return true;
2723 }
2724 
2725 namespace {
2726 
2727 // NOTE: sync changes with SessionCompilePdf.cpp logEntryJson
sourceMarkerJson(const SourceMarker & sourceMarker)2728 json::Value sourceMarkerJson(const SourceMarker& sourceMarker)
2729 {
2730    json::Object obj;
2731    obj["type"] = static_cast<int>(sourceMarker.type);
2732    obj["path"] = module_context::createAliasedPath(sourceMarker.path);
2733    obj["line"] = sourceMarker.line;
2734    obj["column"] = sourceMarker.column;
2735    obj["message"] = sourceMarker.message.text();
2736    obj["log_path"] = "";
2737    obj["log_line"] = -1;
2738    obj["show_error_list"] = sourceMarker.showErrorList;
2739    return std::move(obj);
2740 }
2741 
2742 } // anonymous namespace
2743 
sourceMarkersAsJson(const std::vector<SourceMarker> & markers)2744 json::Array sourceMarkersAsJson(const std::vector<SourceMarker>& markers)
2745 {
2746    json::Array markersJson;
2747    std::transform(markers.begin(),
2748                   markers.end(),
2749                   std::back_inserter(markersJson),
2750                   sourceMarkerJson);
2751    return markersJson;
2752 }
2753 
sourceMarkerTypeFromString(const std::string & type)2754 SourceMarker::Type sourceMarkerTypeFromString(const std::string& type)
2755 {
2756    if (type == "error")
2757       return SourceMarker::Error;
2758    else if (type == "warning")
2759       return SourceMarker::Warning;
2760    else if (type == "box")
2761       return SourceMarker::Box;
2762    else if (type == "info")
2763       return SourceMarker::Info;
2764    else if (type == "style")
2765       return SourceMarker::Style;
2766    else if (type == "usage")
2767       return SourceMarker::Usage;
2768    else
2769       return SourceMarker::Error;
2770 }
2771 
2772 core::json::Array sourceMarkersAsJson(const std::vector<SourceMarker>& markers);
2773 
isLoadBalanced()2774 bool isLoadBalanced()
2775 {
2776    return !core::system::getenv(kRStudioSessionRoute).empty();
2777 }
2778 
2779 #ifdef _WIN32
usingMingwGcc49()2780 bool usingMingwGcc49()
2781 {
2782    // return true if the setting is true
2783    bool gcc49 = prefs::userState().usingMingwGcc49();
2784    if (gcc49)
2785       return true;
2786 
2787    // otherwise check R version
2788    r::exec::RFunction func(".rs.builtWithRtoolsGcc493");
2789    Error error = func.call(&gcc49);
2790    if (error)
2791       LOG_ERROR(error);
2792    return gcc49;
2793 
2794 }
2795 #else
usingMingwGcc49()2796 bool usingMingwGcc49()
2797 {
2798    return false;
2799 }
2800 #endif
2801 
2802 namespace {
2803 
2804 #ifdef __APPLE__
warnXcodeLicense()2805 void warnXcodeLicense()
2806 {
2807    const char* msg =
2808 R"EOF(Warning: macOS is reporting that you have not yet agreed to the Xcode license.
2809 This can occur if Xcode has been updated or reinstalled (e.g. as part of a macOS update).
2810 Some features (e.g. Git / SVN) may be disabled.
2811 
2812 Please run:
2813 
2814     sudo xcodebuild -license accept
2815 
2816 in a terminal to accept the Xcode license, and then restart RStudio.
2817 )EOF";
2818 
2819    std::cerr << msg << std::endl;
2820 }
2821 #endif
2822 
2823 } // end anonymous namespace
2824 
isMacOS()2825 bool isMacOS()
2826 {
2827 #ifdef __APPLE__
2828    return true;
2829 #else
2830    return false;
2831 #endif
2832 }
2833 
hasMacOSDeveloperTools()2834 bool hasMacOSDeveloperTools()
2835 {
2836    if (!isMacOS())
2837       return false;
2838 
2839    core::system::ProcessResult result;
2840    Error error = core::system::runCommand(
2841             "/usr/bin/xcrun --find --show-sdk-path",
2842             core::system::ProcessOptions(),
2843             &result);
2844 
2845    if (error)
2846    {
2847       LOG_ERROR(error);
2848       return false;
2849    }
2850 
2851    if (result.exitStatus == 69)
2852       checkXcodeLicense();
2853 
2854    return result.exitStatus == 0;
2855 }
2856 
hasMacOSCommandLineTools()2857 bool hasMacOSCommandLineTools()
2858 {
2859    if (!isMacOS())
2860       return false;
2861 
2862    return FilePath("/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk").exists();
2863 }
2864 
checkXcodeLicense()2865 void checkXcodeLicense()
2866 {
2867 #ifdef __APPLE__
2868 
2869    // avoid repeatedly warning the user
2870    static bool s_licenseChecked;
2871    if (s_licenseChecked)
2872       return;
2873 
2874    s_licenseChecked = true;
2875 
2876    core::system::ProcessResult result;
2877    Error error = core::system::runCommand(
2878             "/usr/bin/xcrun --find --show-sdk-path",
2879             core::system::ProcessOptions(),
2880             &result);
2881 
2882    // if an error occurs, log it but avoid otherwise annoying the user
2883    if (error)
2884    {
2885       LOG_ERROR(error);
2886       return;
2887    }
2888 
2889    // exit code 69 implies license error
2890    if (result.exitStatus == 69)
2891       warnXcodeLicense();
2892 
2893 #endif
2894 }
2895 
ignoreContentDirs()2896 std::vector<FilePath> ignoreContentDirs()
2897 {
2898    std::vector<FilePath> ignoreDirs;
2899    if (projects::projectContext().hasProject()) {
2900       // python virtual environments
2901       ignoreDirs = projects::projectContext().pythonEnvs();
2902       quarto::QuartoConfig quartoConf = quarto::quartoConfig();
2903       // quarto site output dir
2904       if (quartoConf.is_project) {
2905          FilePath quartoProjDir = module_context::resolveAliasedPath(quartoConf.project_dir);
2906          ignoreDirs.push_back(quartoProjDir.completeChildPath(quartoConf.project_output_dir));
2907          ignoreDirs.push_back(quartoProjDir.completeChildPath("_freeze"));
2908       }
2909       // rmarkdown site output dir
2910       if (module_context::isWebsiteProject())
2911       {
2912          FilePath buildTargetPath = projects::projectContext().buildTargetPath();
2913          std::string outputDir = module_context::websiteOutputDir();
2914          if (!outputDir.empty())
2915             ignoreDirs.push_back(buildTargetPath.completeChildPath(outputDir));
2916          else
2917             ignoreDirs.push_back(buildTargetPath);
2918       }
2919 
2920    }
2921    return ignoreDirs;
2922 }
2923 
isIgnoredContent(const core::FilePath & filePath,const std::vector<core::FilePath> & ignoreDirs)2924 bool isIgnoredContent(const core::FilePath& filePath,
2925                       const std::vector<core::FilePath>& ignoreDirs)
2926 {
2927    auto it = std::find_if(ignoreDirs.begin(), ignoreDirs.end(), [&filePath](const FilePath& dir) {
2928       return dir.exists() && filePath.isWithin(dir);
2929    });
2930    return it != ignoreDirs.end();
2931 }
2932 
2933 
getActiveLanguage()2934 std::string getActiveLanguage()
2935 {
2936    if (modules::reticulate::isReplActive())
2937    {
2938       return "Python";
2939    }
2940    else
2941    {
2942       return "R";
2943    }
2944 }
2945 
adaptToLanguage(const std::string & language)2946 Error adaptToLanguage(const std::string& language)
2947 {
2948    // check to see what language is active in main console
2949    using namespace r::exec;
2950 
2951    // check to see what language is currently active (but default to r)
2952    std::string activeLanguage = getActiveLanguage();
2953 
2954    // now, detect if we are transitioning languages
2955    if (language != activeLanguage)
2956    {
2957       // since it may take some time for the console input to be processed,
2958       // we screen out consecutive transition attempts (otherwise we can
2959       // get multiple interleaved attempts to launch the REPL with console
2960       // input)
2961       static RSTUDIO_BOOST_CONNECTION conn;
2962       if (conn.connected())
2963          return Success();
2964 
2965       // establish the connection, and then simply disconnect once we
2966       // receive the signal
2967       conn = module_context::events().onConsolePrompt.connect([&](const std::string&) {
2968          conn.disconnect();
2969       });
2970 
2971       Error error;
2972 
2973       if (activeLanguage == "R" && language == "Python")
2974       {
2975          // r -> python: activate the reticulate REPL
2976          error = module_context::enqueueConsoleInput("reticulate::repl_python()");
2977       }
2978       else if (activeLanguage == "Python" && language == "R")
2979       {
2980          // python -> r: deactivate the reticulate REPL
2981          error = module_context::enqueueConsoleInput("quit");
2982       }
2983 
2984       if (error)
2985          LOG_ERROR(error);
2986    }
2987 
2988    return Success();
2989 }
2990 
initialize()2991 Error initialize()
2992 {
2993    // register .Call methods
2994    RS_REGISTER_CALL_METHOD(rs_activatePane);
2995    RS_REGISTER_CALL_METHOD(rs_base64decode);
2996    RS_REGISTER_CALL_METHOD(rs_base64encode);
2997    RS_REGISTER_CALL_METHOD(rs_base64encodeFile);
2998    RS_REGISTER_CALL_METHOD(rs_htmlEscape);
2999    RS_REGISTER_CALL_METHOD(rs_enqueClientEvent);
3000    RS_REGISTER_CALL_METHOD(rs_ensureFileHidden);
3001    RS_REGISTER_CALL_METHOD(rs_generateShortUuid);
3002    RS_REGISTER_CALL_METHOD(rs_getPersistentValue);
3003    RS_REGISTER_CALL_METHOD(rs_isRScriptInPackageBuildTarget);
3004    RS_REGISTER_CALL_METHOD(rs_logErrorMessage);
3005    RS_REGISTER_CALL_METHOD(rs_logWarningMessage);
3006    RS_REGISTER_CALL_METHOD(rs_markdownToHTML);
3007    RS_REGISTER_CALL_METHOD(rs_packageLoaded);
3008    RS_REGISTER_CALL_METHOD(rs_packageNameForSourceFile);
3009    RS_REGISTER_CALL_METHOD(rs_packageUnloaded);
3010    RS_REGISTER_CALL_METHOD(rs_resolveAliasedPath);
3011    RS_REGISTER_CALL_METHOD(rs_restartR);
3012    RS_REGISTER_CALL_METHOD(rs_rstudioCitation);
3013    RS_REGISTER_CALL_METHOD(rs_rstudioCRANReposUrl);
3014    RS_REGISTER_CALL_METHOD(rs_rstudioEdition);
3015    RS_REGISTER_CALL_METHOD(rs_rstudioProgramMode);
3016    RS_REGISTER_CALL_METHOD(rs_rstudioVersion);
3017    RS_REGISTER_CALL_METHOD(rs_rstudioLongVersion);
3018    RS_REGISTER_CALL_METHOD(rs_rstudioReleaseName);
3019    RS_REGISTER_CALL_METHOD(rs_sessionModulePath);
3020    RS_REGISTER_CALL_METHOD(rs_setPersistentValue);
3021    RS_REGISTER_CALL_METHOD(rs_setUsingMingwGcc49);
3022    RS_REGISTER_CALL_METHOD(rs_showErrorMessage);
3023    RS_REGISTER_CALL_METHOD(rs_sourceDiagnostics);
3024    RS_REGISTER_CALL_METHOD(rs_threadSleep);
3025    RS_REGISTER_CALL_METHOD(rs_userPrompt);
3026    RS_REGISTER_CALL_METHOD(rs_setRpcDelay);
3027    RS_REGISTER_CALL_METHOD(rs_performBackgroundProcessing);
3028 
3029    // initialize monitored scratch dir
3030    initializeMonitoredUserScratchDir();
3031 
3032    // source the ModuleTools.R file
3033    FilePath modulesPath = session::options().modulesRSourcePath();
3034    return r::sourceManager().sourceTools(modulesPath.completePath("ModuleTools.R"));
3035 }
3036 
3037 
3038 } // namespace module_context
3039 } // namespace session
3040 } // namespace rstudio
3041