Use semicolon as file path separator for lua_load (#2154)

* Use semicolon as file path separator for lua_load.
* Resolve lua path in lua_load.
* Move llua_init to initialisation(), to ensure lua state is initialized before dealing with config file.
* Move package.path setup to llua_init so globals all get set from the same place.
* Handle paths with spaces correctly in `to_real_path` by escaping spaces.

Signed-off-by: Tin Švagelj <tin.svagelj@live.com>
This commit is contained in:
Tin Švagelj
2025-02-21 00:12:44 +01:00
committed by GitHub
parent db46ca9dea
commit 97b4ec7dc9
5 changed files with 130 additions and 79 deletions

View File

@@ -266,7 +266,18 @@ values:
- function_name
- [function arguments]
- name: lua_load
desc: Loads the Lua scripts separated by spaces.
desc: |-
List of Lua script paths to load at startup in order to provide Lua
functions for other hooks. Listed files are loaded (executed) before
'lua_startup_hook' and can (but shouldn't) run code in global scope.
Paths are ';' (semicolon) separated, and can be relative to the config
file path, or absolute.
The paths were previously ' ' (space) separated, this functionality is
still supported if ';' isn't found, but is deprecated and will be removed
in future versions. Empty paths are skipped so './example file.lua;' is
valid.
- name: lua_mouse_hook
desc: |-
This function, if defined, will be called by Conky upon receiving mouse

View File

@@ -146,10 +146,14 @@ std::filesystem::path to_real_path(const std::string &source) {
wordexp_t p;
char **w;
int i;
const char *csource = source.c_str();
if (wordexp(csource, &p, 0) != 0) {
return std::string();
std::string checked = std::string(source);
std::string::size_type n = 0;
while ((n = checked.find(" ", n)) != std::string::npos) {
checked.replace(n, 1, "\\ ");
n += 2;
}
const char *csource = source.c_str();
if (wordexp(checked.c_str(), &p, 0) != 0) { return std::string(); }
w = p.we_wordv;
const char *resolved_path = strdup(w[0]);
wordfree(&p);

View File

@@ -121,7 +121,6 @@
#include "data/timeinfo.h"
#include "data/top.h"
#include "logging.h"
#include "lua/llua.h"
#include "output/nc.h"
#ifdef BUILD_MYSQL
@@ -134,6 +133,7 @@
#include "data/network/ccurl_thread.h"
#endif /* BUILD_CURL */
#include "lua/llua.h"
#include "lua/lua-config.hh"
#include "lua/setting.hh"
#include "output/display-output.hh"
@@ -1985,40 +1985,6 @@ void load_config_file() {
lua::stack_sentry s(l);
l.checkstack(2);
// Extend lua package.path so scripts can use relative paths
{
struct stat file_stat {};
std::string path_ext;
// add XDG directory to lua path
auto xdg_path =
std::filesystem::path(to_real_path(XDG_CONFIG_FILE)).parent_path();
if (stat(xdg_path.c_str(), &file_stat) == 0) {
path_ext.push_back(';');
path_ext.append(xdg_path);
path_ext.append("/?.lua");
}
auto parent_path = current_config.parent_path();
if (xdg_path != parent_path && stat(path_ext.c_str(), &file_stat) == 0) {
path_ext.push_back(';');
path_ext.append(parent_path);
path_ext.append("/?.lua");
}
l.getglobal("package");
l.getfield(-1, "path");
auto path = l.tostring(-1);
path.append(path_ext);
l.pop();
l.pushstring(path.c_str());
l.setfield(-2, "path");
l.pop();
}
try {
#ifdef BUILD_BUILTIN_CONFIG
if (current_config == builtin_config_magic) {
@@ -2171,6 +2137,7 @@ void initialisation(int argc, char **argv) {
set_default_configurations();
set_current_config();
llua_init();
load_config_file();
/* handle other command line arguments */

View File

@@ -22,18 +22,22 @@
*/
#include "config.h"
#include "build.h"
#include <cstring>
#include <filesystem>
#include <sstream>
#include "../conky.h"
#include "../geometry.h"
#include "llua.h"
#include "../logging.h"
#include "build.h"
#include "llua.h"
#ifdef BUILD_GUI
#include "../output/gui.h"
#ifdef BUILD_X11
#include "x11-settings.h"
#include "../output/x11.h"
#include "x11-settings.h"
#endif /* BUILD_X11 */
#ifdef BUILD_MOUSE_EVENTS
@@ -75,13 +79,26 @@ class lua_load_setting : public conky::simple_config_setting<std::string> {
if (init) {
std::string files = do_convert(l, -1).first;
while (!files.empty()) {
std::string::size_type pos = files.find(' ');
if (pos > 0) {
std::string file(files, 0, pos);
llua_load(file.c_str());
// Split file names into separate `\0` strings
if (files.find(';') != std::string::npos) {
for (auto &ch : files) {
if (ch == ';') { ch = '\0'; }
}
files.erase(0, pos == std::string::npos ? pos : pos + 1);
} else {
// TODO: Remove space-delimited file name handling in 3 years (2028.)
for (auto &ch : files) {
if (ch == ' ') { ch = '\0'; }
}
}
const char *start = files.c_str();
const char *end = start + files.size();
while (start < end) {
if (*start != '\0') { // Skip empty strings
llua_load(start);
}
start += strlen(start) + 1;
}
}
@@ -94,7 +111,6 @@ class lua_load_setting : public conky::simple_config_setting<std::string> {
#ifdef HAVE_SYS_INOTIFY_H
llua_rm_notifies();
#endif /* HAVE_SYS_INOTIFY_H */
if (lua_L == nullptr) { return; }
lua_close(lua_L);
lua_L = nullptr;
}
@@ -171,15 +187,50 @@ void llua_init() {
/* add our library path to the lua package.cpath global var */
luaL_openlibs(lua_L);
lua_getglobal(lua_L, "package");
lua_getfield(lua_L, -1, "cpath");
lua_getglobal(lua_L, "package"); // stack: package
lua_getfield(lua_L, -1, "cpath"); // stack: package.cpath, package
old_path = std::string(lua_tostring(lua_L, -1));
new_path = libs + old_path;
lua_pushstring(lua_L, new_path.c_str());
lua_setfield(lua_L, -3, "cpath");
lua_pop(lua_L, 2);
lua_pushstring(lua_L,
new_path.c_str()); // stack: new_path, package.cpath, package
lua_setfield(lua_L, -3, "cpath"); // stack: package.cpath, package
lua_pop(lua_L, 1); // stack: package
/* Add config file and XDG paths to package.path so scripts can load other
* scripts from relative paths */
{
struct stat file_stat{};
std::string path_ext;
// add XDG directory to lua path
auto xdg_path =
std::filesystem::path(to_real_path(XDG_CONFIG_FILE)).parent_path();
if (stat(xdg_path.c_str(), &file_stat) == 0) {
path_ext.append(xdg_path);
path_ext.append("/?.lua");
path_ext.push_back(';');
}
auto parent_path = current_config.parent_path();
if (xdg_path != parent_path && stat(path_ext.c_str(), &file_stat) == 0) {
path_ext.append(parent_path);
path_ext.append("/?.lua");
path_ext.push_back(';');
}
lua_getfield(lua_L, -1, "path"); // stack: package.path, package
old_path = std::string(lua_tostring(lua_L, -1));
new_path = path_ext + old_path;
lua_pushstring(lua_L,
new_path.c_str()); // stack: new_path, package.path, package
lua_setfield(lua_L, -3, "path"); // stack: package.path, package
lua_pop(lua_L, 1); // stack: package
}
lua_pop(lua_L, 1); // stack is empty
lua_pushstring(lua_L, PACKAGE_NAME " " VERSION " compiled for " BUILD_ARCH);
lua_setglobal(lua_L, "conky_build_info");
@@ -216,17 +267,46 @@ inline bool file_exists(const char *path) {
void llua_load(const char *script) {
int error;
std::string path = to_real_path(script);
std::filesystem::path path = to_real_path(script);
if (!file_exists(path.c_str())) {
NORM_ERR("llua_load: specified script file '%s' doesn't exist",
path.c_str());
bool found_alternative = false;
// Try resolving file name by using files in lua path:
lua_getglobal(lua_L, "package"); // stack: package
lua_getfield(lua_L, -1, "path"); // stack: package.path, package
auto lua_path = lua_tostring(lua_L, -1);
lua_pop(lua_L, 2); // stack is empty
std::stringstream path_stream(lua_path);
std::string current;
while (std::getline(path_stream, current, ';')) {
// lua_load conky variable accepts full file names, so replace "?.lua"
// with "?" to ensure file names don't get the unexpected .lua suffix; but
// modules with init.lua will still work.
size_t substitute_pos = current.find("?.lua");
if (substitute_pos != std::string::npos) {
current.replace(substitute_pos, 5, "?");
}
substitute_pos = current.find('?');
if (substitute_pos == std::string::npos) { continue; }
current.replace(substitute_pos, 1, script);
path = to_real_path(current);
if (file_exists(path.c_str())) {
found_alternative = true;
break;
}
}
if (!found_alternative) {
NORM_ERR("llua_load: specified script file '%s' doesn't exist", script);
// return without initializing lua_L because other parts of the code rely
// on it being null if the script is not loaded
return;
}
llua_init();
}
error = luaL_dofile(lua_L, path.c_str());
if (error != 0) {
@@ -347,8 +427,6 @@ static char *llua_getstring(const char *args) {
char *func;
char *ret = nullptr;
if (lua_L == nullptr) { return nullptr; }
func = llua_do_call(args, 1);
if (func != nullptr) {
if (lua_isstring(lua_L, -1) == 0) {
@@ -392,8 +470,6 @@ static char *llua_getstring_read(const char *function, const char *arg)
static int llua_getnumber(const char *args, double *ret) {
char *func;
if (lua_L == nullptr) { return 0; }
func = llua_do_call(args, 1);
if (func != nullptr) {
if (lua_isnumber(lua_L, -1) == 0) {
@@ -489,32 +565,30 @@ void llua_set_number(const char *key, double value) {
}
void llua_startup_hook() {
if ((lua_L == nullptr) || lua_startup_hook.get(*state).empty()) { return; }
if (lua_startup_hook.get(*state).empty()) { return; }
llua_do_call(lua_startup_hook.get(*state).c_str(), 0);
}
void llua_shutdown_hook() {
if ((lua_L == nullptr) || lua_shutdown_hook.get(*state).empty()) { return; }
if (lua_shutdown_hook.get(*state).empty()) { return; }
llua_do_call(lua_shutdown_hook.get(*state).c_str(), 0);
}
#ifdef BUILD_GUI
void llua_draw_pre_hook() {
if ((lua_L == nullptr) || lua_draw_hook_pre.get(*state).empty()) { return; }
if (lua_draw_hook_pre.get(*state).empty()) { return; }
llua_do_call(lua_draw_hook_pre.get(*state).c_str(), 0);
}
void llua_draw_post_hook() {
if ((lua_L == nullptr) || lua_draw_hook_post.get(*state).empty()) { return; }
if (lua_draw_hook_post.get(*state).empty()) { return; }
llua_do_call(lua_draw_hook_post.get(*state).c_str(), 0);
}
#ifdef BUILD_MOUSE_EVENTS
template <typename EventT>
bool llua_mouse_hook(const EventT &ev) {
if ((lua_L == nullptr) || lua_mouse_hook.get(*state).empty()) {
return false;
}
if (lua_mouse_hook.get(*state).empty()) { return false; }
const std::string raw_hook_name = lua_mouse_hook.get(*state);
std::string hook_name;
if (raw_hook_name.rfind("conky_", 0) == 0) {
@@ -579,7 +653,6 @@ void llua_set_userdata(const char *key, const char *type, void *value) {
}
void llua_setup_window_table(conky::rect<int> text_rect) {
if (lua_L == nullptr) { return; }
lua_newtable(lua_L);
#ifdef BUILD_X11
@@ -611,8 +684,6 @@ void llua_setup_window_table(conky::rect<int> text_rect) {
}
void llua_update_window_table(conky::rect<int> text_rect) {
if (lua_L == nullptr) { return; }
lua_getglobal(lua_L, "conky_window");
if (lua_isnil(lua_L, -1)) {
/* window table isn't populated yet */
@@ -635,7 +706,6 @@ void llua_update_window_table(conky::rect<int> text_rect) {
#endif /* BUILD_GUI */
void llua_setup_info(struct information *i, double u_interval) {
if (lua_L == nullptr) { return; }
lua_newtable(lua_L);
llua_set_number("update_interval", u_interval);
@@ -645,8 +715,6 @@ void llua_setup_info(struct information *i, double u_interval) {
}
void llua_update_info(struct information *i, double u_interval) {
if (lua_L == nullptr) { return; }
lua_getglobal(lua_L, "conky_info");
if (lua_isnil(lua_L, -1)) {
/* window table isn't populated yet */

View File

@@ -44,6 +44,7 @@ extern "C" {
void llua_inotify_query(int wd, int mask);
#endif /* HAVE_SYS_INOTIFY_H */
void llua_init();
void llua_startup_hook(void);
void llua_shutdown_hook(void);