Skip to content
Snippets Groups Projects
Commit de031ea0 authored by Jeremy Whiting's avatar Jeremy Whiting
Browse files

Remove old c based sources since all is implemented in python now.

TODO: Add command line parsing for setting hooks path, etc. if needed
not used on steam deck at all.
parent 99022291
No related branches found
No related tags found
1 merge request!1Rewrite devkit service in python.
/*
* This file is part of steamos-devkit
* SPDX-License-Identifier: LGPL-2.1+
*
* Copyright © 2017-2018 Collabora Ltd
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this package. If not, see
* <http://www.gnu.org/licenses/>.
*/
#include "avahi-service.h"
#include <avahi-client/client.h>
#include <avahi-client/publish.h>
#include <avahi-common/alternative.h>
#include <avahi-common/error.h>
#include <avahi-common/malloc.h>
#include <avahi-common/simple-watch.h>
#include <avahi-common/timeval.h>
#include <avahi-glib/glib-malloc.h>
#include <avahi-glib/glib-watch.h>
#include <errno.h>
#include <gio/gio.h>
#include <glib/gstdio.h>
#include <string.h>
#include <unistd.h>
#include "utils.h"
#include "defines.h"
struct _DkdAvahiService
{
GObject parent_instance;
AvahiEntryGroup *group;
gchar *machine_name;
char *service_name;
uint64_t service_port;
AvahiClient *client;
AvahiGLibPoll *glib_poll;
gchar *login;
gchar *settings;
/* devkit1= prefix followed by shell-escaped arguments */
gchar *devkit1_txt;
GMainLoop *loop;
GKeyFile *state;
const gchar *state_dir;
gchar *state_subdir;
gchar *state_file;
};
struct _DkdAvahiServiceClass
{
GObjectClass parent_class;
};
G_DEFINE_TYPE (DkdAvahiService, dkd_avahi_service, G_TYPE_OBJECT)
/* Format version number for TXT record. Increment if we make an
* incompatible change that would cause current clients to parse it
* incorrectly (hopefully we will never need this). See
* https://tools.ietf.org/html/rfc6763#section-6.7 */
#define CURRENT_TXTVERS "txtvers=1"
#define COLLISION_MAX_TRY 3
#define STATE_GROUP "State"
#define STATE_KEY_MACHINE_NAME "MachineName"
#define STATE_KEY_SERVICE_NAME "ServiceName"
static gboolean dkd_avahi_service_create_services (DkdAvahiService *self,
AvahiClient *c,
GError **error);
static void
dkd_avahi_service_load_state (DkdAvahiService *self)
{
GError *error = NULL;
if (self->state == NULL)
{
self->state = g_key_file_new ();
/* Use /var/lib if we're root, or ~/.local/share otherwise */
if (getuid () == 0)
self->state_dir = DEVKIT_LOCALSTATEDIR "/lib";
else
self->state_dir = g_get_user_data_dir ();
self->state_subdir = g_build_filename (self->state_dir, PACKAGE, NULL);
self->state_file = g_build_filename (self->state_subdir, "state.ini",
NULL);
}
/* Make a best-effort attempt to load state, but mostly ignore any errors:
* a missing or malformed state file is equivalent to no state having
* been saved at all */
if (!g_key_file_load_from_file (self->state, self->state_file,
G_KEY_FILE_NONE, &error))
{
g_debug ("Unable to load \"%s\": %s",
self->state_file, error->message);
g_clear_error (&error);
}
}
static void
dkd_avahi_service_save_state (DkdAvahiService *self)
{
GError *error = NULL;
gchar **groups;
gsize n_groups = 0;
g_return_if_fail (self->state != NULL);
groups = g_key_file_get_groups (self->state, &n_groups);
if (n_groups == 0)
{
/* There is no state, so delete the file instead of creating it */
g_debug ("No state to save: deleting \"%s\"", self->state_file);
if (g_unlink (self->state_file) < 0 && errno != ENOENT)
g_warning ("Unable to delete \"%s\": %s", self->state_file,
g_strerror (errno));
if (g_rmdir (self->state_subdir) < 0 && errno != ENOENT &&
errno != ENOTEMPTY)
g_warning ("Unable to delete \"%s\": %s", self->state_subdir,
g_strerror (errno));
return;
}
g_strfreev (groups);
g_debug ("Saving state to \"%s\"", self->state_file);
if (g_mkdir_with_parents (self->state_subdir, 0700) < 0 &&
errno != ENOENT)
{
g_warning ("Unable to create \"%s\": %s", self->state_subdir,
g_strerror (errno));
return;
}
if (!g_key_file_save_to_file (self->state, self->state_file, &error))
{
g_warning ("%s", error->message);
g_clear_error (&error);
}
}
static void
dkd_avahi_service_switch_to_alternative (DkdAvahiService *self)
{
char *n;
n = avahi_alternative_service_name (self->service_name);
avahi_free (self->service_name);
self->service_name = n;
g_debug ("Service name collision, renaming service to '%s'",
self->service_name);
/* Try to save the name we intended to use, and the name we actually
* used, so that we'll use the same fallback in future as mandated by
* https://tools.ietf.org/html/rfc6762#section-9,
* https://tools.ietf.org/html/rfc6763#appendix-D */
dkd_avahi_service_load_state (self);
g_key_file_set_string (self->state, STATE_GROUP,
STATE_KEY_MACHINE_NAME, self->machine_name);
g_key_file_set_string (self->state, STATE_GROUP,
STATE_KEY_SERVICE_NAME, self->service_name);
dkd_avahi_service_save_state (self);
}
static void
entry_group_callback (AvahiEntryGroup * g, AvahiEntryGroupState state,
gpointer userdata)
{
DkdAvahiService *self = DKD_AVAHI_SERVICE (userdata);
GError *error = NULL;
assert (g == self->group || self->group == NULL);
self->group = g;
switch (state)
{
case AVAHI_ENTRY_GROUP_ESTABLISHED:
g_debug ("Service '%s' successfully established.", self->service_name);
break;
case AVAHI_ENTRY_GROUP_COLLISION:
{
dkd_avahi_service_switch_to_alternative (self);
if (!dkd_avahi_service_create_services (self, self->client, &error))
{
g_warning ("%s", error->message);
g_main_loop_quit (self->loop);
}
break;
}
case AVAHI_ENTRY_GROUP_FAILURE:
g_debug ("Entry group failure: %s",
avahi_strerror (avahi_client_errno (self->client)));
g_main_loop_quit (self->loop);
break;
case AVAHI_ENTRY_GROUP_UNCOMMITED:
case AVAHI_ENTRY_GROUP_REGISTERING:
default:
;
}
}
static gboolean
dkd_avahi_service_create_services (DkdAvahiService *self,
AvahiClient *c,
GError **error)
{
g_return_val_if_fail (DKD_IS_AVAHI_SERVICE (self), FALSE);
g_return_val_if_fail (c != NULL, FALSE);
if (!self->group)
{
if (!(self->group = avahi_entry_group_new (c, entry_group_callback, self)))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"avahi_entry_group_new() failed: %s",
avahi_strerror (avahi_client_errno (c)));
goto fail;
}
}
if (avahi_entry_group_is_empty (self->group))
{
gchar *login_pair;
gchar *settings_pair;
int try;
g_debug ("Adding service '%s'", self->service_name);
login_pair = g_strdup_printf ("login=%s", self->login);
settings_pair = g_strdup_printf ("settings=%s", self->settings);
for (try = 0; try < COLLISION_MAX_TRY; try++)
{
int ret;
if ((ret =
avahi_entry_group_add_service (self->group, AVAHI_IF_UNSPEC,
AVAHI_PROTO_UNSPEC, 0,
self->service_name,
"_steamos-devkit._tcp", NULL,
NULL,
self->service_port,
CURRENT_TXTVERS,
login_pair,
self->devkit1_txt,
settings_pair,
NULL)) < 0)
{
if (ret == AVAHI_ERR_COLLISION)
{
dkd_avahi_service_switch_to_alternative (self);
continue;
}
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to add _steamos-devkit._tcp service: %s",
avahi_strerror (ret));
goto fail;
}
if ((ret = avahi_entry_group_commit (self->group)) < 0)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to commit entry group: %s",
avahi_strerror (ret));
goto fail;
}
break;
}
g_free (login_pair);
g_free (settings_pair);
if (try >= COLLISION_MAX_TRY)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Too many name collisions");
goto fail;
}
}
return TRUE;
fail:
return FALSE;
}
static void
client_state_changed_cb (AvahiClient * c, AvahiClientState state,
void *userdata)
{
DkdAvahiService *self = DKD_AVAHI_SERVICE (userdata);
GError *error = NULL;
assert (c);
assert (c == self->client || self->client == NULL);
switch (state)
{
case AVAHI_CLIENT_S_RUNNING:
if (!dkd_avahi_service_create_services (self, c, &error))
{
g_warning ("%s", error->message);
g_main_loop_quit (self->loop);
}
break;
case AVAHI_CLIENT_FAILURE:
g_warning ("Client failure: %s",
avahi_strerror (avahi_client_errno (c)));
g_main_loop_quit (self->loop);
break;
case AVAHI_CLIENT_S_COLLISION:
case AVAHI_CLIENT_S_REGISTERING:
if (self->group)
avahi_entry_group_reset (self->group);
break;
case AVAHI_CLIENT_CONNECTING:
default:
;
}
}
gboolean
dkd_avahi_service_start (DkdAvahiService *self,
const gchar *machine_name,
uint64_t server_port,
const gchar *entry_point_user,
const gchar * const *devkit1_argv,
const gchar *settings_json,
GMainLoop *loop,
GError **error)
{
int err = 0;
const AvahiPoll *poll_api = NULL;
if (!dkd_avahi_service_reconfigure (self, machine_name, server_port,
entry_point_user, devkit1_argv, settings_json,
error))
return FALSE;
poll_api = avahi_glib_poll_get (self->glib_poll);
self->client =
avahi_client_new (poll_api, 0, client_state_changed_cb, self, &err);
self->loop = g_main_loop_ref (loop);
if (self->client == NULL)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Error initializing Avahi: %s", avahi_strerror (err));
return FALSE;
}
return TRUE;
}
gboolean
dkd_avahi_service_reconfigure (DkdAvahiService *self,
const gchar *machine_name,
uint64_t server_port,
const gchar *entry_point_user,
const gchar * const *devkit1_argv,
const gchar *settings_json,
GError **error)
{
gboolean changed_name = FALSE;
gboolean changed_txt = FALSE;
GString *buffer;
if (self->machine_name == NULL ||
strcmp (self->machine_name, machine_name) != 0)
{
gchar *temp;
g_free (self->machine_name);
self->machine_name = g_strdup (machine_name);
temp = dk_sanitize_machine_name (machine_name);
avahi_free (self->service_name);
self->service_name = avahi_strdup (temp);
g_free (temp);
g_debug ("Machine name \"%s\" -> service name \"%s\"",
self->machine_name, self->service_name);
/* If the machine name matches what we were trying to use last
* time, substitute the alternative that we used last time, if
* any. This means that once we've renamed a machine from
* "Foocorp GameMachine X100" to "Foocorp GameMachine X100 #3",
* we'll keep that name indefinitely, meaning we don't need to
* put serial numbers in machine names to get a reasonable UX.
* See https://tools.ietf.org/html/rfc6762#section-9,
* https://tools.ietf.org/html/rfc6763#appendix-D
*
* This behaviour also means that if we change how
* dk_sanitize_machine_name() works, existing machines will keep
* their current names until reconfigured, so client state
* won't be invalidated. */
dkd_avahi_service_load_state (self);
temp = g_key_file_get_string (self->state, STATE_GROUP,
STATE_KEY_MACHINE_NAME, NULL);
if (temp != NULL && strcmp (temp, machine_name) == 0)
{
g_free (temp);
temp = g_key_file_get_string (self->state, STATE_GROUP,
STATE_KEY_SERVICE_NAME, NULL);
if (temp != NULL && strcmp (temp, self->service_name) != 0)
{
g_debug ("Using stored service name \"%s\" instead",
temp);
avahi_free (self->service_name);
self->service_name = avahi_strdup (temp);
}
}
else if (strcmp (machine_name, self->service_name) == 0)
{
/* In the common case where the machine name is the same as
* the service name, don't bother storing either. */
g_debug ("Machine name matches service name: removing "
"saved collision state");
g_key_file_remove_group (self->state, STATE_GROUP, NULL);
dkd_avahi_service_save_state (self);
}
else
{
/* Store the new machine name and the resulting service name */
g_debug ("Saving remapped name");
g_key_file_set_string (self->state, STATE_GROUP,
STATE_KEY_MACHINE_NAME, self->machine_name);
g_key_file_set_string (self->state, STATE_GROUP,
STATE_KEY_SERVICE_NAME, self->service_name);
dkd_avahi_service_save_state (self);
}
g_free (temp);
changed_name = TRUE;
}
if (self->login == NULL ||
strcmp (self->login, entry_point_user) != 0)
{
g_free (self->login);
self->login = g_strdup (entry_point_user);
changed_txt = TRUE;
}
if (self->settings == NULL ||
strcmp (self->settings, settings_json) != 0)
{
g_free (self->settings);
self->settings = g_strdup (settings_json);
changed_txt = TRUE;
}
if (self->service_port != server_port)
{
self->service_port = server_port;
}
buffer = g_string_new ("devkit1=");
if (devkit1_argv == NULL || devkit1_argv[0] == NULL)
{
g_string_append (buffer, "devkit-1");
}
else
{
const gchar *const *iter;
for (iter = devkit1_argv; *iter != NULL; iter++)
{
gchar *tmp = g_shell_quote (*iter);
if (buffer->len > strlen ("devkit1="))
g_string_append_c (buffer, ' ');
g_string_append (buffer, tmp);
g_free (tmp);
}
}
if (self->devkit1_txt == NULL ||
strcmp (self->devkit1_txt, buffer->str) != 0)
{
g_free (self->devkit1_txt);
self->devkit1_txt = g_string_free (buffer, FALSE);
changed_txt = TRUE;
}
else
{
g_string_free (buffer, TRUE);
}
if (self->client != NULL &&
avahi_client_get_state (self->client) == AVAHI_CLIENT_S_RUNNING)
{
if (changed_name || (changed_txt && self->group == NULL))
{
if (self->group)
avahi_entry_group_reset (self->group);
if (!dkd_avahi_service_create_services (self, self->client, error))
{
g_prefix_error (error, "Unable to publish new service name: ");
return FALSE;
}
}
else if (changed_txt)
{
int code;
gchar *login_pair;
gchar *settings_pair;
login_pair = g_strdup_printf ("login=%s", self->login);
settings_pair = g_strdup_printf ("settings=%s", self->settings);
code = avahi_entry_group_update_service_txt (self->group,
AVAHI_IF_UNSPEC,
AVAHI_PROTO_UNSPEC,
0,
self->service_name,
"_steamos-devkit._tcp",
NULL,
CURRENT_TXTVERS,
login_pair,
self->devkit1_txt,
settings_pair,
NULL);
g_free (login_pair);
g_free (settings_pair);
if (code < 0)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to update DNS-SD TXT record: %s",
avahi_strerror (code));
return FALSE;
}
}
}
return TRUE;
}
/* For some reason avahi_entry_group_free() returns int, which means
* gcc 8 doesn't like to cast it as a GDestroyNotify */
static void
dkd_avahi_entry_group_free (AvahiEntryGroup *self)
{
avahi_entry_group_free (self);
}
void
dkd_avahi_service_stop (DkdAvahiService *self)
{
g_clear_pointer (&self->group, dkd_avahi_entry_group_free);
g_clear_pointer (&self->client, avahi_client_free);
g_clear_pointer (&self->glib_poll, avahi_glib_poll_free);
g_clear_pointer (&self->service_name, avahi_free);
g_clear_pointer (&self->login, g_free);
g_clear_pointer (&self->loop, g_main_loop_unref);
}
static void
dkd_avahi_service_init (DkdAvahiService *self)
{
self->service_name = NULL;
self->service_port = SERVICE_PORT;
self->glib_poll = avahi_glib_poll_new (NULL, G_PRIORITY_DEFAULT);
self->client = NULL;
}
static void
dkd_avahi_service_finalize (GObject *object)
{
DkdAvahiService *self = DKD_AVAHI_SERVICE (object);
dkd_avahi_service_stop (self);
g_clear_pointer (&self->state, g_key_file_unref);
g_clear_pointer (&self->machine_name, g_free);
g_clear_pointer (&self->state_subdir, g_free);
g_clear_pointer (&self->state_file, g_free);
G_OBJECT_CLASS (dkd_avahi_service_parent_class)->finalize (object);
}
static void
dkd_avahi_service_class_init (DkdAvahiServiceClass *cls)
{
GObjectClass *object_class = G_OBJECT_CLASS (cls);
avahi_set_allocator (avahi_glib_allocator ());
object_class->finalize = dkd_avahi_service_finalize;
}
DkdAvahiService *
dkd_avahi_service_new (void)
{
return g_object_new (DKD_TYPE_AVAHI_SERVICE,
NULL);
}
/*
* This file is part of steamos-devkit
* SPDX-License-Identifier: LGPL-2.1+
*
* Copyright © 2017 Collabora Ltd
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this package. If not, see
* <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <glib.h>
#include <glib-object.h>
#include <stdint.h>
typedef struct _DkdAvahiService DkdAvahiService;
typedef struct _DkdAvahiServiceClass DkdAvahiServiceClass;
#define DKD_TYPE_AVAHI_SERVICE (dkd_avahi_service_get_type ())
#define DKD_AVAHI_SERVICE(obj) \
G_TYPE_CHECK_INSTANCE_CAST ((obj), DKD_TYPE_AVAHI_SERVICE, DkdAvahiService)
#define DKD_AVAHI_SERVICE_CLASS(cls) \
G_TYPE_CHECK_CLASS_CAST ((cls), DKD_TYPE_AVAHI_SERVICE, DkdAvahiServiceClass)
#define DKD_IS_AVAHI_SERVICE(obj) \
G_TYPE_CHECK_INSTANCE_TYPE ((obj), DKD_TYPE_AVAHI_SERVICE)
#define DKD_IS_AVAHI_SERVICE_CLASS(cls) \
G_TYPE_CHECK_CLASS_TYPE ((cls), DKD_TYPE_AVAHI_SERVICE)
#define DKD_AVAHI_SERVICE_GET_CLASS(obj) \
G_TYPE_INSTANCE_GET_CLASS ((obj), DKD_TYPE_AVAHI_SERVICE, DkdAvahiServiceClass)
DkdAvahiService *dkd_avahi_service_new (void);
GType dkd_avahi_service_get_type (void);
gboolean dkd_avahi_service_start (DkdAvahiService *self,
const gchar *machine_name,
uint64_t server_port,
const gchar *entry_point_user,
const gchar * const *devkit1_argv,
const gchar *settings_json,
GMainLoop *loop,
GError **error);
gboolean dkd_avahi_service_reconfigure (DkdAvahiService *self,
const gchar *machine_name,
uint64_t server_port,
const gchar *entry_point_user,
const gchar * const *devkit1_argv,
const gchar *settings_json,
GError **error);
void dkd_avahi_service_stop (DkdAvahiService *self);
// remnants from the auto build system
#define PACKAGE "steamos-devkit-service"
#define DEVKIT_DATADIR "/usr/share"
#define SERVICE_PORT 32000
#define DEVKIT_HOOKS_DIR "/usr/share/steamos-devkit/hooks"
#define DEVKIT_LOCALSTATEDIR "/var"
/*
* This file is part of steamos-devkit
* SPDX-License-Identifier: LGPL-2.1+
*
* Copyright © 2018 Collabora Ltd
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this package. If not, see
* <http://www.gnu.org/licenses/>.
*/
#include "json.h"
#include "defines.h"
gboolean
dk_json_parser_load_from_bytes (JsonParser *parser,
GBytes *bytes,
GError **error)
{
gconstpointer data = NULL;
gsize len = 0;
g_return_val_if_fail (JSON_IS_PARSER (parser), FALSE);
g_return_val_if_fail (bytes != NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
data = g_bytes_get_data (bytes, &len);
if (data == NULL)
{
/* JsonParser doesn't like NULL data, even if its length is 0.
* g_bytes_get_data() documents a guarantee that it will only
* return NULL if the length is 0. */
g_assert (len == 0);
data = "";
}
return json_parser_load_from_data (parser, data, len, error);
}
#if !JSON_CHECK_VERSION(1, 2, 0)
gchar *
dk_json_to_string (JsonNode *node,
gboolean pretty)
{
JsonGenerator *gen;
gchar *ret;
gen = json_generator_new ();
json_generator_set_root (gen, node); /* a copy */
json_generator_set_pretty (gen, pretty);
ret = json_generator_to_data (gen, NULL);
g_object_unref (gen);
return ret;
}
#endif
/*
* This file is part of steamos-devkit
* SPDX-License-Identifier: LGPL-2.1+
*
* Copyright © 2018 Collabora Ltd
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this package. If not, see
* <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <json-glib/json-glib.h>
gboolean dk_json_parser_load_from_bytes (JsonParser *parser,
GBytes *bytes,
GError **error);
#if JSON_CHECK_VERSION(1, 2, 0)
#define dk_json_to_string(node, pretty) json_to_string (node, pretty)
#else
gchar *dk_json_to_string (JsonNode *node, gboolean pretty);
#endif
This diff is collapsed.
project('steamos-devkit-service', 'c')
dependencies = [
dependency('glib-2.0'),
dependency('gio-unix-2.0'),
dependency('json-glib-1.0'),
dependency('libsoup-2.4'),
dependency('avahi-client'),
dependency('avahi-glib'),
dependency('libsystemd'),
]
executable(
'steamos-devkit-service',
['main.c', 'avahi-service.c', 'utils.c', 'json.c'],
dependencies : dependencies,
install: true,
)
/*
* This file is part of steamos-devkit
* SPDX-License-Identifier: LGPL-2.1+
*
* Copyright © 2018 Collabora Ltd
* Incorporates code from GLib, copyright © 2009 Codethink Ltd
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this package. If not, see
* <http://www.gnu.org/licenses/>.
*/
#include "utils.h"
#include "defines.h"
#include <gio/gio.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
/*
* Windows command-line parsing makes even Unix /bin/sh look
* well-documented and consistent. To make life easier for the Windows
* client-side, escape anything that could become a problem.
* https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
*/
gchar *
dk_sanitize_machine_name (const gchar *machine_name)
{
const char *p = machine_name;
GString *buffer = NULL;
gsize i;
g_return_val_if_fail (machine_name != NULL, NULL);
buffer = g_string_new ("");
while (*p != '\0')
{
gunichar u = g_utf8_get_char_validated (p, -1);
/* Not UTF-8? Turn it into underscores. */
if (u == (gunichar) -1 || u == (gunichar) -2)
{
g_string_append_c (buffer, '_');
p++;
continue;
}
/*
* Allow all printable Unicode outside the ASCII range, plus the
* ASCII punctuation that is not special for cmd.exe, PowerShell,
* CommandLineFromArgvW (inside double quotes), Windows filenames,
* or DNS.
*
* We forbid <>:"/\|?* because they are not allowed in filenames,
* . because it's special in filenames and DNS, "`$ because they
* are special in PowerShell, "\%^ because they are special in
* cmd.exe, "\ because they are special for CommandLineFromArgvW.
* There isn't a whole lot left.
*/
if (u > 127)
{
if (g_unichar_isprint (u))
g_string_append_unichar (buffer, u);
else
g_string_append_c (buffer, '_');
}
else
{
if (g_ascii_isalnum ((char) u) ||
strchr (" !#&'()+,-;=@[]_{}~", (char) u) != NULL)
g_string_append_unichar (buffer, u);
else
g_string_append_c (buffer, '_');
}
p = g_utf8_next_char (p);
}
for (i = 0; i < buffer->len; i++)
{
if (!g_ascii_isspace (buffer->str[i]))
break;
}
g_string_erase (buffer, 0, i);
for (i = buffer->len; i > 0; i--)
{
if (!g_ascii_isspace (buffer->str[i - 1]))
break;
}
g_string_truncate (buffer, i);
/* DNS-SD machine names are DNS labels, limited to 63 bytes, so if
* it's longer than that we truncate to no more than 60 and append
* an ellipsis */
if (buffer->len > 63)
{
while (buffer->len > 60)
{
const gchar *nul = &buffer->str[buffer->len];
const gchar *prev;
prev = g_utf8_find_prev_char (buffer->str, nul);
g_string_truncate (buffer, prev - buffer->str);
}
g_string_append (buffer, "\xe2\x80\xa6");
}
if (buffer->len == 0)
g_string_append_c (buffer, '_');
return g_string_free (buffer, FALSE);
}
/*
* Returns: (transfer full): The absolute path of the hook script
*/
gchar *
dk_find_hook (const gchar * const *hook_dirs,
gboolean use_default_hooks,
const gchar *name,
GError **error)
{
const gchar * const *hook_dir;
gchar *script;
g_return_val_if_fail (name != NULL, NULL);
for (hook_dir = hook_dirs;
hook_dir != NULL && *hook_dir != NULL;
hook_dir++)
{
script = g_build_filename (*hook_dir, name, NULL);
if (access (script, X_OK) == 0)
return script;
g_free (script);
}
if (!use_default_hooks)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Unable to find script \"%s\" in specified "
"hook directories",
name);
return NULL;
}
script = g_strdup_printf ("/etc/%s/hooks/%s", PACKAGE, name);
if (access (script, X_OK) == 0)
return script;
g_free (script);
script = g_strdup_printf ("%s/%s", DEVKIT_HOOKS_DIR, name);
if (access (script, X_OK) == 0)
return script;
g_free (script);
script = g_strdup_printf ("%s/%s.sample", DEVKIT_HOOKS_DIR, name);
if (access (script, X_OK) == 0)
return script;
g_free (script);
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Unable to find script \"%s\" in \"/etc/%s/hooks\" "
"or \"%s\"",
name, PACKAGE, DEVKIT_HOOKS_DIR);
return NULL;
}
/* Taken from gio/gunixfdlist.c */
int
dk_dup_close_on_exec_fd (gint fd, GError ** error)
{
gint new_fd;
gint s;
#ifdef F_DUPFD_CLOEXEC
do
new_fd = fcntl (fd, F_DUPFD_CLOEXEC, 0l);
while (new_fd < 0 && (errno == EINTR));
if (new_fd >= 0)
return new_fd;
/* if that didn't work (new libc/old kernel?), try it the other way. */
#endif
do
new_fd = dup (fd);
while (new_fd < 0 && (errno == EINTR));
if (new_fd < 0)
{
int saved_errno = errno;
g_set_error (error, G_IO_ERROR,
g_io_error_from_errno (saved_errno),
"dup: %s", g_strerror (saved_errno));
return -1;
}
do
{
s = fcntl (new_fd, F_GETFD);
if (s >= 0)
s = fcntl (new_fd, F_SETFD, (long) (s | FD_CLOEXEC));
}
while (s < 0 && (errno == EINTR));
if (s < 0)
{
int saved_errno = errno;
g_set_error (error, G_IO_ERROR,
g_io_error_from_errno (saved_errno),
"fcntl: %s", g_strerror (saved_errno));
close (new_fd);
return -1;
}
return new_fd;
}
/*
* Read the first few bytes of @script and return TRUE if its first
* line is `#!/usr/bin/env python`, possibly with some extra whitespace.
* The devkit server special-cases these scripts to be run by Python 3
* if available, or Python 2 otherwise.
*
* Note that if the script starts with "#!/usr/bin/python",
* "#!/usr/bin/python2", "#!/usr/bin/python3", "#!/usr/bin/env python2"
* or "#!/usr/bin/python3", that doesn't count as a generic version of
* Python for our purposes.
*/
gboolean
dk_hook_is_generic_python (const gchar *script)
{
FILE *fh;
char buf[80];
size_t bytes_read;
fh = fopen (script, "rb");
if (fh == NULL)
return FALSE;
bytes_read = fread (buf, 1, sizeof (buf) - 1, fh);
buf[bytes_read] = '\0';
fclose (fh);
if (g_str_has_prefix (buf, "#!") &&
g_regex_match_simple ("#![ \t]*/usr/bin/env[ \t]+python[ \t]*\n",
buf, G_REGEX_ANCHORED, 0))
return TRUE;
return FALSE;
}
/*
* Return a Python interpreter.
*
* The result is guaranteed to be non-NULL, but is not guaranteed to
* exist: if neither python3 nor python exists in the PATH, we'll
* try to run "python whatever.py" and fail, which is error-handling
* that we'd need to have anyway.
*
* Returns: "python3", or "python" if there is no python3 in PATH
*/
const char *
dk_get_best_python (void)
{
static const char *best_python = NULL;
gchar *found;
if (best_python != NULL)
return best_python;
found = g_find_program_in_path ("python3");
if (found != NULL)
best_python = "python3";
else
best_python = "python";
g_free (found);
return best_python;
}
/*
* This file is part of steamos-devkit
* SPDX-License-Identifier: LGPL-2.1+
*
* Copyright © 2018 Collabora Ltd
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this package. If not, see
* <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <glib.h>
gchar *dk_find_hook (const gchar * const *hook_dirs,
gboolean use_default_hooks,
const gchar *name, GError **error);
gchar *dk_sanitize_machine_name (const gchar *machine_name);
int dk_dup_close_on_exec_fd (gint fd, GError **error);
gboolean dk_hook_is_generic_python (const gchar *script);
const char * dk_get_best_python (void);
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment