polkit-0.118-duktape.patch 49 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498
  1. diff --git a/configure.ac b/configure.ac
  2. index 5cedb4eca980f050fb5855ab577e93100adf8fec..6c274869f39d4b65b08f7cdb9e461b5182d297ec 100644
  3. --- a/configure.ac
  4. +++ b/configure.ac
  5. @@ -79,11 +79,22 @@ PKG_CHECK_MODULES(GLIB, [gmodule-2.0 gio-unix-2.0 >= 2.30.0])
  6. AC_SUBST(GLIB_CFLAGS)
  7. AC_SUBST(GLIB_LIBS)
  8. -PKG_CHECK_MODULES(LIBJS, [mozjs-78])
  9. -
  10. -AC_SUBST(LIBJS_CFLAGS)
  11. -AC_SUBST(LIBJS_CXXFLAGS)
  12. -AC_SUBST(LIBJS_LIBS)
  13. +dnl ---------------------------------------------------------------------------
  14. +dnl - Check javascript backend
  15. +dnl ---------------------------------------------------------------------------
  16. +AC_ARG_WITH(duktape, AS_HELP_STRING([--with-duktape],[Use Duktape as javascript backend]),with_duktape=yes,with_duktape=no)
  17. +AS_IF([test x${with_duktape} == xyes], [
  18. + PKG_CHECK_MODULES(LIBJS, [duktape >= 2.0.0 ])
  19. + AC_SUBST(LIBJS_CFLAGS)
  20. + AC_SUBST(LIBJS_LIBS)
  21. +], [
  22. + PKG_CHECK_MODULES(LIBJS, [mozjs-78])
  23. +
  24. + AC_SUBST(LIBJS_CFLAGS)
  25. + AC_SUBST(LIBJS_CXXFLAGS)
  26. + AC_SUBST(LIBJS_LIBS)
  27. +])
  28. +AM_CONDITIONAL(USE_DUKTAPE, [test x$with_duktape == xyes], [Using duktape as javascript engine library])
  29. EXPAT_LIB=""
  30. AC_ARG_WITH(expat, [ --with-expat=<dir> Use expat from here],
  31. @@ -580,6 +591,13 @@ echo "
  32. PAM support: ${have_pam}
  33. systemdsystemunitdir: ${systemdsystemunitdir}
  34. polkitd user: ${POLKITD_USER}"
  35. +if test "x${with_duktape}" = xyes; then
  36. +echo "
  37. + Javascript engine: Duktape"
  38. +else
  39. +echo "
  40. + Javascript engine: Mozjs"
  41. +fi
  42. if test "$have_pam" = yes ; then
  43. echo "
  44. diff --git a/src/polkitbackend/Makefile.am b/src/polkitbackend/Makefile.am
  45. index e48b739cc0a4e7606be0271ba4b4e3bd33b08545..9572b067effdf6f0dcd1c6b17b2e8c59c1ed6238 100644
  46. --- a/src/polkitbackend/Makefile.am
  47. +++ b/src/polkitbackend/Makefile.am
  48. @@ -33,7 +33,7 @@ libpolkit_backend_1_la_SOURCES = \
  49. polkitbackendprivate.h \
  50. polkitbackendauthority.h polkitbackendauthority.c \
  51. polkitbackendinteractiveauthority.h polkitbackendinteractiveauthority.c \
  52. - polkitbackendjsauthority.h polkitbackendjsauthority.cpp \
  53. + polkitbackendjsauthority.h \
  54. polkitbackendactionpool.h polkitbackendactionpool.c \
  55. polkitbackendactionlookup.h polkitbackendactionlookup.c \
  56. $(NULL)
  57. @@ -51,19 +51,27 @@ libpolkit_backend_1_la_CFLAGS = \
  58. -D_POLKIT_BACKEND_COMPILATION \
  59. $(GLIB_CFLAGS) \
  60. $(LIBSYSTEMD_CFLAGS) \
  61. - $(LIBJS_CFLAGS) \
  62. + $(LIBJS_CFLAGS) \
  63. $(NULL)
  64. libpolkit_backend_1_la_CXXFLAGS = $(libpolkit_backend_1_la_CFLAGS)
  65. libpolkit_backend_1_la_LIBADD = \
  66. $(GLIB_LIBS) \
  67. + $(DUKTAPE_LIBS) \
  68. $(LIBSYSTEMD_LIBS) \
  69. $(top_builddir)/src/polkit/libpolkit-gobject-1.la \
  70. $(EXPAT_LIBS) \
  71. - $(LIBJS_LIBS) \
  72. + $(LIBJS_LIBS) \
  73. $(NULL)
  74. +if USE_DUKTAPE
  75. +libpolkit_backend_1_la_SOURCES += polkitbackendduktapeauthority.c
  76. +libpolkit_backend_1_la_LIBADD += -lm
  77. +else
  78. +libpolkit_backend_1_la_SOURCES += polkitbackendjsauthority.cpp
  79. +endif
  80. +
  81. rulesdir = $(sysconfdir)/polkit-1/rules.d
  82. rules_DATA = 50-default.rules
  83. diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c
  84. new file mode 100644
  85. index 0000000000000000000000000000000000000000..ae984535ed88003ab1b0965e3e109a848479c047
  86. --- /dev/null
  87. +++ b/src/polkitbackend/polkitbackendduktapeauthority.c
  88. @@ -0,0 +1,1402 @@
  89. +/*
  90. + * Copyright (C) 2008-2012 Red Hat, Inc.
  91. + * Copyright (C) 2015 Tangent Space <jstpierre@mecheye.net>
  92. + * Copyright (C) 2019 Wu Xiaotian <yetist@gmail.com>
  93. + *
  94. + * This library is free software; you can redistribute it and/or
  95. + * modify it under the terms of the GNU Lesser General Public
  96. + * License as published by the Free Software Foundation; either
  97. + * version 2 of the License, or (at your option) any later version.
  98. + *
  99. + * This library is distributed in the hope that it will be useful,
  100. + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  101. + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  102. + * Lesser General Public License for more details.
  103. + *
  104. + * You should have received a copy of the GNU Lesser General
  105. + * Public License along with this library; if not, write to the
  106. + * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  107. + * Boston, MA 02111-1307, USA.
  108. + *
  109. + * Author: David Zeuthen <davidz@redhat.com>
  110. + */
  111. +
  112. +#include "config.h"
  113. +#include <sys/wait.h>
  114. +#include <errno.h>
  115. +#include <pwd.h>
  116. +#include <grp.h>
  117. +#include <netdb.h>
  118. +#include <string.h>
  119. +#include <glib/gstdio.h>
  120. +#include <locale.h>
  121. +#include <glib/gi18n-lib.h>
  122. +
  123. +#include <polkit/polkit.h>
  124. +#include "polkitbackendjsauthority.h"
  125. +
  126. +#include <polkit/polkitprivate.h>
  127. +
  128. +#ifdef HAVE_LIBSYSTEMD
  129. +#include <systemd/sd-login.h>
  130. +#endif /* HAVE_LIBSYSTEMD */
  131. +
  132. +#include "initjs.h" /* init.js */
  133. +#include "duktape.h"
  134. +
  135. +/**
  136. + * SECTION:polkitbackendjsauthority
  137. + * @title: PolkitBackendJsAuthority
  138. + * @short_description: JS Authority
  139. + * @stability: Unstable
  140. + *
  141. + * An implementation of #PolkitBackendAuthority that reads and
  142. + * evalates Javascript files and supports interaction with
  143. + * authentication agents (virtue of being based on
  144. + * #PolkitBackendInteractiveAuthority).
  145. + */
  146. +
  147. +/* ---------------------------------------------------------------------------------------------------- */
  148. +
  149. +struct _PolkitBackendJsAuthorityPrivate
  150. +{
  151. + gchar **rules_dirs;
  152. + GFileMonitor **dir_monitors; /* NULL-terminated array of GFileMonitor instances */
  153. + duk_context *cx;
  154. +};
  155. +
  156. +#define WATCHDOG_TIMEOUT (15 * G_TIME_SPAN_SECOND)
  157. +
  158. +static void utils_spawn (const gchar *const *argv,
  159. + guint timeout_seconds,
  160. + GCancellable *cancellable,
  161. + GAsyncReadyCallback callback,
  162. + gpointer user_data);
  163. +
  164. +gboolean utils_spawn_finish (GAsyncResult *res,
  165. + gint *out_exit_status,
  166. + gchar **out_standard_output,
  167. + gchar **out_standard_error,
  168. + GError **error);
  169. +
  170. +static void on_dir_monitor_changed (GFileMonitor *monitor,
  171. + GFile *file,
  172. + GFile *other_file,
  173. + GFileMonitorEvent event_type,
  174. + gpointer user_data);
  175. +
  176. +/* ---------------------------------------------------------------------------------------------------- */
  177. +
  178. +enum
  179. +{
  180. + PROP_0,
  181. + PROP_RULES_DIRS,
  182. +};
  183. +
  184. +/* ---------------------------------------------------------------------------------------------------- */
  185. +
  186. +static GList *polkit_backend_js_authority_get_admin_auth_identities (PolkitBackendInteractiveAuthority *authority,
  187. + PolkitSubject *caller,
  188. + PolkitSubject *subject,
  189. + PolkitIdentity *user_for_subject,
  190. + gboolean subject_is_local,
  191. + gboolean subject_is_active,
  192. + const gchar *action_id,
  193. + PolkitDetails *details);
  194. +
  195. +static PolkitImplicitAuthorization polkit_backend_js_authority_check_authorization_sync (
  196. + PolkitBackendInteractiveAuthority *authority,
  197. + PolkitSubject *caller,
  198. + PolkitSubject *subject,
  199. + PolkitIdentity *user_for_subject,
  200. + gboolean subject_is_local,
  201. + gboolean subject_is_active,
  202. + const gchar *action_id,
  203. + PolkitDetails *details,
  204. + PolkitImplicitAuthorization implicit);
  205. +
  206. +G_DEFINE_TYPE (PolkitBackendJsAuthority, polkit_backend_js_authority, POLKIT_BACKEND_TYPE_INTERACTIVE_AUTHORITY);
  207. +
  208. +/* ---------------------------------------------------------------------------------------------------- */
  209. +
  210. +/* ---------------------------------------------------------------------------------------------------- */
  211. +
  212. +static void
  213. +polkit_backend_js_authority_init (PolkitBackendJsAuthority *authority)
  214. +{
  215. + authority->priv = G_TYPE_INSTANCE_GET_PRIVATE (authority,
  216. + POLKIT_BACKEND_TYPE_JS_AUTHORITY,
  217. + PolkitBackendJsAuthorityPrivate);
  218. +}
  219. +
  220. +static gint
  221. +rules_file_name_cmp (const gchar *a,
  222. + const gchar *b)
  223. +{
  224. + gint ret;
  225. + const gchar *a_base;
  226. + const gchar *b_base;
  227. +
  228. + a_base = strrchr (a, '/');
  229. + b_base = strrchr (b, '/');
  230. +
  231. + g_assert (a_base != NULL);
  232. + g_assert (b_base != NULL);
  233. + a_base += 1;
  234. + b_base += 1;
  235. +
  236. + ret = g_strcmp0 (a_base, b_base);
  237. + if (ret == 0)
  238. + {
  239. + /* /etc wins over /usr */
  240. + ret = g_strcmp0 (a, b);
  241. + g_assert (ret != 0);
  242. + }
  243. +
  244. + return ret;
  245. +}
  246. +
  247. +static void
  248. +load_scripts (PolkitBackendJsAuthority *authority)
  249. +{
  250. + duk_context *cx = authority->priv->cx;
  251. + GList *files = NULL;
  252. + GList *l;
  253. + guint num_scripts = 0;
  254. + GError *error = NULL;
  255. + guint n;
  256. +
  257. + files = NULL;
  258. +
  259. + for (n = 0; authority->priv->rules_dirs != NULL && authority->priv->rules_dirs[n] != NULL; n++)
  260. + {
  261. + const gchar *dir_name = authority->priv->rules_dirs[n];
  262. + GDir *dir = NULL;
  263. +
  264. + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
  265. + "Loading rules from directory %s",
  266. + dir_name);
  267. +
  268. + dir = g_dir_open (dir_name,
  269. + 0,
  270. + &error);
  271. + if (dir == NULL)
  272. + {
  273. + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
  274. + "Error opening rules directory: %s (%s, %d)",
  275. + error->message, g_quark_to_string (error->domain), error->code);
  276. + g_clear_error (&error);
  277. + }
  278. + else
  279. + {
  280. + const gchar *name;
  281. + while ((name = g_dir_read_name (dir)) != NULL)
  282. + {
  283. + if (g_str_has_suffix (name, ".rules"))
  284. + files = g_list_prepend (files, g_strdup_printf ("%s/%s", dir_name, name));
  285. + }
  286. + g_dir_close (dir);
  287. + }
  288. + }
  289. +
  290. + files = g_list_sort (files, (GCompareFunc) rules_file_name_cmp);
  291. +
  292. + for (l = files; l != NULL; l = l->next)
  293. + {
  294. + const gchar *filename = l->data;
  295. +
  296. +#if (DUK_VERSION >= 20000)
  297. + gchar *contents;
  298. + gsize length;
  299. + GError *error = NULL;
  300. + if (!g_file_get_contents (filename, &contents, &length, &error)){
  301. + g_warning("Error when file contents of %s: %s\n", filename, error->message);
  302. + g_error_free (error);
  303. + continue;
  304. + }
  305. + if (duk_peval_lstring_noresult(cx, contents,length) != 0)
  306. +#else
  307. + if (duk_peval_file_noresult (cx, filename) != 0)
  308. +#endif
  309. + {
  310. + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
  311. + "Error compiling script %s: %s",
  312. + filename, duk_safe_to_string (authority->priv->cx, -1));
  313. +#if (DUK_VERSION >= 20000)
  314. + g_free (contents);
  315. +#endif
  316. + continue;
  317. + }
  318. +#if (DUK_VERSION >= 20000)
  319. + g_free (contents);
  320. +#endif
  321. + num_scripts++;
  322. + }
  323. +
  324. + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
  325. + "Finished loading, compiling and executing %d rules",
  326. + num_scripts);
  327. + g_list_free_full (files, g_free);
  328. +}
  329. +
  330. +static void
  331. +reload_scripts (PolkitBackendJsAuthority *authority)
  332. +{
  333. + duk_context *cx = authority->priv->cx;
  334. +
  335. + duk_set_top (cx, 0);
  336. + duk_get_global_string (cx, "polkit");
  337. + duk_push_string (cx, "_deleteRules");
  338. +
  339. + duk_call_prop (cx, 0, 0);
  340. +
  341. + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
  342. + "Collecting garbage unconditionally...");
  343. +
  344. + load_scripts (authority);
  345. +
  346. + /* Let applications know we have new rules... */
  347. + g_signal_emit_by_name (authority, "changed");
  348. +}
  349. +
  350. +static void
  351. +on_dir_monitor_changed (GFileMonitor *monitor,
  352. + GFile *file,
  353. + GFile *other_file,
  354. + GFileMonitorEvent event_type,
  355. + gpointer user_data)
  356. +{
  357. + PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (user_data);
  358. +
  359. + /* TODO: maybe rate-limit so storms of events are collapsed into one with a 500ms resolution?
  360. + * Because when editing a file with emacs we get 4-8 events..
  361. + */
  362. +
  363. + if (file != NULL)
  364. + {
  365. + gchar *name;
  366. +
  367. + name = g_file_get_basename (file);
  368. +
  369. + /* g_print ("event_type=%d file=%p name=%s\n", event_type, file, name); */
  370. + if (!g_str_has_prefix (name, ".") &&
  371. + !g_str_has_prefix (name, "#") &&
  372. + g_str_has_suffix (name, ".rules") &&
  373. + (event_type == G_FILE_MONITOR_EVENT_CREATED ||
  374. + event_type == G_FILE_MONITOR_EVENT_DELETED ||
  375. + event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT))
  376. + {
  377. + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
  378. + "Reloading rules");
  379. + reload_scripts (authority);
  380. + }
  381. + g_free (name);
  382. + }
  383. +}
  384. +
  385. +
  386. +static void
  387. +setup_file_monitors (PolkitBackendJsAuthority *authority)
  388. +{
  389. + guint n;
  390. + GPtrArray *p;
  391. +
  392. + p = g_ptr_array_new ();
  393. + for (n = 0; authority->priv->rules_dirs != NULL && authority->priv->rules_dirs[n] != NULL; n++)
  394. + {
  395. + GFile *file;
  396. + GError *error;
  397. + GFileMonitor *monitor;
  398. +
  399. + file = g_file_new_for_path (authority->priv->rules_dirs[n]);
  400. + error = NULL;
  401. + monitor = g_file_monitor_directory (file,
  402. + G_FILE_MONITOR_NONE,
  403. + NULL,
  404. + &error);
  405. + g_object_unref (file);
  406. + if (monitor == NULL)
  407. + {
  408. + g_warning ("Error monitoring directory %s: %s",
  409. + authority->priv->rules_dirs[n],
  410. + error->message);
  411. + g_clear_error (&error);
  412. + }
  413. + else
  414. + {
  415. + g_signal_connect (monitor,
  416. + "changed",
  417. + G_CALLBACK (on_dir_monitor_changed),
  418. + authority);
  419. + g_ptr_array_add (p, monitor);
  420. + }
  421. + }
  422. + g_ptr_array_add (p, NULL);
  423. + authority->priv->dir_monitors = (GFileMonitor**) g_ptr_array_free (p, FALSE);
  424. +}
  425. +
  426. +static duk_ret_t js_polkit_log (duk_context *cx);
  427. +static duk_ret_t js_polkit_spawn (duk_context *cx);
  428. +static duk_ret_t js_polkit_user_is_in_netgroup (duk_context *cx);
  429. +
  430. +static const duk_function_list_entry js_polkit_functions[] =
  431. +{
  432. + { "log", js_polkit_log, 1 },
  433. + { "spawn", js_polkit_spawn, 1 },
  434. + { "_userIsInNetGroup", js_polkit_user_is_in_netgroup, 2 },
  435. + { NULL, NULL, 0 },
  436. +};
  437. +
  438. +static void
  439. +polkit_backend_js_authority_constructed (GObject *object)
  440. +{
  441. + PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object);
  442. + duk_context *cx;
  443. +
  444. + cx = duk_create_heap (NULL, NULL, NULL, authority, NULL);
  445. + if (cx == NULL)
  446. + goto fail;
  447. +
  448. + authority->priv->cx = cx;
  449. +
  450. + duk_push_global_object (cx);
  451. + duk_push_object (cx);
  452. + duk_put_function_list (cx, -1, js_polkit_functions);
  453. + duk_put_prop_string (cx, -2, "polkit");
  454. +
  455. + duk_eval_string (cx, init_js);
  456. +
  457. + if (authority->priv->rules_dirs == NULL)
  458. + {
  459. + authority->priv->rules_dirs = g_new0 (gchar *, 3);
  460. + authority->priv->rules_dirs[0] = g_strdup (PACKAGE_SYSCONF_DIR "/polkit-1/rules.d");
  461. + authority->priv->rules_dirs[1] = g_strdup (PACKAGE_DATA_DIR "/polkit-1/rules.d");
  462. + }
  463. +
  464. + setup_file_monitors (authority);
  465. + load_scripts (authority);
  466. +
  467. + G_OBJECT_CLASS (polkit_backend_js_authority_parent_class)->constructed (object);
  468. + return;
  469. +
  470. + fail:
  471. + g_critical ("Error initializing JavaScript environment");
  472. + g_assert_not_reached ();
  473. +}
  474. +
  475. +static void
  476. +polkit_backend_js_authority_finalize (GObject *object)
  477. +{
  478. + PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object);
  479. + guint n;
  480. +
  481. + for (n = 0; authority->priv->dir_monitors != NULL && authority->priv->dir_monitors[n] != NULL; n++)
  482. + {
  483. + GFileMonitor *monitor = authority->priv->dir_monitors[n];
  484. + g_signal_handlers_disconnect_by_func (monitor,
  485. + G_CALLBACK (on_dir_monitor_changed),
  486. + authority);
  487. + g_object_unref (monitor);
  488. + }
  489. + g_free (authority->priv->dir_monitors);
  490. + g_strfreev (authority->priv->rules_dirs);
  491. +
  492. + duk_destroy_heap (authority->priv->cx);
  493. +
  494. + G_OBJECT_CLASS (polkit_backend_js_authority_parent_class)->finalize (object);
  495. +}
  496. +
  497. +static void
  498. +polkit_backend_js_authority_set_property (GObject *object,
  499. + guint property_id,
  500. + const GValue *value,
  501. + GParamSpec *pspec)
  502. +{
  503. + PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object);
  504. +
  505. + switch (property_id)
  506. + {
  507. + case PROP_RULES_DIRS:
  508. + g_assert (authority->priv->rules_dirs == NULL);
  509. + authority->priv->rules_dirs = (gchar **) g_value_dup_boxed (value);
  510. + break;
  511. +
  512. + default:
  513. + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
  514. + break;
  515. + }
  516. +}
  517. +
  518. +static const gchar *
  519. +polkit_backend_js_authority_get_name (PolkitBackendAuthority *authority)
  520. +{
  521. + return "js";
  522. +}
  523. +
  524. +static const gchar *
  525. +polkit_backend_js_authority_get_version (PolkitBackendAuthority *authority)
  526. +{
  527. + return PACKAGE_VERSION;
  528. +}
  529. +
  530. +static PolkitAuthorityFeatures
  531. +polkit_backend_js_authority_get_features (PolkitBackendAuthority *authority)
  532. +{
  533. + return POLKIT_AUTHORITY_FEATURES_TEMPORARY_AUTHORIZATION;
  534. +}
  535. +
  536. +static void
  537. +polkit_backend_js_authority_class_init (PolkitBackendJsAuthorityClass *klass)
  538. +{
  539. + GObjectClass *gobject_class;
  540. + PolkitBackendAuthorityClass *authority_class;
  541. + PolkitBackendInteractiveAuthorityClass *interactive_authority_class;
  542. +
  543. +
  544. + gobject_class = G_OBJECT_CLASS (klass);
  545. + gobject_class->finalize = polkit_backend_js_authority_finalize;
  546. + gobject_class->set_property = polkit_backend_js_authority_set_property;
  547. + gobject_class->constructed = polkit_backend_js_authority_constructed;
  548. +
  549. + authority_class = POLKIT_BACKEND_AUTHORITY_CLASS (klass);
  550. + authority_class->get_name = polkit_backend_js_authority_get_name;
  551. + authority_class->get_version = polkit_backend_js_authority_get_version;
  552. + authority_class->get_features = polkit_backend_js_authority_get_features;
  553. +
  554. + interactive_authority_class = POLKIT_BACKEND_INTERACTIVE_AUTHORITY_CLASS (klass);
  555. + interactive_authority_class->get_admin_identities = polkit_backend_js_authority_get_admin_auth_identities;
  556. + interactive_authority_class->check_authorization_sync = polkit_backend_js_authority_check_authorization_sync;
  557. +
  558. + g_object_class_install_property (gobject_class,
  559. + PROP_RULES_DIRS,
  560. + g_param_spec_boxed ("rules-dirs",
  561. + NULL,
  562. + NULL,
  563. + G_TYPE_STRV,
  564. + G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
  565. +
  566. +
  567. + g_type_class_add_private (klass, sizeof (PolkitBackendJsAuthorityPrivate));
  568. +}
  569. +
  570. +/* ---------------------------------------------------------------------------------------------------- */
  571. +
  572. +static void
  573. +set_property_str (duk_context *cx,
  574. + const gchar *name,
  575. + const gchar *value)
  576. +{
  577. + duk_push_string (cx, value);
  578. + duk_put_prop_string (cx, -2, name);
  579. +}
  580. +
  581. +static void
  582. +set_property_strv (duk_context *cx,
  583. + const gchar *name,
  584. + GPtrArray *value)
  585. +{
  586. + guint n;
  587. + duk_push_array (cx);
  588. + for (n = 0; n < value->len; n++)
  589. + {
  590. + duk_push_string (cx, g_ptr_array_index (value, n));
  591. + duk_put_prop_index (cx, -2, n);
  592. + }
  593. + duk_put_prop_string (cx, -2, name);
  594. +}
  595. +
  596. +static void
  597. +set_property_int32 (duk_context *cx,
  598. + const gchar *name,
  599. + gint32 value)
  600. +{
  601. + duk_push_int (cx, value);
  602. + duk_put_prop_string (cx, -2, name);
  603. +}
  604. +
  605. +static void
  606. +set_property_bool (duk_context *cx,
  607. + const char *name,
  608. + gboolean value)
  609. +{
  610. + duk_push_boolean (cx, value);
  611. + duk_put_prop_string (cx, -2, name);
  612. +}
  613. +
  614. +/* ---------------------------------------------------------------------------------------------------- */
  615. +
  616. +static gboolean
  617. +push_subject (duk_context *cx,
  618. + PolkitSubject *subject,
  619. + PolkitIdentity *user_for_subject,
  620. + gboolean subject_is_local,
  621. + gboolean subject_is_active,
  622. + GError **error)
  623. +{
  624. + gboolean ret = FALSE;
  625. + pid_t pid;
  626. + uid_t uid;
  627. + gchar *user_name = NULL;
  628. + GPtrArray *groups = NULL;
  629. + struct passwd *passwd;
  630. + char *seat_str = NULL;
  631. + char *session_str = NULL;
  632. +
  633. + duk_get_global_string (cx, "Subject");
  634. + duk_new (cx, 0);
  635. +
  636. + if (POLKIT_IS_UNIX_PROCESS (subject))
  637. + {
  638. + pid = polkit_unix_process_get_pid (POLKIT_UNIX_PROCESS (subject));
  639. + }
  640. + else if (POLKIT_IS_SYSTEM_BUS_NAME (subject))
  641. + {
  642. + PolkitSubject *process;
  643. + process = polkit_system_bus_name_get_process_sync (POLKIT_SYSTEM_BUS_NAME (subject), NULL, error);
  644. + if (process == NULL)
  645. + goto out;
  646. + pid = polkit_unix_process_get_pid (POLKIT_UNIX_PROCESS (process));
  647. + g_object_unref (process);
  648. + }
  649. + else
  650. + {
  651. + g_assert_not_reached ();
  652. + }
  653. +
  654. +#ifdef HAVE_LIBSYSTEMD
  655. + if (sd_pid_get_session (pid, &session_str) == 0)
  656. + {
  657. + if (sd_session_get_seat (session_str, &seat_str) == 0)
  658. + {
  659. + /* do nothing */
  660. + }
  661. + }
  662. +#endif /* HAVE_LIBSYSTEMD */
  663. +
  664. + g_assert (POLKIT_IS_UNIX_USER (user_for_subject));
  665. + uid = polkit_unix_user_get_uid (POLKIT_UNIX_USER (user_for_subject));
  666. +
  667. + groups = g_ptr_array_new_with_free_func (g_free);
  668. +
  669. + passwd = getpwuid (uid);
  670. + if (passwd == NULL)
  671. + {
  672. + user_name = g_strdup_printf ("%d", (gint) uid);
  673. + g_warning ("Error looking up info for uid %d: %m", (gint) uid);
  674. + }
  675. + else
  676. + {
  677. + gid_t gids[512];
  678. + int num_gids = 512;
  679. +
  680. + user_name = g_strdup (passwd->pw_name);
  681. +
  682. + if (getgrouplist (passwd->pw_name,
  683. + passwd->pw_gid,
  684. + gids,
  685. + &num_gids) < 0)
  686. + {
  687. + g_warning ("Error looking up groups for uid %d: %m", (gint) uid);
  688. + }
  689. + else
  690. + {
  691. + gint n;
  692. + for (n = 0; n < num_gids; n++)
  693. + {
  694. + struct group *group;
  695. + group = getgrgid (gids[n]);
  696. + if (group == NULL)
  697. + {
  698. + g_ptr_array_add (groups, g_strdup_printf ("%d", (gint) gids[n]));
  699. + }
  700. + else
  701. + {
  702. + g_ptr_array_add (groups, g_strdup (group->gr_name));
  703. + }
  704. + }
  705. + }
  706. + }
  707. +
  708. + set_property_int32 (cx, "pid", pid);
  709. + set_property_str (cx, "user", user_name);
  710. + set_property_strv (cx, "groups", groups);
  711. + set_property_str (cx, "seat", seat_str);
  712. + set_property_str (cx, "session", session_str);
  713. + set_property_bool (cx, "local", subject_is_local);
  714. + set_property_bool (cx, "active", subject_is_active);
  715. +
  716. + ret = TRUE;
  717. +
  718. + out:
  719. + free (session_str);
  720. + free (seat_str);
  721. + g_free (user_name);
  722. + if (groups != NULL)
  723. + g_ptr_array_unref (groups);
  724. +
  725. + return ret;
  726. +}
  727. +
  728. +/* ---------------------------------------------------------------------------------------------------- */
  729. +
  730. +static gboolean
  731. +push_action_and_details (duk_context *cx,
  732. + const gchar *action_id,
  733. + PolkitDetails *details,
  734. + GError **error)
  735. +{
  736. + gchar **keys;
  737. + guint n;
  738. +
  739. + duk_get_global_string (cx, "Action");
  740. + duk_new (cx, 0);
  741. +
  742. + set_property_str (cx, "id", action_id);
  743. +
  744. + keys = polkit_details_get_keys (details);
  745. + for (n = 0; keys != NULL && keys[n] != NULL; n++)
  746. + {
  747. + gchar *key;
  748. + const gchar *value;
  749. + key = g_strdup_printf ("_detail_%s", keys[n]);
  750. + value = polkit_details_lookup (details, keys[n]);
  751. + set_property_str (cx, key, value);
  752. + g_free (key);
  753. + }
  754. + g_strfreev (keys);
  755. +
  756. + return TRUE;
  757. +}
  758. +
  759. +/* ---------------------------------------------------------------------------------------------------- */
  760. +
  761. +/* ---------------------------------------------------------------------------------------------------- */
  762. +
  763. +static GList *
  764. +polkit_backend_js_authority_get_admin_auth_identities (PolkitBackendInteractiveAuthority *_authority,
  765. + PolkitSubject *caller,
  766. + PolkitSubject *subject,
  767. + PolkitIdentity *user_for_subject,
  768. + gboolean subject_is_local,
  769. + gboolean subject_is_active,
  770. + const gchar *action_id,
  771. + PolkitDetails *details)
  772. +{
  773. + PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (_authority);
  774. + GList *ret = NULL;
  775. + guint n;
  776. + GError *error = NULL;
  777. + const char *ret_str = NULL;
  778. + gchar **ret_strs = NULL;
  779. + duk_context *cx = authority->priv->cx;
  780. +
  781. + duk_set_top (cx, 0);
  782. + duk_get_global_string (cx, "polkit");
  783. + duk_push_string (cx, "_runAdminRules");
  784. +
  785. + if (!push_action_and_details (cx, action_id, details, &error))
  786. + {
  787. + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
  788. + "Error converting action and details to JS object: %s",
  789. + error->message);
  790. + g_clear_error (&error);
  791. + goto out;
  792. + }
  793. +
  794. + if (!push_subject (cx, subject, user_for_subject, subject_is_local, subject_is_active, &error))
  795. + {
  796. + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
  797. + "Error converting subject to JS object: %s",
  798. + error->message);
  799. + g_clear_error (&error);
  800. + goto out;
  801. + }
  802. +
  803. + if (duk_pcall_prop (cx, 0, 2) != DUK_ERR_NONE)
  804. + {
  805. + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
  806. + "Error evaluating admin rules: ",
  807. + duk_safe_to_string (cx, -1));
  808. + goto out;
  809. + }
  810. +
  811. + ret_str = duk_require_string (cx, -1);
  812. +
  813. + ret_strs = g_strsplit (ret_str, ",", -1);
  814. + for (n = 0; ret_strs != NULL && ret_strs[n] != NULL; n++)
  815. + {
  816. + const gchar *identity_str = ret_strs[n];
  817. + PolkitIdentity *identity;
  818. +
  819. + error = NULL;
  820. + identity = polkit_identity_from_string (identity_str, &error);
  821. + if (identity == NULL)
  822. + {
  823. + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
  824. + "Identity `%s' is not valid, ignoring: %s",
  825. + identity_str, error->message);
  826. + g_clear_error (&error);
  827. + }
  828. + else
  829. + {
  830. + ret = g_list_prepend (ret, identity);
  831. + }
  832. + }
  833. + ret = g_list_reverse (ret);
  834. +
  835. + out:
  836. + g_strfreev (ret_strs);
  837. + /* fallback to root password auth */
  838. + if (ret == NULL)
  839. + ret = g_list_prepend (ret, polkit_unix_user_new (0));
  840. +
  841. + return ret;
  842. +}
  843. +
  844. +/* ---------------------------------------------------------------------------------------------------- */
  845. +
  846. +static PolkitImplicitAuthorization
  847. +polkit_backend_js_authority_check_authorization_sync (PolkitBackendInteractiveAuthority *_authority,
  848. + PolkitSubject *caller,
  849. + PolkitSubject *subject,
  850. + PolkitIdentity *user_for_subject,
  851. + gboolean subject_is_local,
  852. + gboolean subject_is_active,
  853. + const gchar *action_id,
  854. + PolkitDetails *details,
  855. + PolkitImplicitAuthorization implicit)
  856. +{
  857. + PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (_authority);
  858. + PolkitImplicitAuthorization ret = implicit;
  859. + GError *error = NULL;
  860. + gchar *ret_str = NULL;
  861. + gboolean good = FALSE;
  862. + duk_context *cx = authority->priv->cx;
  863. +
  864. + duk_set_top (cx, 0);
  865. + duk_get_global_string (cx, "polkit");
  866. + duk_push_string (cx, "_runRules");
  867. +
  868. + if (!push_action_and_details (cx, action_id, details, &error))
  869. + {
  870. + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
  871. + "Error converting action and details to JS object: %s",
  872. + error->message);
  873. + g_clear_error (&error);
  874. + goto out;
  875. + }
  876. +
  877. + if (!push_subject (cx, subject, user_for_subject, subject_is_local, subject_is_active, &error))
  878. + {
  879. + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
  880. + "Error converting subject to JS object: %s",
  881. + error->message);
  882. + g_clear_error (&error);
  883. + goto out;
  884. + }
  885. +
  886. + if (duk_pcall_prop (cx, 0, 2) != DUK_ERR_NONE)
  887. + {
  888. + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
  889. + "Error evaluating authorization rules: ",
  890. + duk_safe_to_string (cx, -1));
  891. + goto out;
  892. + }
  893. +
  894. + if (duk_is_null(cx, -1)) {
  895. + good = TRUE;
  896. + goto out;
  897. + }
  898. + ret_str = g_strdup (duk_require_string (cx, -1));
  899. + if (!polkit_implicit_authorization_from_string (ret_str, &ret))
  900. + {
  901. + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
  902. + "Returned result `%s' is not valid",
  903. + ret_str);
  904. + goto out;
  905. + }
  906. +
  907. + good = TRUE;
  908. +
  909. + out:
  910. + if (!good)
  911. + ret = POLKIT_IMPLICIT_AUTHORIZATION_NOT_AUTHORIZED;
  912. + g_free (ret_str);
  913. +
  914. + return ret;
  915. +}
  916. +
  917. +/* ---------------------------------------------------------------------------------------------------- */
  918. +
  919. +static duk_ret_t
  920. +js_polkit_log (duk_context *cx)
  921. +{
  922. + const char *str = duk_require_string (cx, 0);
  923. + fprintf (stderr, "%s\n", str);
  924. + return 0;
  925. +}
  926. +
  927. +/* ---------------------------------------------------------------------------------------------------- */
  928. +
  929. +static const gchar *
  930. +get_signal_name (gint signal_number)
  931. +{
  932. + switch (signal_number)
  933. + {
  934. +#define _HANDLE_SIG(sig) case sig: return #sig;
  935. + _HANDLE_SIG (SIGHUP);
  936. + _HANDLE_SIG (SIGINT);
  937. + _HANDLE_SIG (SIGQUIT);
  938. + _HANDLE_SIG (SIGILL);
  939. + _HANDLE_SIG (SIGABRT);
  940. + _HANDLE_SIG (SIGFPE);
  941. + _HANDLE_SIG (SIGKILL);
  942. + _HANDLE_SIG (SIGSEGV);
  943. + _HANDLE_SIG (SIGPIPE);
  944. + _HANDLE_SIG (SIGALRM);
  945. + _HANDLE_SIG (SIGTERM);
  946. + _HANDLE_SIG (SIGUSR1);
  947. + _HANDLE_SIG (SIGUSR2);
  948. + _HANDLE_SIG (SIGCHLD);
  949. + _HANDLE_SIG (SIGCONT);
  950. + _HANDLE_SIG (SIGSTOP);
  951. + _HANDLE_SIG (SIGTSTP);
  952. + _HANDLE_SIG (SIGTTIN);
  953. + _HANDLE_SIG (SIGTTOU);
  954. + _HANDLE_SIG (SIGBUS);
  955. +#ifdef SIGPOLL
  956. + _HANDLE_SIG (SIGPOLL);
  957. +#endif
  958. + _HANDLE_SIG (SIGPROF);
  959. + _HANDLE_SIG (SIGSYS);
  960. + _HANDLE_SIG (SIGTRAP);
  961. + _HANDLE_SIG (SIGURG);
  962. + _HANDLE_SIG (SIGVTALRM);
  963. + _HANDLE_SIG (SIGXCPU);
  964. + _HANDLE_SIG (SIGXFSZ);
  965. +#undef _HANDLE_SIG
  966. + default:
  967. + break;
  968. + }
  969. + return "UNKNOWN_SIGNAL";
  970. +}
  971. +
  972. +typedef struct
  973. +{
  974. + GMainLoop *loop;
  975. + GAsyncResult *res;
  976. +} SpawnData;
  977. +
  978. +static void
  979. +spawn_cb (GObject *source_object,
  980. + GAsyncResult *res,
  981. + gpointer user_data)
  982. +{
  983. + SpawnData *data = user_data;
  984. + data->res = g_object_ref (res);
  985. + g_main_loop_quit (data->loop);
  986. +}
  987. +
  988. +static duk_ret_t
  989. +js_polkit_spawn (duk_context *cx)
  990. +{
  991. +#if (DUK_VERSION >= 20000)
  992. + duk_ret_t ret = DUK_RET_ERROR;
  993. +#else
  994. + duk_ret_t ret = DUK_RET_INTERNAL_ERROR;
  995. +#endif
  996. + gchar *standard_output = NULL;
  997. + gchar *standard_error = NULL;
  998. + gint exit_status;
  999. + GError *error = NULL;
  1000. + guint32 array_len;
  1001. + gchar **argv = NULL;
  1002. + GMainContext *context = NULL;
  1003. + GMainLoop *loop = NULL;
  1004. + SpawnData data = {0};
  1005. + char *err_str = NULL;
  1006. + guint n;
  1007. +
  1008. + if (!duk_is_array (cx, 0))
  1009. + goto out;
  1010. +
  1011. + array_len = duk_get_length (cx, 0);
  1012. +
  1013. + argv = g_new0 (gchar*, array_len + 1);
  1014. + for (n = 0; n < array_len; n++)
  1015. + {
  1016. + duk_get_prop_index (cx, 0, n);
  1017. + argv[n] = g_strdup (duk_to_string (cx, -1));
  1018. + duk_pop (cx);
  1019. + }
  1020. +
  1021. + context = g_main_context_new ();
  1022. + loop = g_main_loop_new (context, FALSE);
  1023. +
  1024. + g_main_context_push_thread_default (context);
  1025. +
  1026. + data.loop = loop;
  1027. + utils_spawn ((const gchar *const *) argv,
  1028. + 10, /* timeout_seconds */
  1029. + NULL, /* cancellable */
  1030. + spawn_cb,
  1031. + &data);
  1032. +
  1033. + g_main_loop_run (loop);
  1034. +
  1035. + g_main_context_pop_thread_default (context);
  1036. +
  1037. + if (!utils_spawn_finish (data.res,
  1038. + &exit_status,
  1039. + &standard_output,
  1040. + &standard_error,
  1041. + &error))
  1042. + {
  1043. + err_str = g_strdup_printf ("Error spawning helper: %s (%s, %d)",
  1044. + error->message, g_quark_to_string (error->domain), error->code);
  1045. + g_clear_error (&error);
  1046. + goto out;
  1047. + }
  1048. +
  1049. + if (!(WIFEXITED (exit_status) && WEXITSTATUS (exit_status) == 0))
  1050. + {
  1051. + GString *gstr;
  1052. + gstr = g_string_new (NULL);
  1053. + if (WIFEXITED (exit_status))
  1054. + {
  1055. + g_string_append_printf (gstr,
  1056. + "Helper exited with non-zero exit status %d",
  1057. + WEXITSTATUS (exit_status));
  1058. + }
  1059. + else if (WIFSIGNALED (exit_status))
  1060. + {
  1061. + g_string_append_printf (gstr,
  1062. + "Helper was signaled with signal %s (%d)",
  1063. + get_signal_name (WTERMSIG (exit_status)),
  1064. + WTERMSIG (exit_status));
  1065. + }
  1066. + g_string_append_printf (gstr, ", stdout=`%s', stderr=`%s'",
  1067. + standard_output, standard_error);
  1068. + err_str = g_string_free (gstr, FALSE);
  1069. + goto out;
  1070. + }
  1071. +
  1072. + duk_push_string (cx, standard_output);
  1073. + ret = 1;
  1074. +
  1075. + out:
  1076. + g_strfreev (argv);
  1077. + g_free (standard_output);
  1078. + g_free (standard_error);
  1079. + g_clear_object (&data.res);
  1080. + if (loop != NULL)
  1081. + g_main_loop_unref (loop);
  1082. + if (context != NULL)
  1083. + g_main_context_unref (context);
  1084. +
  1085. + if (err_str)
  1086. + duk_error (cx, DUK_ERR_ERROR, err_str);
  1087. +
  1088. + return ret;
  1089. +}
  1090. +
  1091. +/* ---------------------------------------------------------------------------------------------------- */
  1092. +
  1093. +
  1094. +static duk_ret_t
  1095. +js_polkit_user_is_in_netgroup (duk_context *cx)
  1096. +{
  1097. + const char *user;
  1098. + const char *netgroup;
  1099. + gboolean is_in_netgroup = FALSE;
  1100. +
  1101. + user = duk_require_string (cx, 0);
  1102. + netgroup = duk_require_string (cx, 1);
  1103. +
  1104. + if (innetgr (netgroup,
  1105. + NULL, /* host */
  1106. + user,
  1107. + NULL)) /* domain */
  1108. + {
  1109. + is_in_netgroup = TRUE;
  1110. + }
  1111. +
  1112. + duk_push_boolean (cx, is_in_netgroup);
  1113. + return 1;
  1114. +}
  1115. +
  1116. +/* ---------------------------------------------------------------------------------------------------- */
  1117. +
  1118. +typedef struct
  1119. +{
  1120. + GSimpleAsyncResult *simple; /* borrowed reference */
  1121. + GMainContext *main_context; /* may be NULL */
  1122. +
  1123. + GCancellable *cancellable; /* may be NULL */
  1124. + gulong cancellable_handler_id;
  1125. +
  1126. + GPid child_pid;
  1127. + gint child_stdout_fd;
  1128. + gint child_stderr_fd;
  1129. +
  1130. + GIOChannel *child_stdout_channel;
  1131. + GIOChannel *child_stderr_channel;
  1132. +
  1133. + GSource *child_watch_source;
  1134. + GSource *child_stdout_source;
  1135. + GSource *child_stderr_source;
  1136. +
  1137. + guint timeout_seconds;
  1138. + gboolean timed_out;
  1139. + GSource *timeout_source;
  1140. +
  1141. + GString *child_stdout;
  1142. + GString *child_stderr;
  1143. +
  1144. + gint exit_status;
  1145. +} UtilsSpawnData;
  1146. +
  1147. +static void
  1148. +utils_child_watch_from_release_cb (GPid pid,
  1149. + gint status,
  1150. + gpointer user_data)
  1151. +{
  1152. +}
  1153. +
  1154. +static void
  1155. +utils_spawn_data_free (UtilsSpawnData *data)
  1156. +{
  1157. + if (data->timeout_source != NULL)
  1158. + {
  1159. + g_source_destroy (data->timeout_source);
  1160. + data->timeout_source = NULL;
  1161. + }
  1162. +
  1163. + /* Nuke the child, if necessary */
  1164. + if (data->child_watch_source != NULL)
  1165. + {
  1166. + g_source_destroy (data->child_watch_source);
  1167. + data->child_watch_source = NULL;
  1168. + }
  1169. +
  1170. + if (data->child_pid != 0)
  1171. + {
  1172. + GSource *source;
  1173. + kill (data->child_pid, SIGTERM);
  1174. + /* OK, we need to reap for the child ourselves - we don't want
  1175. + * to use waitpid() because that might block the calling
  1176. + * thread (the child might handle SIGTERM and use several
  1177. + * seconds for cleanup/rollback).
  1178. + *
  1179. + * So we use GChildWatch instead.
  1180. + *
  1181. + * Avoid taking a references to ourselves. but note that we need
  1182. + * to pass the GSource so we can nuke it once handled.
  1183. + */
  1184. + source = g_child_watch_source_new (data->child_pid);
  1185. + g_source_set_callback (source,
  1186. + (GSourceFunc) utils_child_watch_from_release_cb,
  1187. + source,
  1188. + (GDestroyNotify) g_source_destroy);
  1189. + g_source_attach (source, data->main_context);
  1190. + g_source_unref (source);
  1191. + data->child_pid = 0;
  1192. + }
  1193. +
  1194. + if (data->child_stdout != NULL)
  1195. + {
  1196. + g_string_free (data->child_stdout, TRUE);
  1197. + data->child_stdout = NULL;
  1198. + }
  1199. +
  1200. + if (data->child_stderr != NULL)
  1201. + {
  1202. + g_string_free (data->child_stderr, TRUE);
  1203. + data->child_stderr = NULL;
  1204. + }
  1205. +
  1206. + if (data->child_stdout_channel != NULL)
  1207. + {
  1208. + g_io_channel_unref (data->child_stdout_channel);
  1209. + data->child_stdout_channel = NULL;
  1210. + }
  1211. + if (data->child_stderr_channel != NULL)
  1212. + {
  1213. + g_io_channel_unref (data->child_stderr_channel);
  1214. + data->child_stderr_channel = NULL;
  1215. + }
  1216. +
  1217. + if (data->child_stdout_source != NULL)
  1218. + {
  1219. + g_source_destroy (data->child_stdout_source);
  1220. + data->child_stdout_source = NULL;
  1221. + }
  1222. + if (data->child_stderr_source != NULL)
  1223. + {
  1224. + g_source_destroy (data->child_stderr_source);
  1225. + data->child_stderr_source = NULL;
  1226. + }
  1227. +
  1228. + if (data->child_stdout_fd != -1)
  1229. + {
  1230. + g_warn_if_fail (close (data->child_stdout_fd) == 0);
  1231. + data->child_stdout_fd = -1;
  1232. + }
  1233. + if (data->child_stderr_fd != -1)
  1234. + {
  1235. + g_warn_if_fail (close (data->child_stderr_fd) == 0);
  1236. + data->child_stderr_fd = -1;
  1237. + }
  1238. +
  1239. + if (data->cancellable_handler_id > 0)
  1240. + {
  1241. + g_cancellable_disconnect (data->cancellable, data->cancellable_handler_id);
  1242. + data->cancellable_handler_id = 0;
  1243. + }
  1244. +
  1245. + if (data->main_context != NULL)
  1246. + g_main_context_unref (data->main_context);
  1247. +
  1248. + if (data->cancellable != NULL)
  1249. + g_object_unref (data->cancellable);
  1250. +
  1251. + g_slice_free (UtilsSpawnData, data);
  1252. +}
  1253. +
  1254. +/* called in the thread where @cancellable was cancelled */
  1255. +static void
  1256. +utils_on_cancelled (GCancellable *cancellable,
  1257. + gpointer user_data)
  1258. +{
  1259. + UtilsSpawnData *data = user_data;
  1260. + GError *error;
  1261. +
  1262. + error = NULL;
  1263. + g_warn_if_fail (g_cancellable_set_error_if_cancelled (cancellable, &error));
  1264. + g_simple_async_result_take_error (data->simple, error);
  1265. + g_simple_async_result_complete_in_idle (data->simple);
  1266. + g_object_unref (data->simple);
  1267. +}
  1268. +
  1269. +static gboolean
  1270. +utils_read_child_stderr (GIOChannel *channel,
  1271. + GIOCondition condition,
  1272. + gpointer user_data)
  1273. +{
  1274. + UtilsSpawnData *data = user_data;
  1275. + gchar buf[1024];
  1276. + gsize bytes_read;
  1277. +
  1278. + g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL);
  1279. + g_string_append_len (data->child_stderr, buf, bytes_read);
  1280. + return TRUE;
  1281. +}
  1282. +
  1283. +static gboolean
  1284. +utils_read_child_stdout (GIOChannel *channel,
  1285. + GIOCondition condition,
  1286. + gpointer user_data)
  1287. +{
  1288. + UtilsSpawnData *data = user_data;
  1289. + gchar buf[1024];
  1290. + gsize bytes_read;
  1291. +
  1292. + g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL);
  1293. + g_string_append_len (data->child_stdout, buf, bytes_read);
  1294. + return TRUE;
  1295. +}
  1296. +
  1297. +static void
  1298. +utils_child_watch_cb (GPid pid,
  1299. + gint status,
  1300. + gpointer user_data)
  1301. +{
  1302. + UtilsSpawnData *data = user_data;
  1303. + gchar *buf;
  1304. + gsize buf_size;
  1305. +
  1306. + if (g_io_channel_read_to_end (data->child_stdout_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL)
  1307. + {
  1308. + g_string_append_len (data->child_stdout, buf, buf_size);
  1309. + g_free (buf);
  1310. + }
  1311. + if (g_io_channel_read_to_end (data->child_stderr_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL)
  1312. + {
  1313. + g_string_append_len (data->child_stderr, buf, buf_size);
  1314. + g_free (buf);
  1315. + }
  1316. +
  1317. + data->exit_status = status;
  1318. +
  1319. + /* ok, child watch is history, make sure we don't free it in spawn_data_free() */
  1320. + data->child_pid = 0;
  1321. + data->child_watch_source = NULL;
  1322. +
  1323. + /* we're done */
  1324. + g_simple_async_result_complete_in_idle (data->simple);
  1325. + g_object_unref (data->simple);
  1326. +}
  1327. +
  1328. +static gboolean
  1329. +utils_timeout_cb (gpointer user_data)
  1330. +{
  1331. + UtilsSpawnData *data = user_data;
  1332. +
  1333. + data->timed_out = TRUE;
  1334. +
  1335. + /* ok, timeout is history, make sure we don't free it in spawn_data_free() */
  1336. + data->timeout_source = NULL;
  1337. +
  1338. + /* we're done */
  1339. + g_simple_async_result_complete_in_idle (data->simple);
  1340. + g_object_unref (data->simple);
  1341. +
  1342. + return FALSE; /* remove source */
  1343. +}
  1344. +
  1345. +static void
  1346. +utils_spawn (const gchar *const *argv,
  1347. + guint timeout_seconds,
  1348. + GCancellable *cancellable,
  1349. + GAsyncReadyCallback callback,
  1350. + gpointer user_data)
  1351. +{
  1352. + UtilsSpawnData *data;
  1353. + GError *error;
  1354. +
  1355. + data = g_slice_new0 (UtilsSpawnData);
  1356. + data->timeout_seconds = timeout_seconds;
  1357. + data->simple = g_simple_async_result_new (NULL,
  1358. + callback,
  1359. + user_data,
  1360. + utils_spawn);
  1361. + data->main_context = g_main_context_get_thread_default ();
  1362. + if (data->main_context != NULL)
  1363. + g_main_context_ref (data->main_context);
  1364. +
  1365. + data->cancellable = cancellable != NULL ? g_object_ref (cancellable) : NULL;
  1366. +
  1367. + data->child_stdout = g_string_new (NULL);
  1368. + data->child_stderr = g_string_new (NULL);
  1369. + data->child_stdout_fd = -1;
  1370. + data->child_stderr_fd = -1;
  1371. +
  1372. + /* the life-cycle of UtilsSpawnData is tied to its GSimpleAsyncResult */
  1373. + g_simple_async_result_set_op_res_gpointer (data->simple, data, (GDestroyNotify) utils_spawn_data_free);
  1374. +
  1375. + error = NULL;
  1376. + if (data->cancellable != NULL)
  1377. + {
  1378. + /* could already be cancelled */
  1379. + error = NULL;
  1380. + if (g_cancellable_set_error_if_cancelled (data->cancellable, &error))
  1381. + {
  1382. + g_simple_async_result_take_error (data->simple, error);
  1383. + g_simple_async_result_complete_in_idle (data->simple);
  1384. + g_object_unref (data->simple);
  1385. + goto out;
  1386. + }
  1387. +
  1388. + data->cancellable_handler_id = g_cancellable_connect (data->cancellable,
  1389. + G_CALLBACK (utils_on_cancelled),
  1390. + data,
  1391. + NULL);
  1392. + }
  1393. +
  1394. + error = NULL;
  1395. + if (!g_spawn_async_with_pipes (NULL, /* working directory */
  1396. + (gchar **) argv,
  1397. + NULL, /* envp */
  1398. + G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
  1399. + NULL, /* child_setup */
  1400. + NULL, /* child_setup's user_data */
  1401. + &(data->child_pid),
  1402. + NULL, /* gint *stdin_fd */
  1403. + &(data->child_stdout_fd),
  1404. + &(data->child_stderr_fd),
  1405. + &error))
  1406. + {
  1407. + g_prefix_error (&error, "Error spawning: ");
  1408. + g_simple_async_result_take_error (data->simple, error);
  1409. + g_simple_async_result_complete_in_idle (data->simple);
  1410. + g_object_unref (data->simple);
  1411. + goto out;
  1412. + }
  1413. +
  1414. + if (timeout_seconds > 0)
  1415. + {
  1416. + data->timeout_source = g_timeout_source_new_seconds (timeout_seconds);
  1417. + g_source_set_priority (data->timeout_source, G_PRIORITY_DEFAULT);
  1418. + g_source_set_callback (data->timeout_source, utils_timeout_cb, data, NULL);
  1419. + g_source_attach (data->timeout_source, data->main_context);
  1420. + g_source_unref (data->timeout_source);
  1421. + }
  1422. +
  1423. + data->child_watch_source = g_child_watch_source_new (data->child_pid);
  1424. + g_source_set_callback (data->child_watch_source, (GSourceFunc) utils_child_watch_cb, data, NULL);
  1425. + g_source_attach (data->child_watch_source, data->main_context);
  1426. + g_source_unref (data->child_watch_source);
  1427. +
  1428. + data->child_stdout_channel = g_io_channel_unix_new (data->child_stdout_fd);
  1429. + g_io_channel_set_flags (data->child_stdout_channel, G_IO_FLAG_NONBLOCK, NULL);
  1430. + data->child_stdout_source = g_io_create_watch (data->child_stdout_channel, G_IO_IN);
  1431. + g_source_set_callback (data->child_stdout_source, (GSourceFunc) utils_read_child_stdout, data, NULL);
  1432. + g_source_attach (data->child_stdout_source, data->main_context);
  1433. + g_source_unref (data->child_stdout_source);
  1434. +
  1435. + data->child_stderr_channel = g_io_channel_unix_new (data->child_stderr_fd);
  1436. + g_io_channel_set_flags (data->child_stderr_channel, G_IO_FLAG_NONBLOCK, NULL);
  1437. + data->child_stderr_source = g_io_create_watch (data->child_stderr_channel, G_IO_IN);
  1438. + g_source_set_callback (data->child_stderr_source, (GSourceFunc) utils_read_child_stderr, data, NULL);
  1439. + g_source_attach (data->child_stderr_source, data->main_context);
  1440. + g_source_unref (data->child_stderr_source);
  1441. +
  1442. + out:
  1443. + ;
  1444. +}
  1445. +
  1446. +gboolean
  1447. +utils_spawn_finish (GAsyncResult *res,
  1448. + gint *out_exit_status,
  1449. + gchar **out_standard_output,
  1450. + gchar **out_standard_error,
  1451. + GError **error)
  1452. +{
  1453. + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
  1454. + UtilsSpawnData *data;
  1455. + gboolean ret = FALSE;
  1456. +
  1457. + g_return_val_if_fail (G_IS_ASYNC_RESULT (res), FALSE);
  1458. + g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
  1459. +
  1460. + g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == utils_spawn);
  1461. +
  1462. + if (g_simple_async_result_propagate_error (simple, error))
  1463. + goto out;
  1464. +
  1465. + data = g_simple_async_result_get_op_res_gpointer (simple);
  1466. +
  1467. + if (data->timed_out)
  1468. + {
  1469. + g_set_error (error,
  1470. + G_IO_ERROR,
  1471. + G_IO_ERROR_TIMED_OUT,
  1472. + "Timed out after %d seconds",
  1473. + data->timeout_seconds);
  1474. + goto out;
  1475. + }
  1476. +
  1477. + if (out_exit_status != NULL)
  1478. + *out_exit_status = data->exit_status;
  1479. +
  1480. + if (out_standard_output != NULL)
  1481. + *out_standard_output = g_strdup (data->child_stdout->str);
  1482. +
  1483. + if (out_standard_error != NULL)
  1484. + *out_standard_error = g_strdup (data->child_stderr->str);
  1485. +
  1486. + ret = TRUE;
  1487. +
  1488. + out:
  1489. + return ret;
  1490. +}