2017-11-18 17:09:20 +01:00
|
|
|
/* SPDX-License-Identifier: LGPL-2.1+ */
|
2013-09-25 17:04:41 +02:00
|
|
|
|
|
|
|
#include <fcntl.h>
|
2015-11-16 22:09:36 +01:00
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#include <sys/types.h>
|
2013-09-25 17:04:41 +02:00
|
|
|
#include <linux/vt.h>
|
|
|
|
|
2018-08-22 07:53:51 +02:00
|
|
|
#include "sd-device.h"
|
|
|
|
|
2015-10-27 03:01:06 +01:00
|
|
|
#include "alloc-util.h"
|
2013-11-05 01:10:21 +01:00
|
|
|
#include "bus-error.h"
|
2015-10-25 13:14:12 +01:00
|
|
|
#include "bus-util.h"
|
|
|
|
#include "cgroup-util.h"
|
2017-12-09 11:53:17 +01:00
|
|
|
#include "conf-parser.h"
|
2018-08-28 09:05:35 +02:00
|
|
|
#include "device-util.h"
|
2020-05-26 22:24:02 +02:00
|
|
|
#include "efi-loader.h"
|
2019-07-11 15:42:14 +02:00
|
|
|
#include "errno-util.h"
|
2015-10-25 13:14:12 +01:00
|
|
|
#include "fd-util.h"
|
2019-03-13 11:35:47 +01:00
|
|
|
#include "limits-util.h"
|
2013-11-05 01:10:21 +01:00
|
|
|
#include "logind.h"
|
2017-08-30 17:06:12 +02:00
|
|
|
#include "parse-util.h"
|
2018-08-07 13:49:34 +02:00
|
|
|
#include "path-util.h"
|
2018-01-11 00:39:12 +01:00
|
|
|
#include "process-util.h"
|
2015-10-25 13:14:12 +01:00
|
|
|
#include "strv.h"
|
2015-04-10 23:15:59 +02:00
|
|
|
#include "terminal-util.h"
|
2019-03-09 02:45:48 +01:00
|
|
|
#include "udev-util.h"
|
2015-10-25 22:32:30 +01:00
|
|
|
#include "user-util.h"
|
2019-08-07 16:22:35 +02:00
|
|
|
#include "userdb.h"
|
2020-07-02 16:24:04 +02:00
|
|
|
#include "utmp-wtmp.h"
|
2013-09-25 17:04:41 +02:00
|
|
|
|
2017-12-09 11:53:17 +01:00
|
|
|
void manager_reset_config(Manager *m) {
|
2018-04-24 18:13:12 +02:00
|
|
|
assert(m);
|
|
|
|
|
2017-12-09 11:53:17 +01:00
|
|
|
m->n_autovts = 6;
|
|
|
|
m->reserve_vt = 6;
|
|
|
|
m->remove_ipc = true;
|
|
|
|
m->inhibit_delay_max = 5 * USEC_PER_SEC;
|
2018-08-07 11:02:00 +02:00
|
|
|
m->user_stop_delay = 10 * USEC_PER_SEC;
|
|
|
|
|
2017-12-09 11:53:17 +01:00
|
|
|
m->handle_power_key = HANDLE_POWEROFF;
|
|
|
|
m->handle_suspend_key = HANDLE_SUSPEND;
|
|
|
|
m->handle_hibernate_key = HANDLE_HIBERNATE;
|
|
|
|
m->handle_lid_switch = HANDLE_SUSPEND;
|
|
|
|
m->handle_lid_switch_ep = _HANDLE_ACTION_INVALID;
|
|
|
|
m->handle_lid_switch_docked = HANDLE_IGNORE;
|
|
|
|
m->power_key_ignore_inhibited = false;
|
|
|
|
m->suspend_key_ignore_inhibited = false;
|
|
|
|
m->hibernate_key_ignore_inhibited = false;
|
|
|
|
m->lid_switch_ignore_inhibited = true;
|
|
|
|
|
|
|
|
m->holdoff_timeout_usec = 30 * USEC_PER_SEC;
|
|
|
|
|
|
|
|
m->idle_action_usec = 30 * USEC_PER_MINUTE;
|
|
|
|
m->idle_action = HANDLE_IGNORE;
|
|
|
|
|
|
|
|
m->runtime_dir_size = physical_memory_scale(10U, 100U); /* 10% */
|
2020-05-23 16:00:41 +02:00
|
|
|
m->runtime_dir_inodes = DIV_ROUND_UP(m->runtime_dir_size, 4096); /* 4k per inode */
|
2017-12-09 11:53:17 +01:00
|
|
|
m->sessions_max = 8192;
|
|
|
|
m->inhibitors_max = 8192;
|
|
|
|
|
|
|
|
m->kill_user_processes = KILL_USER_PROCESSES;
|
|
|
|
|
|
|
|
m->kill_only_users = strv_free(m->kill_only_users);
|
|
|
|
m->kill_exclude_users = strv_free(m->kill_exclude_users);
|
|
|
|
}
|
|
|
|
|
|
|
|
int manager_parse_config_file(Manager *m) {
|
|
|
|
assert(m);
|
|
|
|
|
2020-06-02 14:55:12 +02:00
|
|
|
return config_parse_many_nulstr(
|
|
|
|
PKGSYSCONFDIR "/logind.conf",
|
|
|
|
CONF_PATHS_NULSTR("systemd/logind.conf.d"),
|
|
|
|
"Login\0",
|
|
|
|
config_item_perf_lookup, logind_gperf_lookup,
|
|
|
|
CONFIG_PARSE_WARN, m,
|
|
|
|
NULL);
|
2017-12-09 11:53:17 +01:00
|
|
|
}
|
|
|
|
|
2019-09-06 09:18:33 +02:00
|
|
|
int manager_add_device(Manager *m, const char *sysfs, bool master, Device **ret_device) {
|
2013-09-25 17:04:41 +02:00
|
|
|
Device *d;
|
|
|
|
|
|
|
|
assert(m);
|
|
|
|
assert(sysfs);
|
|
|
|
|
|
|
|
d = hashmap_get(m->devices, sysfs);
|
2014-02-03 05:58:16 +01:00
|
|
|
if (d)
|
2013-09-25 17:04:41 +02:00
|
|
|
/* we support adding master-flags, but not removing them */
|
|
|
|
d->master = d->master || master;
|
2014-02-03 05:58:16 +01:00
|
|
|
else {
|
|
|
|
d = device_new(m, sysfs, master);
|
|
|
|
if (!d)
|
|
|
|
return -ENOMEM;
|
2013-09-25 17:04:41 +02:00
|
|
|
}
|
|
|
|
|
2019-09-06 09:18:33 +02:00
|
|
|
if (ret_device)
|
|
|
|
*ret_device = d;
|
2013-09-25 17:04:41 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-09-06 09:18:33 +02:00
|
|
|
int manager_add_seat(Manager *m, const char *id, Seat **ret_seat) {
|
2013-09-25 17:04:41 +02:00
|
|
|
Seat *s;
|
2018-08-03 18:53:09 +02:00
|
|
|
int r;
|
2013-09-25 17:04:41 +02:00
|
|
|
|
|
|
|
assert(m);
|
|
|
|
assert(id);
|
|
|
|
|
|
|
|
s = hashmap_get(m->seats, id);
|
2014-02-03 05:58:16 +01:00
|
|
|
if (!s) {
|
2018-08-03 18:53:09 +02:00
|
|
|
r = seat_new(&s, m, id);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
2013-09-25 17:04:41 +02:00
|
|
|
}
|
|
|
|
|
2019-09-06 09:18:33 +02:00
|
|
|
if (ret_seat)
|
|
|
|
*ret_seat = s;
|
2013-09-25 17:04:41 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-09-06 09:18:33 +02:00
|
|
|
int manager_add_session(Manager *m, const char *id, Session **ret_session) {
|
2013-09-25 17:04:41 +02:00
|
|
|
Session *s;
|
2018-08-03 18:53:09 +02:00
|
|
|
int r;
|
2013-09-25 17:04:41 +02:00
|
|
|
|
|
|
|
assert(m);
|
|
|
|
assert(id);
|
|
|
|
|
|
|
|
s = hashmap_get(m->sessions, id);
|
2014-02-03 05:58:16 +01:00
|
|
|
if (!s) {
|
2018-08-03 18:53:09 +02:00
|
|
|
r = session_new(&s, m, id);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
2013-09-25 17:04:41 +02:00
|
|
|
}
|
|
|
|
|
2019-09-06 09:18:33 +02:00
|
|
|
if (ret_session)
|
|
|
|
*ret_session = s;
|
2013-09-25 17:04:41 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-08-08 15:27:49 +02:00
|
|
|
int manager_add_user(
|
|
|
|
Manager *m,
|
2019-08-07 16:22:35 +02:00
|
|
|
UserRecord *ur,
|
2019-09-06 09:18:33 +02:00
|
|
|
User **ret_user) {
|
2018-08-08 15:27:49 +02:00
|
|
|
|
2013-09-25 17:04:41 +02:00
|
|
|
User *u;
|
2015-09-29 11:10:01 +02:00
|
|
|
int r;
|
2013-09-25 17:04:41 +02:00
|
|
|
|
|
|
|
assert(m);
|
2019-08-07 16:22:35 +02:00
|
|
|
assert(ur);
|
2013-09-25 17:04:41 +02:00
|
|
|
|
2019-08-07 16:22:35 +02:00
|
|
|
u = hashmap_get(m->users, UID_TO_PTR(ur->uid));
|
2014-02-03 05:58:16 +01:00
|
|
|
if (!u) {
|
2019-08-07 16:22:35 +02:00
|
|
|
r = user_new(&u, m, ur);
|
2015-09-29 11:10:01 +02:00
|
|
|
if (r < 0)
|
|
|
|
return r;
|
2013-09-25 17:04:41 +02:00
|
|
|
}
|
|
|
|
|
2019-09-06 09:18:33 +02:00
|
|
|
if (ret_user)
|
|
|
|
*ret_user = u;
|
2013-09-25 17:04:41 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-08-08 15:27:49 +02:00
|
|
|
int manager_add_user_by_name(
|
|
|
|
Manager *m,
|
|
|
|
const char *name,
|
2019-09-06 09:18:33 +02:00
|
|
|
User **ret_user) {
|
2018-08-08 15:27:49 +02:00
|
|
|
|
2019-08-07 16:22:35 +02:00
|
|
|
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
|
2013-09-25 17:04:41 +02:00
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(m);
|
|
|
|
assert(name);
|
|
|
|
|
2020-04-09 14:28:56 +02:00
|
|
|
r = userdb_by_name(name, USERDB_AVOID_SHADOW, &ur);
|
2013-09-25 17:04:41 +02:00
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
2019-08-07 16:22:35 +02:00
|
|
|
return manager_add_user(m, ur, ret_user);
|
2013-09-25 17:04:41 +02:00
|
|
|
}
|
|
|
|
|
2019-08-07 16:22:35 +02:00
|
|
|
int manager_add_user_by_uid(
|
|
|
|
Manager *m,
|
|
|
|
uid_t uid,
|
|
|
|
User **ret_user) {
|
|
|
|
|
|
|
|
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
|
|
|
|
int r;
|
2013-09-25 17:04:41 +02:00
|
|
|
|
|
|
|
assert(m);
|
2019-08-07 16:22:35 +02:00
|
|
|
assert(uid_is_valid(uid));
|
2013-09-25 17:04:41 +02:00
|
|
|
|
2020-04-09 14:28:56 +02:00
|
|
|
r = userdb_by_uid(uid, USERDB_AVOID_SHADOW, &ur);
|
2019-08-07 16:22:35 +02:00
|
|
|
if (r < 0)
|
|
|
|
return r;
|
2013-09-25 17:04:41 +02:00
|
|
|
|
2019-08-07 16:22:35 +02:00
|
|
|
return manager_add_user(m, ur, ret_user);
|
2013-09-25 17:04:41 +02:00
|
|
|
}
|
|
|
|
|
2019-07-23 10:27:19 +02:00
|
|
|
int manager_add_inhibitor(Manager *m, const char* id, Inhibitor **ret) {
|
2013-09-25 17:04:41 +02:00
|
|
|
Inhibitor *i;
|
2019-07-23 10:27:19 +02:00
|
|
|
int r;
|
2013-09-25 17:04:41 +02:00
|
|
|
|
|
|
|
assert(m);
|
|
|
|
assert(id);
|
|
|
|
|
|
|
|
i = hashmap_get(m->inhibitors, id);
|
2019-07-23 10:27:19 +02:00
|
|
|
if (!i) {
|
|
|
|
r = inhibitor_new(&i, m, id);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
2013-09-25 17:04:41 +02:00
|
|
|
}
|
|
|
|
|
2019-07-23 10:27:19 +02:00
|
|
|
if (ret)
|
|
|
|
*ret = i;
|
2013-09-25 17:04:41 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-09-06 09:18:33 +02:00
|
|
|
int manager_add_button(Manager *m, const char *name, Button **ret_button) {
|
2013-09-25 17:04:41 +02:00
|
|
|
Button *b;
|
|
|
|
|
|
|
|
assert(m);
|
|
|
|
assert(name);
|
|
|
|
|
|
|
|
b = hashmap_get(m->buttons, name);
|
2014-02-03 05:58:16 +01:00
|
|
|
if (!b) {
|
|
|
|
b = button_new(m, name);
|
|
|
|
if (!b)
|
|
|
|
return -ENOMEM;
|
2013-09-25 17:04:41 +02:00
|
|
|
}
|
|
|
|
|
2019-09-06 09:18:33 +02:00
|
|
|
if (ret_button)
|
|
|
|
*ret_button = b;
|
2013-09-25 17:04:41 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-08-22 07:53:51 +02:00
|
|
|
int manager_process_seat_device(Manager *m, sd_device *d) {
|
2013-09-25 17:04:41 +02:00
|
|
|
Device *device;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(m);
|
|
|
|
|
2019-03-09 02:45:48 +01:00
|
|
|
if (device_for_action(d, DEVICE_ACTION_REMOVE)) {
|
2018-08-22 07:53:51 +02:00
|
|
|
const char *syspath;
|
|
|
|
|
|
|
|
r = sd_device_get_syspath(d, &syspath);
|
|
|
|
if (r < 0)
|
|
|
|
return 0;
|
2013-09-25 17:04:41 +02:00
|
|
|
|
2018-08-22 07:53:51 +02:00
|
|
|
device = hashmap_get(m->devices, syspath);
|
2013-09-25 17:04:41 +02:00
|
|
|
if (!device)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
seat_add_to_gc_queue(device->seat);
|
|
|
|
device_free(device);
|
|
|
|
|
|
|
|
} else {
|
2018-08-22 07:53:51 +02:00
|
|
|
const char *sn, *syspath;
|
2013-09-25 17:04:41 +02:00
|
|
|
bool master;
|
2018-08-22 07:53:51 +02:00
|
|
|
Seat *seat;
|
2013-09-25 17:04:41 +02:00
|
|
|
|
2018-08-22 07:53:51 +02:00
|
|
|
if (sd_device_get_property_value(d, "ID_SEAT", &sn) < 0 || isempty(sn))
|
2013-09-25 17:04:41 +02:00
|
|
|
sn = "seat0";
|
|
|
|
|
|
|
|
if (!seat_name_is_valid(sn)) {
|
2018-10-22 05:54:27 +02:00
|
|
|
log_device_warning(d, "Device with invalid seat name %s found, ignoring.", sn);
|
2013-09-25 17:04:41 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-03-03 19:30:16 +01:00
|
|
|
seat = hashmap_get(m->seats, sn);
|
2018-08-22 07:53:51 +02:00
|
|
|
master = sd_device_has_tag(d, "master-of-seat") > 0;
|
2014-03-03 19:30:16 +01:00
|
|
|
|
|
|
|
/* Ignore non-master devices for unknown seats */
|
|
|
|
if (!master && !seat)
|
2013-09-25 17:04:41 +02:00
|
|
|
return 0;
|
|
|
|
|
2018-08-22 07:53:51 +02:00
|
|
|
r = sd_device_get_syspath(d, &syspath);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
r = manager_add_device(m, syspath, master, &device);
|
2013-09-25 17:04:41 +02:00
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
if (!seat) {
|
|
|
|
r = manager_add_seat(m, sn, &seat);
|
|
|
|
if (r < 0) {
|
|
|
|
if (!device->seat)
|
|
|
|
device_free(device);
|
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
device_attach(device, seat);
|
|
|
|
seat_start(seat);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-08-22 07:53:51 +02:00
|
|
|
int manager_process_button_device(Manager *m, sd_device *d) {
|
2019-03-09 02:45:48 +01:00
|
|
|
const char *sysname;
|
2013-09-25 17:04:41 +02:00
|
|
|
Button *b;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(m);
|
|
|
|
|
2018-08-22 07:53:51 +02:00
|
|
|
r = sd_device_get_sysname(d, &sysname);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
2019-03-09 02:45:48 +01:00
|
|
|
if (device_for_action(d, DEVICE_ACTION_REMOVE)) {
|
2013-09-25 17:04:41 +02:00
|
|
|
|
2018-08-22 07:53:51 +02:00
|
|
|
b = hashmap_get(m->buttons, sysname);
|
2013-09-25 17:04:41 +02:00
|
|
|
if (!b)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
button_free(b);
|
|
|
|
|
|
|
|
} else {
|
2018-09-01 16:13:54 +02:00
|
|
|
const char *sn;
|
2013-09-25 17:04:41 +02:00
|
|
|
|
2018-08-22 07:53:51 +02:00
|
|
|
r = manager_add_button(m, sysname, &b);
|
2013-09-25 17:04:41 +02:00
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
2018-08-22 07:53:51 +02:00
|
|
|
if (sd_device_get_property_value(d, "ID_SEAT", &sn) < 0 || isempty(sn))
|
2013-09-25 17:04:41 +02:00
|
|
|
sn = "seat0";
|
|
|
|
|
|
|
|
button_set_seat(b, sn);
|
2017-06-27 17:46:28 +02:00
|
|
|
|
|
|
|
r = button_open(b);
|
|
|
|
if (r < 0) /* event device doesn't have any keys or switches relevant to us? (or any other error
|
|
|
|
* opening the device?) let's close the button again. */
|
|
|
|
button_free(b);
|
2013-09-25 17:04:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-03-21 19:04:45 +01:00
|
|
|
int manager_get_session_by_pid(Manager *m, pid_t pid, Session **ret) {
|
2013-09-25 17:04:41 +02:00
|
|
|
_cleanup_free_ char *unit = NULL;
|
|
|
|
Session *s;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(m);
|
|
|
|
|
2017-10-03 13:26:02 +02:00
|
|
|
if (!pid_is_valid(pid))
|
2013-09-25 17:04:41 +02:00
|
|
|
return -EINVAL;
|
|
|
|
|
2018-08-07 12:08:24 +02:00
|
|
|
s = hashmap_get(m->sessions_by_leader, PID_TO_PTR(pid));
|
|
|
|
if (!s) {
|
|
|
|
r = cg_pid_get_unit(pid, &unit);
|
2019-09-06 09:38:33 +02:00
|
|
|
if (r >= 0)
|
|
|
|
s = hashmap_get(m->session_units, unit);
|
2018-08-07 12:08:24 +02:00
|
|
|
}
|
2018-03-21 19:04:45 +01:00
|
|
|
|
|
|
|
if (ret)
|
|
|
|
*ret = s;
|
2013-09-25 17:04:41 +02:00
|
|
|
|
2019-09-06 09:38:33 +02:00
|
|
|
return !!s;
|
2013-09-25 17:04:41 +02:00
|
|
|
}
|
|
|
|
|
2018-03-21 19:04:45 +01:00
|
|
|
int manager_get_user_by_pid(Manager *m, pid_t pid, User **ret) {
|
2013-09-25 17:04:41 +02:00
|
|
|
_cleanup_free_ char *unit = NULL;
|
2019-09-06 09:38:33 +02:00
|
|
|
User *u = NULL;
|
2013-09-25 17:04:41 +02:00
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(m);
|
|
|
|
|
2017-10-03 13:26:02 +02:00
|
|
|
if (!pid_is_valid(pid))
|
2013-09-25 17:04:41 +02:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
r = cg_pid_get_slice(pid, &unit);
|
2019-09-06 09:38:33 +02:00
|
|
|
if (r >= 0)
|
|
|
|
u = hashmap_get(m->user_units, unit);
|
2018-03-21 19:04:45 +01:00
|
|
|
|
|
|
|
if (ret)
|
|
|
|
*ret = u;
|
2013-09-25 17:04:41 +02:00
|
|
|
|
2019-09-06 09:38:33 +02:00
|
|
|
return !!u;
|
2013-09-25 17:04:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
int manager_get_idle_hint(Manager *m, dual_timestamp *t) {
|
|
|
|
Session *s;
|
|
|
|
bool idle_hint;
|
2015-06-16 01:08:12 +02:00
|
|
|
dual_timestamp ts = DUAL_TIMESTAMP_NULL;
|
2013-09-25 17:04:41 +02:00
|
|
|
Iterator i;
|
|
|
|
|
|
|
|
assert(m);
|
|
|
|
|
2013-11-27 02:38:06 +01:00
|
|
|
idle_hint = !manager_is_inhibited(m, INHIBIT_IDLE, INHIBIT_BLOCK, t, false, false, 0, NULL);
|
2013-09-25 17:04:41 +02:00
|
|
|
|
|
|
|
HASHMAP_FOREACH(s, m->sessions, i) {
|
|
|
|
dual_timestamp k;
|
|
|
|
int ih;
|
|
|
|
|
|
|
|
ih = session_get_idle_hint(s, &k);
|
|
|
|
if (ih < 0)
|
|
|
|
return ih;
|
|
|
|
|
|
|
|
if (!ih) {
|
|
|
|
if (!idle_hint) {
|
|
|
|
if (k.monotonic < ts.monotonic)
|
|
|
|
ts = k;
|
|
|
|
} else {
|
|
|
|
idle_hint = false;
|
|
|
|
ts = k;
|
|
|
|
}
|
|
|
|
} else if (idle_hint) {
|
|
|
|
|
|
|
|
if (k.monotonic > ts.monotonic)
|
|
|
|
ts = k;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (t)
|
|
|
|
*t = ts;
|
|
|
|
|
|
|
|
return idle_hint;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool manager_shall_kill(Manager *m, const char *user) {
|
|
|
|
assert(m);
|
|
|
|
assert(user);
|
|
|
|
|
2016-04-09 21:03:19 +02:00
|
|
|
if (!m->kill_exclude_users && streq(user, "root"))
|
|
|
|
return false;
|
|
|
|
|
2013-09-25 17:04:41 +02:00
|
|
|
if (strv_contains(m->kill_exclude_users, user))
|
|
|
|
return false;
|
|
|
|
|
2016-04-13 05:52:41 +02:00
|
|
|
if (!strv_isempty(m->kill_only_users))
|
|
|
|
return strv_contains(m->kill_only_users, user);
|
2013-09-25 17:04:41 +02:00
|
|
|
|
2016-04-13 05:52:41 +02:00
|
|
|
return m->kill_user_processes;
|
2013-09-25 17:04:41 +02:00
|
|
|
}
|
|
|
|
|
2017-08-30 17:06:12 +02:00
|
|
|
int config_parse_n_autovts(
|
|
|
|
const char *unit,
|
|
|
|
const char *filename,
|
|
|
|
unsigned line,
|
|
|
|
const char *section,
|
|
|
|
unsigned section_line,
|
|
|
|
const char *lvalue,
|
|
|
|
int ltype,
|
|
|
|
const char *rvalue,
|
|
|
|
void *data,
|
|
|
|
void *userdata) {
|
|
|
|
|
|
|
|
unsigned *n = data;
|
|
|
|
unsigned o;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(filename);
|
|
|
|
assert(lvalue);
|
|
|
|
assert(rvalue);
|
|
|
|
assert(data);
|
|
|
|
|
|
|
|
r = safe_atou(rvalue, &o);
|
|
|
|
if (r < 0) {
|
|
|
|
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse number of autovts, ignoring: %s", rvalue);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (o > 15) {
|
|
|
|
log_syntax(unit, LOG_ERR, filename, line, r, "A maximum of 15 autovts are supported, ignoring: %s", rvalue);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
*n = o;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-10-19 20:00:46 +02:00
|
|
|
static int vt_is_busy(unsigned vtnr) {
|
2013-09-25 17:04:41 +02:00
|
|
|
struct vt_stat vt_stat;
|
2014-02-03 05:58:16 +01:00
|
|
|
int r = 0;
|
|
|
|
_cleanup_close_ int fd;
|
2013-09-25 17:04:41 +02:00
|
|
|
|
|
|
|
assert(vtnr >= 1);
|
|
|
|
|
2017-08-30 17:06:12 +02:00
|
|
|
/* VT_GETSTATE "cannot return state for more than 16 VTs, since v_state is short" */
|
|
|
|
assert(vtnr <= 15);
|
|
|
|
|
2013-09-25 17:04:41 +02:00
|
|
|
/* We explicitly open /dev/tty1 here instead of /dev/tty0. If
|
|
|
|
* we'd open the latter we'd open the foreground tty which
|
|
|
|
* hence would be unconditionally busy. By opening /dev/tty1
|
|
|
|
* we avoid this. Since tty1 is special and needs to be an
|
|
|
|
* explicitly loaded getty or DM this is safe. */
|
|
|
|
|
|
|
|
fd = open_terminal("/dev/tty1", O_RDWR|O_NOCTTY|O_CLOEXEC);
|
|
|
|
if (fd < 0)
|
|
|
|
return -errno;
|
|
|
|
|
|
|
|
if (ioctl(fd, VT_GETSTATE, &vt_stat) < 0)
|
|
|
|
r = -errno;
|
|
|
|
else
|
|
|
|
r = !!(vt_stat.v_state & (1 << vtnr));
|
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2018-10-19 20:00:46 +02:00
|
|
|
int manager_spawn_autovt(Manager *m, unsigned vtnr) {
|
tree-wide: expose "p"-suffix unref calls in public APIs to make gcc cleanup easy
GLIB has recently started to officially support the gcc cleanup
attribute in its public API, hence let's do the same for our APIs.
With this patch we'll define an xyz_unrefp() call for each public
xyz_unref() call, to make it easy to use inside a
__attribute__((cleanup())) expression. Then, all code is ported over to
make use of this.
The new calls are also documented in the man pages, with examples how to
use them (well, I only added docs where the _unref() call itself already
had docs, and the examples, only cover sd_bus_unrefp() and
sd_event_unrefp()).
This also renames sd_lldp_free() to sd_lldp_unref(), since that's how we
tend to call our destructors these days.
Note that this defines no public macro that wraps gcc's attribute and
makes it easier to use. While I think it's our duty in the library to
make our stuff easy to use, I figure it's not our duty to make gcc's own
features easy to use on its own. Most likely, client code which wants to
make use of this should define its own:
#define _cleanup_(function) __attribute__((cleanup(function)))
Or similar, to make the gcc feature easier to use.
Making this logic public has the benefit that we can remove three header
files whose only purpose was to define these functions internally.
See #2008.
2015-11-27 19:13:45 +01:00
|
|
|
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
2018-10-19 20:00:46 +02:00
|
|
|
char name[sizeof("autovt@tty.service") + DECIMAL_STR_MAX(unsigned)];
|
2013-09-25 17:04:41 +02:00
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(m);
|
|
|
|
assert(vtnr >= 1);
|
|
|
|
|
2013-11-28 17:05:34 +01:00
|
|
|
if (vtnr > m->n_autovts &&
|
|
|
|
vtnr != m->reserve_vt)
|
2013-09-25 17:04:41 +02:00
|
|
|
return 0;
|
|
|
|
|
2013-11-28 17:05:34 +01:00
|
|
|
if (vtnr != m->reserve_vt) {
|
2013-09-25 17:04:41 +02:00
|
|
|
/* If this is the reserved TTY, we'll start the getty
|
|
|
|
* on it in any case, but otherwise only if it is not
|
|
|
|
* busy. */
|
|
|
|
|
|
|
|
r = vt_is_busy(vtnr);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
else if (r > 0)
|
|
|
|
return -EBUSY;
|
|
|
|
}
|
|
|
|
|
2014-02-03 05:58:16 +01:00
|
|
|
snprintf(name, sizeof(name), "autovt@tty%u.service", vtnr);
|
2013-11-05 01:10:21 +01:00
|
|
|
r = sd_bus_call_method(
|
2013-09-25 17:04:41 +02:00
|
|
|
m->bus,
|
|
|
|
"org.freedesktop.systemd1",
|
|
|
|
"/org/freedesktop/systemd1",
|
|
|
|
"org.freedesktop.systemd1.Manager",
|
|
|
|
"StartUnit",
|
2013-11-05 01:10:21 +01:00
|
|
|
&error,
|
2013-09-25 17:04:41 +02:00
|
|
|
NULL,
|
2013-11-05 01:10:21 +01:00
|
|
|
"ss", name, "fail");
|
|
|
|
if (r < 0)
|
2018-08-07 03:14:30 +02:00
|
|
|
return log_error_errno(r, "Failed to start %s: %s", name, bus_error_message(&error, r));
|
2013-09-25 17:04:41 +02:00
|
|
|
|
2018-08-07 03:14:30 +02:00
|
|
|
return 0;
|
2013-09-25 17:04:41 +02:00
|
|
|
}
|
2014-02-24 16:22:23 +01:00
|
|
|
|
2018-10-22 12:41:34 +02:00
|
|
|
bool manager_is_lid_closed(Manager *m) {
|
|
|
|
Iterator i;
|
|
|
|
Button *b;
|
|
|
|
|
|
|
|
HASHMAP_FOREACH(b, m->buttons, i)
|
|
|
|
if (b->lid_closed)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-06-17 00:24:05 +02:00
|
|
|
static bool manager_is_docked(Manager *m) {
|
2014-02-24 16:22:23 +01:00
|
|
|
Iterator i;
|
|
|
|
Button *b;
|
|
|
|
|
|
|
|
HASHMAP_FOREACH(b, m->buttons, i)
|
|
|
|
if (b->docked)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2014-03-03 19:30:16 +01:00
|
|
|
|
2015-06-17 00:24:05 +02:00
|
|
|
static int manager_count_external_displays(Manager *m) {
|
2018-08-22 07:53:51 +02:00
|
|
|
_cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
|
|
|
|
sd_device *d;
|
|
|
|
int r, n = 0;
|
2014-03-03 19:30:16 +01:00
|
|
|
|
2018-08-22 07:53:51 +02:00
|
|
|
r = sd_device_enumerator_new(&e);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
2014-03-03 19:30:16 +01:00
|
|
|
|
2018-08-22 07:53:51 +02:00
|
|
|
r = sd_device_enumerator_allow_uninitialized(e);
|
2014-03-03 19:30:16 +01:00
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
2018-08-22 07:53:51 +02:00
|
|
|
r = sd_device_enumerator_add_match_subsystem(e, "drm", true);
|
2014-03-03 19:30:16 +01:00
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
2018-08-28 09:05:35 +02:00
|
|
|
FOREACH_DEVICE(e, d) {
|
2018-11-23 16:30:23 +01:00
|
|
|
const char *status, *enabled, *dash, *nn, *subsys;
|
2018-08-22 07:53:51 +02:00
|
|
|
sd_device *p;
|
2014-03-03 19:30:16 +01:00
|
|
|
|
2018-08-22 07:53:51 +02:00
|
|
|
if (sd_device_get_parent(d, &p) < 0)
|
2014-03-11 16:49:00 +01:00
|
|
|
continue;
|
2014-03-03 19:30:16 +01:00
|
|
|
|
|
|
|
/* If the parent shares the same subsystem as the
|
|
|
|
* device we are looking at then it is a connector,
|
|
|
|
* which is what we are interested in. */
|
2018-08-22 07:53:51 +02:00
|
|
|
if (sd_device_get_subsystem(p, &subsys) < 0 || !streq(subsys, "drm"))
|
2014-03-03 19:30:16 +01:00
|
|
|
continue;
|
|
|
|
|
2018-08-22 07:53:51 +02:00
|
|
|
if (sd_device_get_sysname(d, &nn) < 0)
|
2015-06-17 00:24:05 +02:00
|
|
|
continue;
|
|
|
|
|
2020-06-23 08:31:16 +02:00
|
|
|
/* Ignore internal displays: the type is encoded in the sysfs name, as the second dash
|
|
|
|
* separated item (the first is the card name, the last the connector number). We implement a
|
|
|
|
* deny list of external displays here, rather than an allow list of internal ones, to ensure
|
|
|
|
* we don't block suspends too eagerly. */
|
2015-06-17 00:24:05 +02:00
|
|
|
dash = strchr(nn, '-');
|
|
|
|
if (!dash)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
dash++;
|
2018-11-23 16:30:23 +01:00
|
|
|
if (!STARTSWITH_SET(dash,
|
|
|
|
"VGA-", "DVI-I-", "DVI-D-", "DVI-A-"
|
|
|
|
"Composite-", "SVIDEO-", "Component-",
|
|
|
|
"DIN-", "DP-", "HDMI-A-", "HDMI-B-", "TV-"))
|
2015-06-17 00:24:05 +02:00
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Ignore ports that are not enabled */
|
2018-08-22 07:53:51 +02:00
|
|
|
if (sd_device_get_sysattr_value(d, "enabled", &enabled) < 0 || !streq(enabled, "enabled"))
|
2015-06-17 00:24:05 +02:00
|
|
|
continue;
|
|
|
|
|
2014-03-03 19:30:16 +01:00
|
|
|
/* We count any connector which is not explicitly
|
|
|
|
* "disconnected" as connected. */
|
2018-08-22 07:53:51 +02:00
|
|
|
if (sd_device_get_sysattr_value(d, "status", &status) < 0 || !streq(status, "disconnected"))
|
2014-03-03 19:30:16 +01:00
|
|
|
n++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return n;
|
|
|
|
}
|
2014-08-26 22:08:02 +02:00
|
|
|
|
2015-06-17 00:24:05 +02:00
|
|
|
bool manager_is_docked_or_external_displays(Manager *m) {
|
2014-08-26 22:08:02 +02:00
|
|
|
int n;
|
|
|
|
|
|
|
|
/* If we are docked don't react to lid closing */
|
|
|
|
if (manager_is_docked(m)) {
|
|
|
|
log_debug("System is docked.");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If we have more than one display connected,
|
|
|
|
* assume that we are docked. */
|
2015-06-17 00:24:05 +02:00
|
|
|
n = manager_count_external_displays(m);
|
2014-08-26 22:08:02 +02:00
|
|
|
if (n < 0)
|
2014-11-28 13:19:16 +01:00
|
|
|
log_warning_errno(n, "Display counting failed: %m");
|
2015-06-17 00:24:05 +02:00
|
|
|
else if (n >= 1) {
|
|
|
|
log_debug("External (%i) displays connected.", n);
|
2014-08-26 22:08:02 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2018-02-09 17:37:39 +01:00
|
|
|
|
|
|
|
bool manager_is_on_external_power(void) {
|
|
|
|
int r;
|
|
|
|
|
2018-10-22 13:02:07 +02:00
|
|
|
/* For now we only check for AC power, but 'external power' can apply to anything that isn't an internal
|
|
|
|
* battery */
|
2018-02-09 17:37:39 +01:00
|
|
|
r = on_ac_power();
|
|
|
|
if (r < 0)
|
|
|
|
log_warning_errno(r, "Failed to read AC power status: %m");
|
|
|
|
|
2018-10-22 13:02:07 +02:00
|
|
|
return r != 0; /* Treat failure as 'on AC' */
|
2018-02-09 17:37:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool manager_all_buttons_ignored(Manager *m) {
|
2018-02-26 18:31:06 +01:00
|
|
|
assert(m);
|
|
|
|
|
2018-02-09 17:37:39 +01:00
|
|
|
if (m->handle_power_key != HANDLE_IGNORE)
|
|
|
|
return false;
|
|
|
|
if (m->handle_suspend_key != HANDLE_IGNORE)
|
|
|
|
return false;
|
|
|
|
if (m->handle_hibernate_key != HANDLE_IGNORE)
|
|
|
|
return false;
|
|
|
|
if (m->handle_lid_switch != HANDLE_IGNORE)
|
|
|
|
return false;
|
2019-04-29 18:22:22 +02:00
|
|
|
if (!IN_SET(m->handle_lid_switch_ep, _HANDLE_ACTION_INVALID, HANDLE_IGNORE))
|
2018-02-09 17:37:39 +01:00
|
|
|
return false;
|
|
|
|
if (m->handle_lid_switch_docked != HANDLE_IGNORE)
|
|
|
|
return false;
|
2018-02-26 18:31:06 +01:00
|
|
|
|
2018-02-09 17:37:39 +01:00
|
|
|
return true;
|
|
|
|
}
|
2018-08-07 13:49:34 +02:00
|
|
|
|
|
|
|
int manager_read_utmp(Manager *m) {
|
|
|
|
#if ENABLE_UTMP
|
|
|
|
int r;
|
2020-07-02 16:24:04 +02:00
|
|
|
_cleanup_(utxent_cleanup) bool utmpx = false;
|
2018-08-07 13:49:34 +02:00
|
|
|
|
|
|
|
assert(m);
|
|
|
|
|
|
|
|
if (utmpxname(_PATH_UTMPX) < 0)
|
|
|
|
return log_error_errno(errno, "Failed to set utmp path to " _PATH_UTMPX ": %m");
|
|
|
|
|
2020-07-02 16:24:04 +02:00
|
|
|
utmpx = utxent_start();
|
2018-08-07 13:49:34 +02:00
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
_cleanup_free_ char *t = NULL;
|
|
|
|
struct utmpx *u;
|
|
|
|
const char *c;
|
|
|
|
Session *s;
|
|
|
|
|
|
|
|
errno = 0;
|
|
|
|
u = getutxent();
|
|
|
|
if (!u) {
|
|
|
|
if (errno != 0)
|
|
|
|
log_warning_errno(errno, "Failed to read " _PATH_UTMPX ", ignoring: %m");
|
2020-07-02 16:24:04 +02:00
|
|
|
return 0;
|
2018-08-07 13:49:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (u->ut_type != USER_PROCESS)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (!pid_is_valid(u->ut_pid))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
t = strndup(u->ut_line, sizeof(u->ut_line));
|
2020-07-02 16:24:04 +02:00
|
|
|
if (!t)
|
|
|
|
return log_oom();
|
2018-08-07 13:49:34 +02:00
|
|
|
|
|
|
|
c = path_startswith(t, "/dev/");
|
|
|
|
if (c) {
|
|
|
|
r = free_and_strdup(&t, c);
|
2020-07-02 16:24:04 +02:00
|
|
|
if (r < 0)
|
|
|
|
return log_oom();
|
2018-08-07 13:49:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (isempty(t))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
s = hashmap_get(m->sessions_by_leader, PID_TO_PTR(u->ut_pid));
|
|
|
|
if (!s)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (s->tty_validity == TTY_FROM_UTMP && !streq_ptr(s->tty, t)) {
|
|
|
|
/* This may happen on multiplexed SSH connection (i.e. 'SSH connection sharing'). In
|
|
|
|
* this case PAM and utmp sessions don't match. In such a case let's invalidate the TTY
|
|
|
|
* information and never acquire it again. */
|
|
|
|
|
|
|
|
s->tty = mfree(s->tty);
|
|
|
|
s->tty_validity = TTY_UTMP_INCONSISTENT;
|
|
|
|
log_debug("Session '%s' has inconsistent TTY information, dropping TTY information.", s->id);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Never override what we figured out once */
|
|
|
|
if (s->tty || s->tty_validity >= 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
s->tty = TAKE_PTR(t);
|
|
|
|
s->tty_validity = TTY_FROM_UTMP;
|
|
|
|
log_debug("Acquired TTY information '%s' from utmp for session '%s'.", s->tty, s->id);
|
|
|
|
}
|
|
|
|
|
|
|
|
#else
|
2018-11-07 17:29:21 +01:00
|
|
|
return 0;
|
2018-08-07 13:49:34 +02:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
#if ENABLE_UTMP
|
|
|
|
static int manager_dispatch_utmp(sd_event_source *s, const struct inotify_event *event, void *userdata) {
|
|
|
|
Manager *m = userdata;
|
|
|
|
|
|
|
|
assert(m);
|
|
|
|
|
|
|
|
/* If there's indication the file itself might have been removed or became otherwise unavailable, then let's
|
|
|
|
* reestablish the watch on whatever there's now. */
|
|
|
|
if ((event->mask & (IN_ATTRIB|IN_DELETE_SELF|IN_MOVE_SELF|IN_Q_OVERFLOW|IN_UNMOUNT)) != 0)
|
|
|
|
manager_connect_utmp(m);
|
|
|
|
|
|
|
|
(void) manager_read_utmp(m);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
void manager_connect_utmp(Manager *m) {
|
|
|
|
#if ENABLE_UTMP
|
|
|
|
sd_event_source *s = NULL;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(m);
|
|
|
|
|
|
|
|
/* Watch utmp for changes via inotify. We do this to deal with tools such as ssh, which will register the PAM
|
|
|
|
* session early, and acquire a TTY only much later for the connection. Thus during PAM the TTY won't be known
|
|
|
|
* yet. ssh will register itself with utmp when it finally acquired the TTY. Hence, let's make use of this, and
|
|
|
|
* watch utmp for the TTY asynchronously. We use the PAM session's leader PID as key, to find the right entry.
|
|
|
|
*
|
|
|
|
* Yes, relying on utmp is pretty ugly, but it's good enough for informational purposes, as well as idle
|
|
|
|
* detection (which, for tty sessions, relies on the TTY used) */
|
|
|
|
|
|
|
|
r = sd_event_add_inotify(m->event, &s, _PATH_UTMPX, IN_MODIFY|IN_MOVE_SELF|IN_DELETE_SELF|IN_ATTRIB, manager_dispatch_utmp, m);
|
|
|
|
if (r < 0)
|
|
|
|
log_full_errno(r == -ENOENT ? LOG_DEBUG: LOG_WARNING, r, "Failed to create inotify watch on " _PATH_UTMPX ", ignoring: %m");
|
|
|
|
else {
|
|
|
|
r = sd_event_source_set_priority(s, SD_EVENT_PRIORITY_IDLE);
|
|
|
|
if (r < 0)
|
|
|
|
log_warning_errno(r, "Failed to adjust utmp event source priority, ignoring: %m");
|
|
|
|
|
|
|
|
(void) sd_event_source_set_description(s, "utmp");
|
|
|
|
}
|
|
|
|
|
|
|
|
sd_event_source_unref(m->utmp_event_source);
|
|
|
|
m->utmp_event_source = s;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void manager_reconnect_utmp(Manager *m) {
|
|
|
|
#if ENABLE_UTMP
|
|
|
|
assert(m);
|
|
|
|
|
|
|
|
if (m->utmp_event_source)
|
|
|
|
return;
|
|
|
|
|
|
|
|
manager_connect_utmp(m);
|
|
|
|
#endif
|
|
|
|
}
|
2020-05-26 22:24:02 +02:00
|
|
|
|
|
|
|
int manager_read_efi_boot_loader_entries(Manager *m) {
|
|
|
|
#if ENABLE_EFI
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(m);
|
|
|
|
if (m->efi_boot_loader_entries_set)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
r = efi_loader_get_entries(&m->efi_boot_loader_entries);
|
|
|
|
if (r == -ENOENT || ERRNO_IS_NOT_SUPPORTED(r)) {
|
|
|
|
log_debug_errno(r, "Boot loader reported no entries.");
|
|
|
|
m->efi_boot_loader_entries_set = true;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (r < 0)
|
|
|
|
return log_error_errno(r, "Failed to determine entries reported by boot loader: %m");
|
|
|
|
|
|
|
|
m->efi_boot_loader_entries_set = true;
|
|
|
|
return 1;
|
|
|
|
#else
|
|
|
|
return 0;
|
|
|
|
#endif
|
|
|
|
}
|