~ci-train-bot/ubuntu-app-launch/ubuntu-app-launch-ubuntu-yakkety-landing-1944

« back to all changes in this revision

Viewing changes to libubuntu-app-launch/application-impl-base.cpp

Migrate starting and stopping applications to new classes

Approved by: Charles Kerr, unity-api-1-bot

Show diffs side-by-side

added added

removed removed

Lines of Context:
25
25
 
26
26
#include <upstart.h>
27
27
 
28
 
#include "app-info.h"
29
28
#include "application-impl-base.h"
 
29
#include "helpers.h"
30
30
#include "registry-impl.h"
 
31
#include "second-exec-core.h"
 
32
 
 
33
extern "C" {
 
34
#include "ubuntu-app-launch-trace.h"
 
35
}
31
36
 
32
37
namespace ubuntu
33
38
{
46
51
    return !instances().empty();
47
52
}
48
53
 
 
54
/** Function to create all the standard environment variables that we're
 
55
    building for everyone. Mostly stuff involving paths.
 
56
 
 
57
    \param package Name of the package
 
58
    \param pkgdir Directory that the package lives in
 
59
*/
 
60
std::list<std::pair<std::string, std::string>> Base::confinedEnv(const std::string& package, const std::string& pkgdir)
 
61
{
 
62
    std::list<std::pair<std::string, std::string>> retval{{"UBUNTU_APPLICATION_ISOLATION", "1"}};
 
63
 
 
64
    /* C Funcs can return null, which offends std::string */
 
65
    auto cset = [&retval](const gchar* key, const gchar* value) {
 
66
        if (value != nullptr)
 
67
        {
 
68
            g_debug("Setting '%s' to '%s'", key, value);
 
69
            retval.emplace_back(std::make_pair(key, value));
 
70
        }
 
71
    };
 
72
 
 
73
    cset("XDG_CACHE_HOME", g_get_user_cache_dir());
 
74
    cset("XDG_CONFIG_HOME", g_get_user_config_dir());
 
75
    cset("XDG_DATA_HOME", g_get_user_data_dir());
 
76
    cset("XDG_RUNTIME_DIR", g_get_user_runtime_dir());
 
77
 
 
78
    /* Add the application's dir to the list of sources for data */
 
79
    gchar* basedatadirs = g_strjoinv(":", (gchar**)g_get_system_data_dirs());
 
80
    gchar* datadirs = g_strjoin(":", pkgdir.c_str(), basedatadirs, nullptr);
 
81
    cset("XDG_DATA_DIRS", datadirs);
 
82
    g_free(datadirs);
 
83
    g_free(basedatadirs);
 
84
 
 
85
    /* Set TMPDIR to something sane and application-specific */
 
86
    gchar* tmpdir = g_strdup_printf("%s/confined/%s", g_get_user_runtime_dir(), package.c_str());
 
87
    cset("TMPDIR", tmpdir);
 
88
    g_debug("Creating '%s'", tmpdir);
 
89
    g_mkdir_with_parents(tmpdir, 0700);
 
90
    g_free(tmpdir);
 
91
 
 
92
    /* Do the same for nvidia */
 
93
    gchar* nv_shader_cachedir = g_strdup_printf("%s/%s", g_get_user_cache_dir(), package.c_str());
 
94
    cset("__GL_SHADER_DISK_CACHE_PATH", nv_shader_cachedir);
 
95
    g_free(nv_shader_cachedir);
 
96
 
 
97
    return retval;
 
98
}
 
99
 
 
100
/** Checks to see if we have a primary PID for the instance */
49
101
bool UpstartInstance::isRunning()
50
102
{
51
103
    return primaryPid() != 0;
52
104
}
53
105
 
 
106
/** Uses Upstart to get the primary PID of the instance using Upstart's
 
107
    DBus interface */
54
108
pid_t UpstartInstance::primaryPid()
55
109
{
56
110
    auto jobpath = registry_->impl->upstartJobPath(job_);
73
127
                                        G_VARIANT_TYPE("(o)"),                          /* return type */
74
128
                                        G_DBUS_CALL_FLAGS_NONE,                         /* flags */
75
129
                                        -1,                                             /* timeout: default */
76
 
                                        registry_->impl->thread.getCancellable().get(), /* cancelable */
 
130
                                        registry_->impl->thread.getCancellable().get(), /* cancellable */
77
131
                                        &error);
78
132
 
79
133
        if (error != nullptr)
110
164
                                        G_VARIANT_TYPE("(a{sv})"),                             /* return type */
111
165
                                        G_DBUS_CALL_FLAGS_NONE,                                /* flags */
112
166
                                        -1,                                                    /* timeout: default */
113
 
                                        registry_->impl->thread.getCancellable().get(),        /* cancelable */
 
167
                                        registry_->impl->thread.getCancellable().get(),        /* cancellable */
114
168
                                        &error);
115
169
 
116
170
        if (error != nullptr)
147
201
    });
148
202
}
149
203
 
 
204
/** Looks at the PIDs in the instance cgroup and checks to see if @pid
 
205
    is in the set.
 
206
 
 
207
    @param pid PID to look for
 
208
*/
150
209
bool UpstartInstance::hasPid(pid_t pid)
151
210
{
152
211
    for (auto testpid : registry_->impl->pidsFromCgroup(job_, instance_))
155
214
    return false;
156
215
}
157
216
 
 
217
/** Gets the path to the log file for this instance */
158
218
std::string UpstartInstance::logPath()
159
219
{
160
 
    auto cpath = ubuntu_app_launch_application_log_path(std::string(appId_).c_str());
161
 
    if (cpath != nullptr)
162
 
    {
163
 
        std::string retval(cpath);
164
 
        g_free(cpath);
165
 
        return retval;
166
 
    }
167
 
    else
168
 
    {
169
 
        return {};
170
 
    }
 
220
    std::string logfile = job_;
 
221
    if (!instance_.empty())
 
222
    {
 
223
        logfile += "-";
 
224
        logfile += instance_;
 
225
    }
 
226
 
 
227
    logfile += ".log";
 
228
 
 
229
    gchar* cpath = g_build_filename(g_get_user_cache_dir(), "upstart", logfile.c_str(), nullptr);
 
230
    std::string path(cpath);
 
231
    g_free(cpath);
 
232
 
 
233
    return path;
171
234
}
172
235
 
 
236
/** Returns all the PIDs that are in the cgroup for this application */
173
237
std::vector<pid_t> UpstartInstance::pids()
174
238
{
175
239
    auto pids = registry_->impl->pidsFromCgroup(job_, instance_);
177
241
    return pids;
178
242
}
179
243
 
 
244
/** Pauses this application by sending SIGSTOP to all the PIDs in the
 
245
    cgroup and tells Zeitgeist that we've left the application. */
180
246
void UpstartInstance::pause()
181
247
{
182
248
    g_debug("Pausing application: %s", std::string(appId_).c_str());
192
258
    pidListToDbus(pids, "ApplicationPaused");
193
259
}
194
260
 
 
261
/** Resumes this application by sending SIGCONT to all the PIDs in the
 
262
    cgroup and tells Zeitgeist that we're accessing the application. */
195
263
void UpstartInstance::resume()
196
264
{
197
265
    g_debug("Resuming application: %s", std::string(appId_).c_str());
207
275
    pidListToDbus(pids, "ApplicationResumed");
208
276
}
209
277
 
 
278
/** Stops this instance by asking Upstart to stop it. Upstart will then
 
279
    send a SIGTERM and five seconds later start killing things. */
210
280
void UpstartInstance::stop()
211
281
{
212
 
    ubuntu_app_launch_stop_application(std::string(appId_).c_str());
 
282
    if (!registry_->impl->thread.executeOnThread<bool>([this]() {
 
283
 
 
284
            g_debug("Stopping job %s app_id %s instance_id %s", job_.c_str(), std::string(appId_).c_str(),
 
285
                    instance_.c_str());
 
286
 
 
287
            auto jobpath = registry_->impl->upstartJobPath(job_);
 
288
            if (jobpath.empty())
 
289
            {
 
290
                throw new std::runtime_error("Unable to get job path for Upstart job '" + job_ + "'");
 
291
            }
 
292
 
 
293
            GVariantBuilder builder;
 
294
            g_variant_builder_init(&builder, G_VARIANT_TYPE_TUPLE);
 
295
            g_variant_builder_open(&builder, G_VARIANT_TYPE_ARRAY);
 
296
 
 
297
            g_variant_builder_add_value(
 
298
                &builder, g_variant_new_take_string(g_strdup_printf("APP_ID=%s", std::string(appId_).c_str())));
 
299
 
 
300
            if (!instance_.empty())
 
301
            {
 
302
                g_variant_builder_add_value(
 
303
                    &builder, g_variant_new_take_string(g_strdup_printf("INSTANCE_ID=%s", instance_.c_str())));
 
304
            }
 
305
 
 
306
            g_variant_builder_close(&builder);
 
307
            g_variant_builder_add_value(&builder, g_variant_new_boolean(FALSE)); /* wait */
 
308
 
 
309
            GError* error = nullptr;
 
310
            GVariant* stop_variant =
 
311
                g_dbus_connection_call_sync(registry_->impl->_dbus.get(),                   /* Dbus */
 
312
                                            DBUS_SERVICE_UPSTART,                           /* Upstart name */
 
313
                                            jobpath.c_str(),                                /* path */
 
314
                                            DBUS_INTERFACE_UPSTART_JOB,                     /* interface */
 
315
                                            "Stop",                                         /* method */
 
316
                                            g_variant_builder_end(&builder),                /* params */
 
317
                                            nullptr,                                        /* return */
 
318
                                            G_DBUS_CALL_FLAGS_NONE,                         /* flags */
 
319
                                            -1,                                             /* timeout: default */
 
320
                                            registry_->impl->thread.getCancellable().get(), /* cancellable */
 
321
                                            &error);                                        /* error (hopefully not) */
 
322
 
 
323
            g_clear_pointer(&stop_variant, g_variant_unref);
 
324
 
 
325
            if (error != nullptr)
 
326
            {
 
327
                g_warning("Unable to stop job %s app_id %s instance_id %s: %s", job_.c_str(),
 
328
                          std::string(appId_).c_str(), instance_.c_str(), error->message);
 
329
                g_error_free(error);
 
330
                return false;
 
331
            }
 
332
 
 
333
            return true;
 
334
        }))
 
335
    {
 
336
        g_warning("Unable to stop Upstart instance");
 
337
    }
213
338
}
214
339
 
215
340
/** Sets the OOM adjustment by getting the list of PIDs and writing
216
 
    the value to each of their files in proc */
 
341
    the value to each of their files in proc
 
342
 
 
343
    \param score OOM Score to set
 
344
*/
217
345
void UpstartInstance::setOomAdjustment(const oom::Score score)
218
346
{
219
347
    forAllPids([this, &score](pid_t pid) { oomValueToPid(pid, score); });
251
379
}
252
380
 
253
381
/** Go through the list of PIDs calling a function and handling
254
 
    the issue with getting PIDs being a racey condition. */
 
382
    the issue with getting PIDs being a racey condition.
 
383
 
 
384
    \param eachPid Function to run on each PID
 
385
*/
255
386
std::vector<pid_t> UpstartInstance::forAllPids(std::function<void(pid_t)> eachPid)
256
387
{
257
388
    std::set<pid_t> seenPids;
275
406
}
276
407
 
277
408
/** Sends a signal to a PID with a warning if we can't send it.
278
 
    We could throw an exception, but we can't handle it usefully anyway */
 
409
    We could throw an exception, but we can't handle it usefully anyway
 
410
 
 
411
    \param pid PID to send the signal to
 
412
    \param signal signal to send
 
413
*/
279
414
void UpstartInstance::signalToPid(pid_t pid, int signal)
280
415
{
281
416
    if (-1 == kill(pid, signal))
287
422
 
288
423
/** Get the path to the PID's OOM adjust path, with allowing for an
289
424
    override for testing using the environment variable
290
 
    UBUNTU_APP_LAUNCH_OOM_PROC_PATH */
 
425
    UBUNTU_APP_LAUNCH_OOM_PROC_PATH
 
426
 
 
427
    \param pid PID to build path for
 
428
*/
291
429
std::string UpstartInstance::pidToOomPath(pid_t pid)
292
430
{
293
431
    static std::string procpath;
308
446
}
309
447
 
310
448
/** Writes an OOM value to proc, assuming we have a string
311
 
    in the outer loop */
 
449
    in the outer loop
 
450
 
 
451
    \param pid PID to change the OOM value of
 
452
    \param oomvalue OOM value to set
 
453
*/
312
454
void UpstartInstance::oomValueToPid(pid_t pid, const oom::Score oomvalue)
313
455
{
314
456
    auto oomstr = std::to_string(static_cast<std::int32_t>(oomvalue));
356
498
}
357
499
 
358
500
/** Use a setuid root helper for setting the oom value of
359
 
    Chromium instances */
 
501
    Chromium instances
 
502
 
 
503
    \param pid PID to change the OOM value of
 
504
    \param oomvalue OOM value to set
 
505
*/
360
506
void UpstartInstance::oomValueToPidHelper(pid_t pid, const oom::Score oomvalue)
361
507
{
362
508
    GError* error = nullptr;
400
546
}
401
547
 
402
548
/** Send a signal that we've change the application. Do this on the
403
 
    registry thread in an idle so that we don't block anyone. */
 
549
    registry thread in an idle so that we don't block anyone.
 
550
 
 
551
    \param pids List of PIDs to turn into variants to send
 
552
    \param signal Name of the DBus signal to send
 
553
*/
404
554
void UpstartInstance::pidListToDbus(const std::vector<pid_t>& pids, const std::string& signal)
405
555
{
406
556
    auto registry = registry_;
458
608
    });
459
609
}
460
610
 
 
611
/** Create a new Upstart Instance object that can track the job and
 
612
    get information about it.
 
613
 
 
614
    \param appId Application ID
 
615
    \param job Upstart job name
 
616
    \param instance Upstart instance name
 
617
    \param urls URLs sent to the application (only on launch today)
 
618
    \param registry Registry of persistent connections to use
 
619
*/
461
620
UpstartInstance::UpstartInstance(const AppID& appId,
462
621
                                 const std::string& job,
463
622
                                 const std::string& instance,
 
623
                                 const std::vector<Application::URL>& urls,
464
624
                                 const std::shared_ptr<Registry>& registry)
465
625
    : appId_(appId)
466
626
    , job_(job)
467
627
    , instance_(instance)
 
628
    , urls_(urls)
468
629
    , registry_(registry)
469
630
{
 
631
    g_debug("Creating a new UpstartInstance for '%s' instance '%s'", std::string(appId_).c_str(), instance.c_str());
470
632
}
471
633
 
472
 
std::shared_ptr<gchar*> urlsToStrv(const std::vector<Application::URL>& urls)
 
634
/** Reformat a C++ vector of URLs into a C GStrv of strings
 
635
 
 
636
    \param urls Vector of URLs to make into C strings
 
637
*/
 
638
std::shared_ptr<gchar*> UpstartInstance::urlsToStrv(const std::vector<Application::URL>& urls)
473
639
{
474
640
    if (urls.empty())
475
641
    {
481
647
    for (auto url : urls)
482
648
    {
483
649
        auto str = g_strdup(url.value().c_str());
 
650
        g_debug("Converting URL: %s", str);
484
651
        g_array_append_val(array, str);
485
652
    }
486
653
 
487
654
    return std::shared_ptr<gchar*>((gchar**)g_array_free(array, FALSE), g_strfreev);
488
655
}
489
656
 
490
 
std::shared_ptr<UpstartInstance> UpstartInstance::launch(const AppID& appId,
491
 
                                                         const std::string& job,
492
 
                                                         const std::string& instance,
493
 
                                                         const std::vector<Application::URL>& urls,
494
 
                                                         const std::shared_ptr<Registry>& registry,
495
 
                                                         launchMode mode)
496
 
{
497
 
    auto urlstrv = urlsToStrv(urls);
498
 
    auto start_result = registry->impl->thread.executeOnThread<gboolean>([registry, &appId, &urlstrv, &mode]() {
499
 
        return start_application_core(registry->impl->_dbus.get(), registry->impl->thread.getCancellable().get(),
500
 
                                      std::string(appId).c_str(), urlstrv.get(),
501
 
                                      mode == launchMode::STANDARD ? FALSE : TRUE);
502
 
    });
503
 
 
504
 
    if (start_result == FALSE)
 
657
/** Small helper that we can new/delete to work better with C stuff */
 
658
struct StartCHelper
 
659
{
 
660
    std::shared_ptr<UpstartInstance> ptr;
 
661
};
 
662
 
 
663
/** Callback from starting an application. It checks to see whether the
 
664
    app is already running. If it is already running then we need to send
 
665
    the URLs to it via DBus.
 
666
 
 
667
    \param obj The GDBusConnection object
 
668
    \param res Async result object
 
669
    \param user_data A pointer to a StartCHelper structure
 
670
*/
 
671
void UpstartInstance::application_start_cb(GObject* obj, GAsyncResult* res, gpointer user_data)
 
672
{
 
673
    auto data = static_cast<StartCHelper*>(user_data);
 
674
    GError* error{nullptr};
 
675
    GVariant* result{nullptr};
 
676
 
 
677
    tracepoint(ubuntu_app_launch, libual_start_message_callback, std::string(data->ptr->appId_).c_str());
 
678
 
 
679
    g_debug("Started Message Callback: %s", std::string(data->ptr->appId_).c_str());
 
680
 
 
681
    result = g_dbus_connection_call_finish(G_DBUS_CONNECTION(obj), res, &error);
 
682
 
 
683
    g_clear_pointer(&result, g_variant_unref);
 
684
 
 
685
    if (error != nullptr)
505
686
    {
 
687
        if (g_dbus_error_is_remote_error(error))
 
688
        {
 
689
            gchar* remote_error = g_dbus_error_get_remote_error(error);
 
690
            g_debug("Remote error: %s", remote_error);
 
691
            if (g_strcmp0(remote_error, "com.ubuntu.Upstart0_6.Error.AlreadyStarted") == 0)
 
692
            {
 
693
                auto urls = urlsToStrv(data->ptr->urls_);
 
694
                second_exec(data->ptr->registry_->impl->_dbus.get(),                   /* DBus */
 
695
                            data->ptr->registry_->impl->thread.getCancellable().get(), /* cancellable */
 
696
                            data->ptr->primaryPid(),                                   /* primary pid */
 
697
                            std::string(data->ptr->appId_).c_str(),                    /* appid */
 
698
                            urls.get());                                               /* urls */
 
699
            }
 
700
 
 
701
            g_free(remote_error);
 
702
        }
 
703
        else
 
704
        {
 
705
            g_warning("Unable to emit event to start application: %s", error->message);
 
706
        }
 
707
        g_error_free(error);
 
708
    }
 
709
 
 
710
    delete data;
 
711
}
 
712
 
 
713
/** Launch an application and create a new UpstartInstance object to track
 
714
    its progress.
 
715
 
 
716
    \param appId Application ID
 
717
    \param job Upstart job name
 
718
    \param instance Upstart instance name
 
719
    \param urls URLs sent to the application (only on launch today)
 
720
    \param registry Registry of persistent connections to use
 
721
    \param mode Whether or not to setup the environment for testing
 
722
    \param getenv A function to get additional environment variable when appropriate
 
723
*/
 
724
std::shared_ptr<UpstartInstance> UpstartInstance::launch(
 
725
    const AppID& appId,
 
726
    const std::string& job,
 
727
    const std::string& instance,
 
728
    const std::vector<Application::URL>& urls,
 
729
    const std::shared_ptr<Registry>& registry,
 
730
    launchMode mode,
 
731
    std::function<std::list<std::pair<std::string, std::string>>(void)>& getenv)
 
732
{
 
733
    if (appId.empty())
506
734
        return {};
507
 
    }
508
 
 
509
 
    return std::make_shared<UpstartInstance>(appId, job, instance, registry);
 
735
 
 
736
    return registry->impl->thread.executeOnThread<std::shared_ptr<UpstartInstance>>(
 
737
        [&]() -> std::shared_ptr<UpstartInstance> {
 
738
            std::string appIdStr{appId};
 
739
            g_debug("Initializing params for an new UpstartInstance for: %s", appIdStr.c_str());
 
740
 
 
741
            tracepoint(ubuntu_app_launch, libual_start, appIdStr.c_str());
 
742
 
 
743
            int timeout = 1;
 
744
            if (ubuntu::app_launch::Registry::Impl::isWatchingAppStarting())
 
745
            {
 
746
                timeout = 0;
 
747
            }
 
748
 
 
749
            auto handshake = starting_handshake_start(appIdStr.c_str(), timeout);
 
750
            if (handshake == nullptr)
 
751
            {
 
752
                g_warning("Unable to setup starting handshake");
 
753
            }
 
754
 
 
755
            /* Figure out the DBus path for the job */
 
756
            auto jobpath = registry->impl->upstartJobPath(job);
 
757
 
 
758
            /* Build up our environment */
 
759
            auto env = getenv();
 
760
 
 
761
            env.emplace_back(std::make_pair("APP_ID", appIdStr));                           /* Application ID */
 
762
            env.emplace_back(std::make_pair("APP_LAUNCHER_PID", std::to_string(getpid()))); /* Who we are, for bugs */
 
763
 
 
764
            if (!urls.empty())
 
765
            {
 
766
                auto accumfunc = [](const std::string& prev, Application::URL thisurl) -> std::string {
 
767
                    gchar* gescaped = g_shell_quote(thisurl.value().c_str());
 
768
                    std::string escaped;
 
769
                    if (gescaped != nullptr)
 
770
                    {
 
771
                        escaped = gescaped;
 
772
                        g_free(gescaped);
 
773
                    }
 
774
                    else
 
775
                    {
 
776
                        g_warning("Unable to escape URL: %s", thisurl.value().c_str());
 
777
                        return prev;
 
778
                    }
 
779
 
 
780
                    if (prev.empty())
 
781
                    {
 
782
                        return escaped;
 
783
                    }
 
784
                    else
 
785
                    {
 
786
                        return prev + " " + escaped;
 
787
                    }
 
788
                };
 
789
                auto urlstring = std::accumulate(urls.begin(), urls.end(), std::string{}, accumfunc);
 
790
                env.emplace_back(std::make_pair("APP_URIS", urlstring));
 
791
            }
 
792
 
 
793
            if (mode == launchMode::TEST)
 
794
            {
 
795
                env.emplace_back(std::make_pair("QT_LOAD_TESTABILITY", "1"));
 
796
            }
 
797
 
 
798
            /* Convert to GVariant */
 
799
            GVariantBuilder builder;
 
800
            g_variant_builder_init(&builder, G_VARIANT_TYPE_TUPLE);
 
801
 
 
802
            g_variant_builder_open(&builder, G_VARIANT_TYPE_ARRAY);
 
803
 
 
804
            for (const auto& envvar : env)
 
805
            {
 
806
                g_variant_builder_add_value(&builder, g_variant_new_take_string(g_strdup_printf(
 
807
                                                          "%s=%s", envvar.first.c_str(), envvar.second.c_str())));
 
808
            }
 
809
 
 
810
            g_variant_builder_close(&builder);
 
811
            g_variant_builder_add_value(&builder, g_variant_new_boolean(TRUE));
 
812
 
 
813
            auto retval = std::make_shared<UpstartInstance>(appId, job, instance, urls, registry);
 
814
            auto chelper = new StartCHelper{};
 
815
            chelper->ptr = retval;
 
816
 
 
817
            tracepoint(ubuntu_app_launch, handshake_wait, appIdStr.c_str());
 
818
            starting_handshake_wait(handshake);
 
819
            tracepoint(ubuntu_app_launch, handshake_complete, appIdStr.c_str());
 
820
 
 
821
            /* Call the job start function */
 
822
            g_debug("Asking Upstart to start task for: %s", appIdStr.c_str());
 
823
            g_dbus_connection_call(registry->impl->_dbus.get(),                   /* bus */
 
824
                                   DBUS_SERVICE_UPSTART,                          /* service name */
 
825
                                   jobpath.c_str(),                               /* Path */
 
826
                                   DBUS_INTERFACE_UPSTART_JOB,                    /* interface */
 
827
                                   "Start",                                       /* method */
 
828
                                   g_variant_builder_end(&builder),               /* params */
 
829
                                   nullptr,                                       /* return */
 
830
                                   G_DBUS_CALL_FLAGS_NONE,                        /* flags */
 
831
                                   -1,                                            /* default timeout */
 
832
                                   registry->impl->thread.getCancellable().get(), /* cancellable */
 
833
                                   application_start_cb,                          /* callback */
 
834
                                   chelper                                        /* object */
 
835
                                   );
 
836
 
 
837
            tracepoint(ubuntu_app_launch, libual_start_message_sent, appIdStr.c_str());
 
838
 
 
839
            return retval;
 
840
        });
510
841
}
511
842
 
512
843
}  // namespace app_impls