diff --git a/.gitignore b/.gitignore index 3d14d5f..061d361 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ *.o *.so -/luastatus/luastatus /_gitignored/ # CMake CMakeCache.txt @@ -12,6 +11,4 @@ install_manifest.txt CTestTestfile.cmake build/ # generated headers -/config.h -/version.h -/probes.h +*.generated.[ch] diff --git a/CMakeLists.txt b/CMakeLists.txt index eea83aa..f2f35e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,14 +44,15 @@ else () lua53 lua-5.3 lua5.3 lua52 lua-5.2 lua5.2 lua51 lua-5.1 lua5.1 - luajit) + luajit + lua) endif () endif () function (luastatus_target_compile_with target var) target_include_directories (${target} SYSTEM PUBLIC ${${var}_INCLUDE_DIRS}) - target_compile_definitions (${target} PUBLIC + target_compile_options (${target} PUBLIC ${${var}_CFLAGS_OTHER}) endfunction () @@ -62,16 +63,19 @@ function (luastatus_target_build_with target var) endfunction () #------------------------------------------------------------------------------ +include(GNUInstallDirs) -set (BARLIBS_DIR "${CMAKE_INSTALL_PREFIX}/lib/luastatus/barlibs") -set (PLUGINS_DIR "${CMAKE_INSTALL_PREFIX}/lib/luastatus/plugins") +set (BARLIBS_DIR "${CMAKE_INSTALL_FULL_LIBDIR}/luastatus/barlibs") +set (PLUGINS_DIR "${CMAKE_INSTALL_FULL_LIBDIR}/luastatus/plugins") function (luastatus_add_barlib_or_plugin destdir name) set (sources ${ARGV}) list (REMOVE_AT sources 0 1) add_library ("${name}" MODULE ${sources}) set_target_properties ("${name}" PROPERTIES PREFIX "") - install (TARGETS "${name}" DESTINATION "${destdir}") + if (destdir) + install (TARGETS "${name}" DESTINATION "${destdir}") + endif () endfunction () function (luastatus_add_barlib) @@ -82,15 +86,24 @@ function (luastatus_add_plugin) luastatus_add_barlib_or_plugin ("${PLUGINS_DIR}" ${ARGV}) endfunction () +function (luastatus_add_barlib_noinstall) + luastatus_add_barlib_or_plugin ("" ${ARGV}) +endfunction () + +function (luastatus_add_plugin_noinstall) + luastatus_add_barlib_or_plugin ("" ${ARGV}) +endfunction () + #------------------------------------------------------------------------------ option (BUILD_BARLIB_DWM "build barlibs/dwm" ON) option (BUILD_BARLIB_I3 "build barlibs/i3" ON) +option (BUILD_BARLIB_LEMONBAR "build barlibs/lemonbar" ON) option (BUILD_PLUGIN_ALSA "build plugins/alsa" ON) option (BUILD_PLUGIN_FS "build plugins/fs" ON) +option (BUILD_PLUGIN_INOTIFY "build plugins/inotify" ON) option (BUILD_PLUGIN_MPD "build plugins/mpd" ON) -option (BUILD_PLUGIN_PIPE "build plugins/pipe" ON) option (BUILD_PLUGIN_TIMER "build plugins/timer" ON) option (BUILD_PLUGIN_XKB "build plugins/xkb" ON) option (BUILD_PLUGIN_XTITLE "build plugins/xtitle" ON) @@ -99,6 +112,7 @@ option (BUILD_PLUGIN_XTITLE "build plugins/xtitle" ON) add_subdirectory (libls) add_subdirectory (luastatus) +add_subdirectory (tests) if (BUILD_BARLIB_DWM) add_subdirectory (barlibs/dwm) @@ -108,6 +122,10 @@ if (BUILD_BARLIB_I3) add_subdirectory (barlibs/i3) endif () +if (BUILD_BARLIB_LEMONBAR) + add_subdirectory (barlibs/lemonbar) +endif () + if (BUILD_PLUGIN_ALSA) add_subdirectory (plugins/alsa) endif () @@ -116,12 +134,12 @@ if (BUILD_PLUGIN_FS) add_subdirectory (plugins/fs) endif () -if (BUILD_PLUGIN_MPD) - add_subdirectory (plugins/mpd) +if (BUILD_PLUGIN_INOTIFY) + add_subdirectory (plugins/inotify) endif () -if (BUILD_PLUGIN_PIPE) - add_subdirectory (plugins/pipe) +if (BUILD_PLUGIN_MPD) + add_subdirectory (plugins/mpd) endif () if (BUILD_PLUGIN_TIMER) diff --git a/DOCS/MIGRATION_GUIDE.md b/DOCS/MIGRATION_GUIDE.md new file mode 100644 index 0000000..3a0277f --- /dev/null +++ b/DOCS/MIGRATION_GUIDE.md @@ -0,0 +1,21 @@ +0.0.1 to 0.0.2 +=== +* `luastatus.spawn`, `luastatus.rc` and `luastatus.dollar` functions have been removed. + Use `os.execute` and/or `io.popen` instead — and please don’t forget to properly escape the arguments: + ````lua + function shell_escape(s) + return "'" .. s:gsub("'", "'\\''") .. "'" + end + ```` + +* `pipe` plugin has been removed. Use the `timer` plugin and `io.open` instead: + ````lua + wdiget = { + plugin = 'timer', + cb = function() + for line in assert(io.open('your command', 'r')):lines() do + -- blah-blah-blah + end + end, + } + ```` diff --git a/DOCS/WRITING_BARLIB_OR_PLUGIN.md b/DOCS/WRITING_BARLIB_OR_PLUGIN.md new file mode 100644 index 0000000..dd7c7dd --- /dev/null +++ b/DOCS/WRITING_BARLIB_OR_PLUGIN.md @@ -0,0 +1,36 @@ +Thread-safety +=== +Each non-thread-safe thing must be synchronized with other entities by means of +the `map_get` function (see `DOCS/design/map_get.md`). + +Misc +=== +Your plugin or barlib must not modify environment variables (this includes +`unsetenv()`, `setenv()`, `putenv()`, and modifying `environ`). + +Your plugin or barlib can install signal handlers, but: + + 1. this must be done with `sigaction()`, and `sa_mask` field of + `struct sigaction` must include `SA_RESTART`. + + 2. the handler must not be `SIG_IGN`, as it gets inherited by child + processes; + + 3. you must add a taint with the name of the signal, e.g. `"SIGUSR1"`. + +Your plugin or barlib can call `pthread_sigmask()` (and, consequently, +`pselect()`). + +Writing a plugin +=== +Copy `include/plugin_data.h`, `include/plugin_data_v1.h`, `include/plugin_v1.h` and `include/common.h`; +include `include/plugin_v1.h` and start reading `include/plugin_data.h`. + +Then, declare a global `const LuastatusIfacePlugin luastatus_iface_plugin_v1` variable. + +Writing a barlib +=== +Copy `include/barlib_data.h`, `include/barlib_data_v1.h`, `include/barlib_v1.h` and `include/common.h`; +include `include/barlib_v1.h` and start reading `include/barlib_data.h`. + +Then, declare a global `const LuastatusIfaceBarlib luastatus_iface_barlib_v1` variable. diff --git a/DOCS/c_notes/eintr-policy.md b/DOCS/c_notes/eintr-policy.md new file mode 100644 index 0000000..13e939d --- /dev/null +++ b/DOCS/c_notes/eintr-policy.md @@ -0,0 +1,5 @@ +The main problem is that all the stdio functions may, according to POSIX, fail with `EINTR`, and there is no way to restart some of them, e.g. `fprintf`. + +Our solution is to ensure that `SA_RESTART` is set for all the signals that could be raised (without terminating the program). + +See also: http://man7.org/linux/man-pages/man7/signal.7.html, section "Interruption of system calls and library functions by signal handlers". diff --git a/DOCS/c_notes/empty-ranges-and-c-stdlib.md b/DOCS/c_notes/empty-ranges-and-c-stdlib.md new file mode 100644 index 0000000..eab4c10 --- /dev/null +++ b/DOCS/c_notes/empty-ranges-and-c-stdlib.md @@ -0,0 +1,45 @@ +http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf + +Subclause 7.1.4 Use of library functions, point 1: + +> Each of the following statements applies unless explicitly stated otherwise in the +> detailed descriptions that follow: If an argument to a function has an invalid value (such +> as a value outside the domain of the function, or a pointer outside the address space of +> the program, or a null pointer, or a pointer to non-modifiable storage when the +> corresponding parameter is not const-qualified) or a type (after promotion) not expected +> by a function with variable number of arguments, the behavior is undefined. If a function +> argument is described as being an array, the pointer actually passed to the function shall +> have a value such that all address computations and accesses to objects (that would be +> valid if the pointer did point to the first element of such an array) are in fact valid. + +It’s not OK to call any function defined in `` with a pointer that can’t be dereferenced: + +Subclause 7.21.1 String handling conventions, point 2: + +> Where an argument declared as `size_t n` specifies the length of the array for a function, `n` +> can have the value zero on a call to that function. Unless explicitly stated otherwise in +> the description of a particular function in this subclause, pointer arguments on such a +> call shall still have valid values, as described in 7.1.4. On such a call, a function +> that locates a character finds no occurrence, a function that compares two character +> sequences returns zero, and a function that copies characters copies zero characters. + +It’s not OK to call `qsort`/`bsearch` with a pointer that can’t be dereferenced: + +Subclause 7.20.5 Searching and sorting utilites, point 1: + +> These utilities make use of a comparison function to search or sort arrays of unspecified +> type. Where an argument declared as `size_t nmemb` specifies the length of the array for a +> function, `nmemb` can have the value zero on a call to that function; the comparison function +> is not called, a search finds no matching element, and sorting performs no rearrangement. +> Pointer arguments on such a call shall still have valid values, as described in 7.1.4. + +However, it’s OK to call `snprintf`/`vsnprintf` with a pointer that can’t be dereferenced, as long as `n` is zero: + +Subclause 7.19.6.5 The `snprintf` function, point 2: + +> The `snprintf` function is equivalent to `fprintf`, except that the output is written into an +> array (specified by argument `s`) rather than to a stream. If `n` is zero, nothing is written, and +> `s` may be a null pointer. Otherwise, output characters beyond the `n-1`st are discarded rather +> than being written to the array, and a null character is written at the end of the characters +> actually written into the array. If copying takes place between objects that overlap, the behavior +> is undefined. diff --git a/DOCS/design/map_get.md b/DOCS/design/map_get.md new file mode 100644 index 0000000..acc66e3 --- /dev/null +++ b/DOCS/design/map_get.md @@ -0,0 +1,15 @@ +luastatus provides the following function to barlibs and plugins: + +````c +void ** (*map_get)(void *userdata, const char *key); +```` + +This function is **not** thread-safe and should only be used in the `init` function. + +luastatus maintains a global mapping from zero-terminated strings to pointers (`void *`). +`map_get` returns a pointer to the pointer corresponding to the given key; if a map entry with the given key does not exist, it creates one with a null pointer value. + +You can read and/or write from/to this pointer-to-pointer; it is guaranteed to be persistent across other calls to `map_get` and other functions. +However, note that it is **not** `void *volatile *`, so you should not write to it from functions other than `init`. + +Its intended use is for synchronization. diff --git a/README.md b/README.md index 1d3faee..d7b24d1 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ configure the way the data from plugins is processed and shown with Lua. Its main feature is that the content can be updated immediately as some event occurs, be it a change of keyboard layout, active window title, volume or a song -in your favourite player (if there is a plugin for it, of course) — a thing +in your favorite player (if there is a plugin for it, of course) — a thing rather uncommon for tiling window managers. Its motto is: @@ -16,8 +16,7 @@ Screenshot ![...](https://cloud.githubusercontent.com/assets/22565120/19625163/fecb2310-9921-11e6-90f3-17d291278531.gif) Above is i3bar with luastatus with time, battery, volume and keyboard layout -widgets. Widgets and i3bar configuration are -[here](https://github.com/shdown/luastatus/tree/master/contrib/widget-examples). +widgets. Widgets === @@ -30,7 +29,7 @@ The `widget` table **must** contain the following entries: receive data from. If it contains a slash, it is treated as a path to a shared library. If it does not, luastatus tries to load `.so` from the directory configured at the build time (CMake `PLUGINS_DIR` variable, defaults - to `${CMAKE_INSTALL_PREFIX}/lib/luastatus/plugins`). + to `${CMAKE_INSTALL_FULL_LIBDIR}/luastatus/plugins`). * `cb`: a function that converts the data received from a *plugin* to the format a *barlib* (see below) understands. It should take exactly one @@ -41,9 +40,20 @@ The `widget` table **may** contain the following entries: * `opts`: a table with plugin’s options. If undefined, an empty table will be substituted. - * `event`: a function. If defined, it will be called by the *barlib* when some - event with the widget occurs (typically a click). It should take exactly one - argument and not return anything. + * `event`: a function or a string. + - If is a function, it is called by the *barlib* when some event with the + widget occurs (typically a click). It should take exactly one argument and + not return anything; + - if is a string, it is compiled as a function in a *separate state*, and + when some event with the widget occurs, the compiled function is called in + that state (not in the widget’s state, in which `cb` gets called). This may + be useful for widgets that want not to receive data from plugin, but to + generate it themselves (possibly using some external modules). Such a + widget may want to specify `plugin = 'timer', opts = {period = 0}` and + block in `cb` until it wants to update. The problem is that in this case, + widget’s Lua mutex is almost always being acquired by `cb`, and there is + very little chance for `event` to get called. A separate-state `event` + function solves that. Plugins === @@ -51,7 +61,7 @@ A plugin is a thing that knows when to call the `cb` function and what to pass to. Plugins are shared libraries. For how to write them, see -`WRITING_BARLIB_OR_PLUGIN.md`. +`DOCS/WRITING_BARLIB_OR_PLUGIN.md`. Barlibs === @@ -64,11 +74,11 @@ A barlib (**bar** **lib**rary) is a thing that knows: * how to indicate an error, should one happen. Barlibs are shared libraries, too. For how to write them, see -`WRITING_BARLIB_OR_PLUGIN.md`. +`DOCS/WRITING_BARLIB_OR_PLUGIN.md`. Barlibs are capable of taking options. -The event loop +The mechanism === Each widget runs in its own thread and has its own Lua interpreter instance. @@ -81,39 +91,14 @@ Also, due to luastatus’ architecture, no two `event()` functions, even from different widgets, can overlap. (Note that `cb()` functions from different widgets can overlap.) -Lua libraries +Plugins’ and barlib’s Lua functions === -luastatus provides the `luastatus` module, which contains the following -functions: - -* `luastatus.rc(args)` - - Spawns a process, waits for its termination, and returns its exit status if it - exited normally, 128+**n** if it was killed by a signal with number **n**, or - 127 if there was an error spawning the process. - -* `luastatus.dollar(args)` - - Spawns a process with stdout redirected to an internal buffer, waits for its - termination, and returns output and exit status (rules for `luastatus.rc` - apply). Note that it drops all trailing newlines, as all shells do, so that - `luastatus.dollar{"echo", "hello"} == "hello"` (not `"hello\n"`). - - It is named so because of its similarity to the `$(...)` shell construct. - -* `luastatus.spawn(args)` - - Spawns a process, does **not** wait for its termination, and does not return - anything. - -Plugins and barlibs can also register Lua functions. They appear in -`luastatus.plugin` and `luastatus.barlib` submodules correspondingly. +Plugins and barlibs can register Lua functions. They appear in +`luastatus.plugin` and `luastatus.barlib` submodules, correspondingly. Lua limitations === -luastatus prohibits `os.execute`, `os.setlocale`, and `os.exit` as they are not -thread-safe. It’s pretty easy to implement `os.execute` in terms of -`luastatus.rc`, and two other just shouldn’t be used in widgets. +In luastatus, `os.setlocale` always fails as it is inherently not thread-safe. Supported Lua versions === @@ -122,30 +107,45 @@ Supported Lua versions * 5.2 * 5.3 -Usage +Getting started === +First, find your barlib’s subdirectory in the `barlibs/` directory. Then read +its `README.md` file for detailed instructions and documentation. + +Similary, for plugins’ documentation, see `README.md` files in the +subdirectories of `plugins/`. + +Finally, widget examples are in `contrib/widget-examples`. + +Using luastatus binary +=== +Note that some barlibs can provide their own wrappers for luastatus; that’s why +you should consult your barlib’s `README.md` first. + Pass a barlib with `-b`, then (optionally) its options with `-B`, then widget files. If `-b` argument contains a slash, it is treated as a path to a shared library. If it does not, luastatus tries to load `.so` from the directory configured at the build time (CMake `BARLIBS_DIR` variable, defaults to -`${CMAKE_INSTALL_PREFIX}/lib/luastatus/barlibs`). +`${CMAKE_INSTALL_FULL_LIBDIR}/luastatus/barlibs`). Example: luastatus -b dwm -B display=:0 -B separator=' ' widget1.lua widget2.lua -Note that some barlibs can provide their own wrappers for luastatus. - Installation === -`cmake . && make && make install` +`cmake . && make && sudo make install` -You can specify a Lua library to build with: `cmake -DWITH_LUA_LIBRARY=luajit .`. +You can specify a Lua library to build with: `cmake -DWITH_LUA_LIBRARY=luajit .` -You can disable building certain barlibs and plugins, e.g. `cmake -DBUILD_PLUGIN_XTITLE=OFF .`. +You can disable building certain barlibs and plugins, e.g. `cmake -DBUILD_PLUGIN_XTITLE=OFF .` Reporting bugs, requesting features, suggesting patches === Feel free to open an issue or a pull request. + +Migrating from older versions +=== +See `DOCS/MIGRATION_GUIDE.md`. diff --git a/WRITING_BARLIB_OR_PLUGIN.md b/WRITING_BARLIB_OR_PLUGIN.md deleted file mode 100644 index 4a58e7a..0000000 --- a/WRITING_BARLIB_OR_PLUGIN.md +++ /dev/null @@ -1,58 +0,0 @@ -Taints -=== -A taint is a thread-unsafe thing that only one plugin or barlib can do. Examples -are Xlib and installing signal handlers. - -Plugins and barlibs "advertise" what taints they have via the `taints` -NULL-terminating array of strings. On startup, luastatus ensures that no two -plugins, or a plugin and the barlib, have the same taint. - -Naming taints ---- -If it is a shared library, the name is `lib` + its name, e.g. for Xlib, its -`"libx11"`. - -If it is a signal handler, the name is the name of the signal, e.g. `"SIGUSR1"`. - -If neither, please open a GitHub issue. - -Threads, signals, environment variables -=== -Your plugin or barlib must be thread-safe, or, for each non-thread-safe -thing, a taint must be added. - -Your plugin or barlib must not modify environment variables (this includes -`unsetenv()`, `setenv()`, `putenv()`, and modifying `environ`). - -Your plugin or barlib can install signal handlers, but: - - 1. this must be done with `sigaction()`, and `sa_mask` field of - `struct sigaction` must include `SA_RESTART`. - - 2. the handler must not be `SIG_IGN`, as it gets inherited by child - processes; - - 3. you must add a taint with the name of the signal, e.g. `"SIGUSR1"`. - -Your plugin or barlib can call `pthread_sigmask()` (and, consequently, -`pselect()`). - -Writing a barlib -=== -You should copy `include/barlib.h`, `include/loglevel.h`, and -`include/barlib_data.h` to your project and include `include/barlib.h`. - -Then, you must declare `LuastatusBarlibIface luastatus_barlib_iface` as a -global variable. - -Read `include/barlib_data.h` for more information. - -Writing a plugin -=== -You should copy `include/plugin.h` and `include/plugin_data.h` to your -project and include `include/plugin.h`. - -Then, you must declare `LuastatusPluginIface luastatus_plugin_iface` as a -global variable. - -Read `include/plugin_data.h` for more information. diff --git a/barlibs/dwm/README.md b/barlibs/dwm/README.md index 76d5e51..7692183 100644 --- a/barlibs/dwm/README.md +++ b/barlibs/dwm/README.md @@ -6,18 +6,7 @@ It does not provide functions and does not support events. `cb` return value === -A string or nil. Nil is equivalent to an empty string. - -Example -=== -````lua -widget = { - plugin = 'timer', - cb = function(t) - return os.date('%H:%M') - end, -} -```` +A string, an array (that is, a table with numeric keys) of strings (all non-empty elements of which are joined by the separator), or nil. Nil or an empty table is equivalent to an empty string. Options === diff --git a/barlibs/dwm/dwm.c b/barlibs/dwm/dwm.c index 96637dc..2ebdcf4 100644 --- a/barlibs/dwm/dwm.c +++ b/barlibs/dwm/dwm.c @@ -1,5 +1,5 @@ -#include "include/barlib.h" -#include "include/barlib_logf_macros.h" +#include "include/barlib_v1.h" +#include "include/sayf_macros.h" #include #include @@ -11,78 +11,97 @@ #include "libls/alloc_utils.h" #include "libls/cstring_utils.h" -#include "libls/string.h" +#include "libls/string_.h" #include "libls/vector.h" +#include "libls/lua_utils.h" +// Barlib's private data typedef struct { + // Number of widgets. size_t nwidgets; + + // Content of the widgets. LSString *bufs; - LSString glued; + + // Buffer for the content of the widgets joined by /sep/. + LSString joined; + + // A zero-terminated separator string. char *sep; + + // The XCB connection. xcb_connection_t *conn; + + // The root window. xcb_window_t root; } Priv; +static void -priv_destroy(Priv *p) +destroy(LuastatusBarlibData *bd) { + Priv *p = bd->priv; for (size_t i = 0; i < p->nwidgets; ++i) { LS_VECTOR_FREE(p->bufs[i]); } free(p->bufs); - LS_VECTOR_FREE(p->glued); + LS_VECTOR_FREE(p->joined); free(p->sep); if (p->conn) { xcb_disconnect(p->conn); } + free(p); } +static bool redraw(LuastatusBarlibData *bd) { Priv *p = bd->priv; - LSString *glued = &p->glued; + LSString *joined = &p->joined; size_t n = p->nwidgets; LSString *bufs = p->bufs; const char *sep = p->sep; - LS_VECTOR_CLEAR(*glued); + LS_VECTOR_CLEAR(*joined); for (size_t i = 0; i < n; ++i) { if (bufs[i].size) { - if (glued->size) { - ls_string_append_s(glued, sep); + if (joined->size) { + ls_string_append_s(joined, sep); } - ls_string_append_b(glued, bufs[i].data, bufs[i].size); + ls_string_append_b(joined, bufs[i].data, bufs[i].size); } } + xcb_generic_error_t *err = xcb_request_check( p->conn, xcb_change_property_checked(p->conn, XCB_PROP_MODE_REPLACE, p->root, XCB_ATOM_WM_NAME, - XCB_ATOM_STRING, /*apparently, UTF-8*/ 8, glued->size, glued->data)); + XCB_ATOM_STRING, 8, joined->size, joined->data)); if (err) { - LUASTATUS_FATALF(bd, "XCB error %d occured", err->error_code); + LS_FATALF(bd, "XCB error %d occured", err->error_code); free(err); return false; } return true; } -LuastatusBarlibInitResult +static +int init(LuastatusBarlibData *bd, const char *const *opts, size_t nwidgets) { Priv *p = bd->priv = LS_XNEW(Priv, 1); *p = (Priv) { .nwidgets = nwidgets, .bufs = LS_XNEW(LSString, nwidgets), - .glued = LS_VECTOR_NEW_RESERVE(char, 1024), + .joined = LS_VECTOR_NEW_RESERVE(char, 1024), .sep = NULL, .conn = NULL, }; for (size_t i = 0; i < nwidgets; ++i) { LS_VECTOR_INIT_RESERVE(p->bufs[i], 512); } - // display=, separator= may be specified multiple times + // All the options may be passed multiple times! const char *dpyname = NULL; const char *sep = NULL; for (const char *const *s = opts; *s; ++s) { @@ -92,17 +111,18 @@ init(LuastatusBarlibData *bd, const char *const *opts, size_t nwidgets) } else if ((v = ls_strfollow(*s, "separator="))) { sep = v; } else { - LUASTATUS_FATALF(bd, "unknown option '%s'", *s); + LS_FATALF(bd, "unknown option '%s'", *s); goto error; } } p->sep = ls_xstrdup(sep ? sep : " | "); + // Initialize /p->conn/ and /p->root/. int screenp; p->conn = xcb_connect(dpyname, &screenp); int r = xcb_connection_has_error(p->conn); if (r != 0) { - LUASTATUS_FATALF(bd, "can't connect to display: XCB error %d", r); + LS_FATALF(bd, "can't connect to display: XCB error %d", r); goto error; } const xcb_setup_t *setup = xcb_get_setup(p->conn); @@ -111,58 +131,85 @@ init(LuastatusBarlibData *bd, const char *const *opts, size_t nwidgets) xcb_screen_next(&iter); } p->root = iter.data->root; - // clear the current name + + // Clear the current name. if (!redraw(bd)) { goto error; } - return LUASTATUS_BARLIB_INIT_RESULT_OK; + + return LUASTATUS_OK; error: - priv_destroy(p); - free(p); - return LUASTATUS_BARLIB_INIT_RESULT_ERR; + destroy(bd); + return LUASTATUS_ERR; } -LuastatusBarlibSetResult +static +int set(LuastatusBarlibData *bd, lua_State *L, size_t widget_idx) { Priv *p = bd->priv; - int type = lua_type(L, -1); - if (type == LUA_TSTRING) { - size_t ns; - const char *s = lua_tolstring(L, -1, &ns); - ls_string_assign_b(&p->bufs[widget_idx], s, ns); - } else if (type == LUA_TNIL) { + switch (lua_type(L, -1)) { + case LUA_TSTRING: + { + size_t ns; + const char *s = lua_tolstring(L, -1, &ns); + ls_string_assign_b(&p->bufs[widget_idx], s, ns); + } + break; + case LUA_TNIL: LS_VECTOR_CLEAR(p->bufs[widget_idx]); - } else { - LUASTATUS_ERRF(bd, "expected string or nil, found %s", luaL_typename(L, -1)); - return LUASTATUS_BARLIB_SET_RESULT_NONFATAL_ERR; + break; + case LUA_TTABLE: + { + LSString *buf = &p->bufs[widget_idx]; + const char *sep = p->sep; + + LS_VECTOR_CLEAR(*buf); + LS_LUA_TRAVERSE(L, -1) { + if (!lua_isnumber(L, LS_LUA_KEY)) { + LS_ERRF(bd, "table key: expected number, found %s", + luaL_typename(L, LS_LUA_KEY)); + return LUASTATUS_NONFATAL_ERR; + } + if (!lua_isstring(L, LS_LUA_VALUE)) { + LS_ERRF(bd, "table value: expected string, found %s", + luaL_typename(L, LS_LUA_VALUE)); + return LUASTATUS_NONFATAL_ERR; + } + size_t ns; + const char *s = lua_tolstring(L, LS_LUA_VALUE, &ns); + if (buf->size && ns) { + ls_string_append_s(buf, sep); + } + ls_string_append_b(buf, s, ns); + } + } + break; + default: + LS_ERRF(bd, "expected table, string or nil, found %s", luaL_typename(L, -1)); + return LUASTATUS_NONFATAL_ERR; } + if (!redraw(bd)) { - return LUASTATUS_BARLIB_SET_RESULT_FATAL_ERR; + return LUASTATUS_ERR; } - return LUASTATUS_BARLIB_SET_RESULT_OK; + return LUASTATUS_OK; } -LuastatusBarlibSetErrorResult +static +int set_error(LuastatusBarlibData *bd, size_t widget_idx) { Priv *p = bd->priv; ls_string_assign_s(&p->bufs[widget_idx], "(Error)"); if (!redraw(bd)) { - return LUASTATUS_BARLIB_SET_ERROR_RESULT_FATAL_ERR; + return LUASTATUS_ERR; } - return LUASTATUS_BARLIB_SET_ERROR_RESULT_OK; -} - -void -destroy(LuastatusBarlibData *bd) -{ - priv_destroy(bd->priv); - free(bd->priv); + return LUASTATUS_OK; } -LuastatusBarlibIface luastatus_barlib_iface = { +LuastatusBarlibIface luastatus_barlib_iface_v1 = { .init = init, .set = set, .set_error = set_error, diff --git a/barlibs/i3/CMakeLists.txt b/barlibs/i3/CMakeLists.txt index 047a9a3..a9224fe 100644 --- a/barlibs/i3/CMakeLists.txt +++ b/barlibs/i3/CMakeLists.txt @@ -9,4 +9,6 @@ find_package (PkgConfig REQUIRED) pkg_check_modules (YAJL REQUIRED yajl>=2.1.0) luastatus_target_build_with (i3 YAJL) -install (FILES luastatus-i3-wrapper DESTINATION bin) +include(GNUInstallDirs) + +install (PROGRAMS luastatus-i3-wrapper DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/barlibs/i3/README.md b/barlibs/i3/README.md index 6e56b57..2435cbc 100644 --- a/barlibs/i3/README.md +++ b/barlibs/i3/README.md @@ -2,8 +2,8 @@ This barlib talks with `i3bar`. Redirections and `luastatus-i3-wrapper` === -`i3bar` requires all the data to be written to stdout and read from stdin. This makes it very easy to mess things up: Lua’s `print()` prints to stdout, processes spawned by widgets/plugins/barlib inherit our stdin and stdout, etc. -That’s why this barlib requires that stdin and stdout file descriptors are manually redirected. A shell wrapper, `luastatus-i3-wrapper`, is shipped with it; it does all the redirections and executes `luastatus` with `-b i3`, all the required `-B` options, and additional arguments passed by you. +`i3bar` requires all the data to be written to stdout and read from stdin. This makes it very easy to mess things up: Lua’s `print()` prints to stdout, processes spawned by widgets/plugins inherit our stdin and stdout, etc. +That’s why this barlib requires that stdin and stdout file descriptors are manually redirected. A shell wrapper, `luastatus-i3-wrapper`, is shipped with it; it does all the redirections and executes `luastatus` (or whatever is in `LUASTATUS` environment variable) with `-b i3` (or whatever is in `LUASTATUS_I3_BARLIB` environment variable), all the required `-B` options, and additional arguments passed by you. `cb` return value === @@ -21,7 +21,9 @@ See http://i3wm.org/docs/i3bar-protocol.html#_click_events. Functions === -`pango_escape` escapes text for Pango markup. +* `escaped_str = luastatus.plugin.pango_escape(str)` + + Escapes text for Pango markup. Example === @@ -29,25 +31,26 @@ Example i = 0 widget = { plugin = 'timer', - opts = {timeout=2.0}, + opts = {period = 2}, cb = function(t) i = i+1 if i == 1 then return {} -- hides widget; alternatively, you can return nil. elseif i == 2 then -- no markup unless you specify markup='pango' - return {full_text='', color='#aaaa00'} + return {full_text = '', color = '#aaaa00'} elseif i == 3 then -- see https://developer.gnome.org/pango/stable/PangoMarkupFormat.html - return {full_text='Hello, ' .. luastatus.barlib.pango_escape(luastatus.dollar{'whoami'}) .. '!', markup='pango'} + return {full_text = 'Hello, ' .. luastatus.barlib.pango_escape(luastatus.dollar{'whoami'}) .. '!', + markup = 'pango'} elseif i == 4 then i = 0 return { - {full_text='Now,', instance='now-segment'}, + {full_text = 'Now,', instance = 'now-segment'}, nil, -- nils are ignored so that you can do -- return {get_time(), get_battery(), get_smth_else()} -- where, for example, get_battery() returns nil if the battery is full. - {full_text='click me!', instance='click-me-segment'}, + {full_text = 'click me!', instance = 'click-me-segment'}, } end end, diff --git a/barlibs/i3/event_watcher.c b/barlibs/i3/event_watcher.c index db00746..d7f0eb8 100644 --- a/barlibs/i3/event_watcher.c +++ b/barlibs/i3/event_watcher.c @@ -1,86 +1,86 @@ #include "event_watcher.h" + #include #include -#include +#include #include #include #include -#include + +#include "include/sayf_macros.h" + #include "libls/errno_utils.h" #include "libls/parse_int.h" -#include "libls/string.h" +#include "libls/strarr.h" #include "libls/vector.h" -#include "include/barlib_logf_macros.h" + #include "priv.h" -typedef struct { - size_t offset, size; -} EventWatcherString; +typedef size_t StringIndex; typedef struct { enum { - EVENT_WATCHER_VALUE_TYPE_STRING, - EVENT_WATCHER_VALUE_TYPE_DOUBLE, - EVENT_WATCHER_VALUE_TYPE_BOOL, + TYPE_STRING, + TYPE_DOUBLE, + TYPE_BOOL, } type; union { - EventWatcherString s; + StringIndex s_idx; double d; bool b; } u; -} EventWatcherValue; +} Value; typedef struct { - EventWatcherString key; - EventWatcherValue value; -} EventWatcherKeyValue; + StringIndex key_idx; + Value value; +} KeyValue; typedef struct { - LuastatusBarlibData *bd; - LuastatusBarlibEWCallBegin *call_begin; - LuastatusBarlibEWCallEnd *call_end; - enum { - EVENT_WATCHER_STATE_EXPECTING_ARRAY_BEGIN, - EVENT_WATCHER_STATE_EXPECTING_MAP_BEGIN, - EVENT_WATCHER_STATE_INSIDE_MAP, + STATE_EXPECTING_ARRAY_BEGIN, + STATE_EXPECTING_MAP_BEGIN, + STATE_INSIDE_MAP, } state; + LSStringArray strs; + LS_VECTOR_OF(KeyValue) params; + StringIndex last_key_idx; + int widget; - LSString buf; - EventWatcherString last_key; - LS_VECTOR_OF(EventWatcherKeyValue) params; - int widget_idx; -} EventWatcherCtx; + LuastatusBarlibData *bd; + LuastatusBarlibEWFuncs funcs; +} Context; static inline -EventWatcherString -append_to_buf(EventWatcherCtx *ctx, const char *buf, size_t nbuf) +StringIndex +append_to_strs(Context *ctx, const char *buf, size_t nbuf) { - size_t offset = ctx->buf.size; - ls_string_append_b(&ctx->buf, buf, nbuf); - return (EventWatcherString) {.offset = offset, .size = nbuf}; + ls_strarr_append(&ctx->strs, buf, nbuf); + return ls_strarr_size(ctx->strs) - 1; } static int -callback_value_helper(EventWatcherCtx *ctx, EventWatcherValue value) +value_helper(Context *ctx, Value value) { - if (ctx->state != EVENT_WATCHER_STATE_INSIDE_MAP) { - LUASTATUS_ERRF(ctx->bd, "(event watcher) unexpected JSON value"); + if (ctx->state != STATE_INSIDE_MAP) { + LS_ERRF(ctx->bd, "(event watcher) unexpected JSON value"); return 0; } - if (ctx->last_key.size == 4 && - memcmp("name", ctx->buf.data + ctx->last_key.offset, 4) == 0) - { - if (value.type != EVENT_WATCHER_VALUE_TYPE_STRING) { - LUASTATUS_ERRF(ctx->bd, "(event watcher) 'name' is not a string"); + size_t nkey; + const char *key = ls_strarr_at(ctx->strs, ctx->last_key_idx, &nkey); + if (nkey == 4 && memcmp("name", key, 4) == 0) { + if (value.type != TYPE_STRING) { + LS_ERRF(ctx->bd, "(event watcher) 'name' is not a string"); return 0; } - // parse error is OK here, widget_idx is checked later - ctx->widget_idx = ls_full_parse_uint(ctx->buf.data + value.u.s.offset, value.u.s.size); + size_t nname; + const char *name = ls_strarr_at(ctx->strs, value.u.s_idx, &nname); + // parse error is OK here, ctx->widget is checked later + ctx->widget = ls_full_parse_uint_b(name, nname); } else { - LS_VECTOR_PUSH(ctx->params, ((EventWatcherKeyValue) { - .key = ctx->last_key, + LS_VECTOR_PUSH(ctx->params, ((KeyValue) { + .key_idx = ctx->last_key_idx, .value = value, })); } @@ -91,12 +91,12 @@ static int callback_null(void *vctx) { - EventWatcherCtx *ctx = vctx; - if (ctx->state != EVENT_WATCHER_STATE_INSIDE_MAP) { - LUASTATUS_ERRF(ctx->bd, "(event watcher) unexpected null"); + Context *ctx = vctx; + if (ctx->state != STATE_INSIDE_MAP) { + LS_ERRF(ctx->bd, "(event watcher) unexpected null"); return 0; } - // nothing to do + // nothing to do: Lua tables can't have nil values return 1; } @@ -104,8 +104,8 @@ static int callback_boolean(void *vctx, int value) { - return callback_value_helper(vctx, (EventWatcherValue){ - .type = EVENT_WATCHER_VALUE_TYPE_BOOL, + return value_helper(vctx, (Value) { + .type = TYPE_BOOL, .u = { .b = value }, }); } @@ -114,8 +114,8 @@ static int callback_integer(void *vctx, long long value) { - return callback_value_helper(vctx, (EventWatcherValue){ - .type = EVENT_WATCHER_VALUE_TYPE_DOUBLE, + return value_helper(vctx, (Value) { + .type = TYPE_DOUBLE, .u = { .d = value }, }); } @@ -124,8 +124,8 @@ static int callback_double(void *vctx, double value) { - return callback_value_helper(vctx, (EventWatcherValue){ - .type = EVENT_WATCHER_VALUE_TYPE_DOUBLE, + return value_helper(vctx, (Value) { + .type = TYPE_DOUBLE, .u = { .d = value }, }); } @@ -134,9 +134,9 @@ static int callback_string(void *vctx, const unsigned char *buf, size_t nbuf) { - return callback_value_helper(vctx, (EventWatcherValue){ - .type = EVENT_WATCHER_VALUE_TYPE_STRING, - .u = { .s = append_to_buf(vctx, (const char*) buf, nbuf) }, + return value_helper(vctx, (Value) { + .type = TYPE_STRING, + .u = { .s_idx = append_to_strs(vctx, (const char *) buf, nbuf) }, }); } @@ -144,15 +144,15 @@ static int callback_start_map(void *vctx) { - EventWatcherCtx *ctx = vctx; - if (ctx->state != EVENT_WATCHER_STATE_EXPECTING_MAP_BEGIN) { - LUASTATUS_ERRF(ctx->bd, "(event watcher) unexpected {"); + Context *ctx = vctx; + if (ctx->state != STATE_EXPECTING_MAP_BEGIN) { + LS_ERRF(ctx->bd, "(event watcher) unexpected {"); return 0; } - ctx->state = EVENT_WATCHER_STATE_INSIDE_MAP; - LS_VECTOR_CLEAR(ctx->buf); + ctx->state = STATE_INSIDE_MAP; + ls_strarr_clear(&ctx->strs); LS_VECTOR_CLEAR(ctx->params); - ctx->widget_idx = -1; + ctx->widget = -1; return 1; } @@ -160,12 +160,12 @@ static int callback_map_key(void *vctx, const unsigned char *buf, size_t nbuf) { - EventWatcherCtx *ctx = vctx; - if (ctx->state != EVENT_WATCHER_STATE_INSIDE_MAP) { - LUASTATUS_ERRF(ctx->bd, "(event watcher) unexpected map key"); + Context *ctx = vctx; + if (ctx->state != STATE_INSIDE_MAP) { + LS_ERRF(ctx->bd, "(event watcher) unexpected map key"); return 0; } - ctx->last_key = append_to_buf(vctx, (const char*) buf, nbuf); + ctx->last_key_idx = append_to_strs(vctx, (const char *) buf, nbuf); return 1; } @@ -173,37 +173,43 @@ static int callback_end_map(void *vctx) { - EventWatcherCtx *ctx = vctx; - if (ctx->state != EVENT_WATCHER_STATE_INSIDE_MAP) { - LUASTATUS_ERRF(ctx->bd, "(event watcher) unexpected }"); + Context *ctx = vctx; + if (ctx->state != STATE_INSIDE_MAP) { + LS_ERRF(ctx->bd, "(event watcher) unexpected }"); return 0; } - ctx->state = EVENT_WATCHER_STATE_EXPECTING_MAP_BEGIN; + ctx->state = STATE_EXPECTING_MAP_BEGIN; + +#define PUSH_STRS_ELEM(Idx_) \ + do { \ + size_t ns_; \ + const char *s_ = ls_strarr_at(ctx->strs, Idx_, &ns_); \ + lua_pushlstring(L, s_, ns_); \ + } while (0) - if (ctx->widget_idx >= 0) { - lua_State *L = ctx->call_begin(ctx->bd->userdata, ctx->widget_idx); + Priv *p = ctx->bd->priv; + if (ctx->widget >= 0 || (size_t) ctx->widget < p->nwidgets) { + lua_State *L = ctx->funcs.call_begin(ctx->bd->userdata, ctx->widget); // L: - lua_newtable(L); // L: table for (size_t i = 0; i < ctx->params.size; ++i) { // L: table - EventWatcherKeyValue kv = ctx->params.data[i]; -#define PUSH_EW_STR(S_) \ - lua_pushlstring(L, ctx->buf.data + (S_).offset, (S_).size) - PUSH_EW_STR(kv.key); // L: table key + KeyValue kv = ctx->params.data[i]; + PUSH_STRS_ELEM(kv.key_idx); // L: table key switch (kv.value.type) { - case EVENT_WATCHER_VALUE_TYPE_STRING: - PUSH_EW_STR(kv.value.u.s); // L: table key value + case TYPE_STRING: + PUSH_STRS_ELEM(kv.value.u.s_idx); // L: table key value break; - case EVENT_WATCHER_VALUE_TYPE_DOUBLE: + case TYPE_DOUBLE: lua_pushnumber(L, kv.value.u.d); // L: table key value break; - case EVENT_WATCHER_VALUE_TYPE_BOOL: + case TYPE_BOOL: lua_pushboolean(L, kv.value.u.b); // L: table key value } lua_rawset(L, -3); // L: table } - ctx->call_end(ctx->bd->userdata, ctx->widget_idx); -#undef PUSH_EW_STR + ctx->funcs.call_end(ctx->bd->userdata, ctx->widget); +#undef PUSH_STRS_ELEM } return 1; } @@ -212,12 +218,12 @@ static int callback_start_array(void *vctx) { - EventWatcherCtx *ctx = vctx; - if (ctx->state != EVENT_WATCHER_STATE_EXPECTING_ARRAY_BEGIN) { - LUASTATUS_ERRF(ctx->bd, "(event watcher) unexpected ["); + Context *ctx = vctx; + if (ctx->state != STATE_EXPECTING_ARRAY_BEGIN) { + LS_ERRF(ctx->bd, "(event watcher) unexpected ["); return 0; } - ctx->state = EVENT_WATCHER_STATE_EXPECTING_MAP_BEGIN; + ctx->state = STATE_EXPECTING_MAP_BEGIN; return 1; } @@ -225,29 +231,26 @@ static int callback_end_array(void *vctx) { - EventWatcherCtx *ctx = vctx; - LUASTATUS_ERRF(ctx->bd, "(event watcher) unexpected ]"); + Context *ctx = vctx; + LS_ERRF(ctx->bd, "(event watcher) unexpected ]"); return 0; } -LuastatusBarlibEWResult -event_watcher(LuastatusBarlibData *bd, - LuastatusBarlibEWCallBegin call_begin, - LuastatusBarlibEWCallEnd call_end) +int +event_watcher(LuastatusBarlibData *bd, LuastatusBarlibEWFuncs funcs) { Priv *p = bd->priv; if (p->noclickev) { - return LUASTATUS_BARLIB_EW_RESULT_NO_MORE_EVENTS; + return LUASTATUS_NONFATAL_ERR; } - EventWatcherCtx ctx = { - .bd = bd, - .call_begin = call_begin, - .call_end = call_end, - .state = EVENT_WATCHER_STATE_EXPECTING_ARRAY_BEGIN, - .buf = LS_VECTOR_NEW(), + Context ctx = { + .state = STATE_EXPECTING_ARRAY_BEGIN, + .strs = ls_strarr_new(), .params = LS_VECTOR_NEW(), - .widget_idx = -1, + .widget = -1, + .bd = bd, + .funcs = funcs, }; yajl_handle hand = yajl_alloc( &(yajl_callbacks) { @@ -271,11 +274,11 @@ event_watcher(LuastatusBarlibData *bd, ssize_t nread = read(p->in_fd, buf, sizeof(buf)); if (nread < 0) { LS_WITH_ERRSTR(s, errno, - LUASTATUS_ERRF(bd, "(event watcher) read error: %s", s); + LS_ERRF(bd, "(event watcher) read error: %s", s); ); goto error; } else if (nread == 0) { - LUASTATUS_ERRF(bd, "(event watcher) i3bar closed its end of the pipe"); + LS_ERRF(bd, "(event watcher) i3bar closed its end of the pipe"); goto error; } switch (yajl_parse(hand, buf, nread)) { @@ -286,7 +289,7 @@ event_watcher(LuastatusBarlibData *bd, case yajl_status_error: { unsigned char *descr = yajl_get_error(hand, /*verbose*/ 1, buf, nread); - LUASTATUS_ERRF(bd, "(event watcher) yajl parse error: %s", (char*) descr); + LS_ERRF(bd, "(event watcher) yajl parse error: %s", (char *) descr); yajl_free_error(hand, descr); } goto error; @@ -294,8 +297,8 @@ event_watcher(LuastatusBarlibData *bd, } error: - LS_VECTOR_FREE(ctx.buf); + ls_strarr_destroy(ctx.strs); LS_VECTOR_FREE(ctx.params); yajl_free(hand); - return LUASTATUS_BARLIB_EW_RESULT_FATAL_ERR; + return LUASTATUS_ERR; } diff --git a/barlibs/i3/event_watcher.h b/barlibs/i3/event_watcher.h index 77be556..cd67bbb 100644 --- a/barlibs/i3/event_watcher.h +++ b/barlibs/i3/event_watcher.h @@ -1,11 +1,9 @@ #ifndef event_watcher_h_ #define event_watcher_h_ -#include "include/barlib_data.h" +#include "include/barlib_data_v1.h" -LuastatusBarlibEWResult -event_watcher(LuastatusBarlibData *bd, - LuastatusBarlibEWCallBegin call_begin, - LuastatusBarlibEWCallEnd call_end); +int +event_watcher(LuastatusBarlibData *bd, LuastatusBarlibEWFuncs funcs); #endif diff --git a/barlibs/i3/generator_utils.c b/barlibs/i3/generator_utils.c index 694dfc2..5fb219c 100644 --- a/barlibs/i3/generator_utils.c +++ b/barlibs/i3/generator_utils.c @@ -10,7 +10,7 @@ json_ls_string_append_escaped_s(LSString *s, const char *zts) size_t prev = 0; size_t i; for (i = 0; ; ++i) { - unsigned char c = (unsigned char) zts[i]; + unsigned char c = zts[i]; if (c == '\0') { break; } @@ -24,7 +24,6 @@ json_ls_string_append_escaped_s(LSString *s, const char *zts) ls_string_append_c(s, '"'); } - bool json_ls_string_append_number(LSString *s, double value) { @@ -34,26 +33,23 @@ json_ls_string_append_number(LSString *s, double value) return ls_string_append_f(s, "%.20g", value); } - void pango_ls_string_append_escaped_b(LSString *s, const char *buf, size_t nbuf) { size_t prev = 0; for (size_t i = 0; i < nbuf; ++i) { + const char *esc; switch (buf[i]) { -#define RE(Sym_, Esc_) \ - case Sym_: \ - ls_string_append_b(s, buf + prev, i - prev); \ - ls_string_append_s(s, Esc_); \ - prev = i + 1; \ - break; - RE('&', "&") - RE('<', "<") - RE('>', ">") - RE('\'', "'") - RE('"', """) -#undef RE + case '&': esc = "&"; break; + case '<': esc = "<"; break; + case '>': esc = ">"; break; + case '\'': esc = "'"; break; + case '"': esc = """; break; + default: continue; } + ls_string_append_b(s, buf + prev, i - prev); + ls_string_append_s(s, esc); + prev = i + 1; } ls_string_append_b(s, buf + prev, nbuf - prev); } diff --git a/barlibs/i3/generator_utils.h b/barlibs/i3/generator_utils.h index 261efe0..65894e6 100644 --- a/barlibs/i3/generator_utils.h +++ b/barlibs/i3/generator_utils.h @@ -1,10 +1,11 @@ #ifndef generator_utils_h_ #define generator_utils_h_ -#include "libls/string.h" #include #include +#include "libls/string_.h" + void json_ls_string_append_escaped_s(LSString *s, const char *zts); diff --git a/barlibs/i3/i3.c b/barlibs/i3/i3.c index 090c9fa..8dc6fa3 100644 --- a/barlibs/i3/i3.c +++ b/barlibs/i3/i3.c @@ -6,9 +6,11 @@ #include #include #include -#include "include/barlib.h" -#include "include/barlib_logf_macros.h" -#include "libls/string.h" + +#include "include/barlib_v1.h" +#include "include/sayf_macros.h" + +#include "libls/string_.h" #include "libls/vector.h" #include "libls/alloc_utils.h" #include "libls/parse_int.h" @@ -17,13 +19,16 @@ #include "libls/io_utils.h" #include "libls/osdep.h" #include "libls/lua_utils.h" + #include "priv.h" #include "generator_utils.h" #include "event_watcher.h" +static void -priv_destroy(Priv *p) +destroy(LuastatusBarlibData *bd) { + Priv *p = bd->priv; for (size_t i = 0; i < p->nwidgets; ++i) { LS_VECTOR_FREE(p->bufs[i]); } @@ -33,9 +38,11 @@ priv_destroy(Priv *p) fclose(p->out); } LS_VECTOR_FREE(p->luabuf); + free(p); } -LuastatusBarlibInitResult +static +int init(LuastatusBarlibData *bd, const char *const *opts, size_t nwidgets) { Priv *p = bd->priv = LS_XNEW(Priv, 1); @@ -52,19 +59,20 @@ init(LuastatusBarlibData *bd, const char *const *opts, size_t nwidgets) LS_VECTOR_INIT_RESERVE(p->bufs[i], 1024); } + // All the options may be passed multiple times! int in_fd = -1; int out_fd = -1; bool allow_stopping = false; for (const char *const *s = opts; *s; ++s) { const char *v; if ((v = ls_strfollow(*s, "in_fd="))) { - if ((in_fd = ls_full_parse_uint_cstr(v)) < 0) { - LUASTATUS_FATALF(bd, "in_fd value is not a valid unsigned integer"); + if ((in_fd = ls_full_parse_uint_s(v)) < 0) { + LS_FATALF(bd, "in_fd value is not a valid unsigned integer"); goto error; } } else if ((v = ls_strfollow(*s, "out_fd="))) { - if ((out_fd = ls_full_parse_uint_cstr(v)) < 0) { - LUASTATUS_FATALF(bd, "out_fd value is not a valid unsigned integer"); + if ((out_fd = ls_full_parse_uint_s(v)) < 0) { + LS_FATALF(bd, "out_fd value is not a valid unsigned integer"); goto error; } } else if (strcmp(*s, "no_click_events") == 0) { @@ -74,18 +82,20 @@ init(LuastatusBarlibData *bd, const char *const *opts, size_t nwidgets) } else if (strcmp(*s, "allow_stopping") == 0) { allow_stopping = true; } else { - LUASTATUS_FATALF(bd, "unknown option '%s'", *s); + LS_FATALF(bd, "unknown option '%s'", *s); goto error; } } // check + // we require in_fd/out_fd to be not less that 3 because making stdin/stdout/stderr CLOEXEC has + // very bad consequences, and we just don't want to complicate the logic. if (in_fd < 3) { - LUASTATUS_FATALF(bd, "in_fd is not specified or less than 3"); + LS_FATALF(bd, "in_fd is not specified or less than 3"); goto error; } if (out_fd < 3) { - LUASTATUS_FATALF(bd, "out_fd is not specified or less than 3"); + LS_FATALF(bd, "out_fd is not specified or less than 3"); goto error; } @@ -93,7 +103,7 @@ init(LuastatusBarlibData *bd, const char *const *opts, size_t nwidgets) p->in_fd = in_fd; if (!(p->out = fdopen(out_fd, "w"))) { LS_WITH_ERRSTR(s, errno, - LUASTATUS_FATALF(bd, "can't fdopen %d: %s", out_fd, s); + LS_FATALF(bd, "can't fdopen %d: %s", out_fd, s); ); goto error; } @@ -101,13 +111,13 @@ init(LuastatusBarlibData *bd, const char *const *opts, size_t nwidgets) // make CLOEXEC if (ls_make_cloexec(in_fd) < 0) { LS_WITH_ERRSTR(s, errno, - LUASTATUS_FATALF(bd, "can't make fd %d CLOEXEC: %s", in_fd, s); + LS_FATALF(bd, "can't make fd %d CLOEXEC: %s", in_fd, s); ); goto error; } if (ls_make_cloexec(out_fd) < 0) { LS_WITH_ERRSTR(s, errno, - LUASTATUS_FATALF(bd, "can't make fd %d CLOEXEC: %s", out_fd, s); + LS_FATALF(bd, "can't make fd %d CLOEXEC: %s", out_fd, s); ); goto error; } @@ -121,19 +131,19 @@ init(LuastatusBarlibData *bd, const char *const *opts, size_t nwidgets) fflush(p->out); if (ferror(p->out)) { LS_WITH_ERRSTR(s, errno, - LUASTATUS_FATALF(bd, "write error: %s", s); + LS_FATALF(bd, "write error: %s", s); ); goto error; } - return LUASTATUS_BARLIB_INIT_RESULT_OK; + return LUASTATUS_OK; error: - priv_destroy(p); - free(p); - return LUASTATUS_BARLIB_INIT_RESULT_ERR; + destroy(bd); + return LUASTATUS_ERR; } +static bool redraw(LuastatusBarlibData *bd) { @@ -158,21 +168,19 @@ redraw(LuastatusBarlibData *bd) fflush(out); if (ferror(out)) { LS_WITH_ERRSTR(s, errno, - LUASTATUS_FATALF(bd, "write error: %s", s); + LS_FATALF(bd, "write error: %s", s); ); return false; } return true; } +static int l_pango_escape(lua_State *L) { - // L: - size_t ns; - // WARNING: luaL_check*() call luaL_error() on error, which, in turn, calls lua_error(), - // which, it turn, does longjmp(). If this function should acquire any resource, it should be - // done *after* calling luaL_check*(). + // WARNING: luaL_check*() functions do a long jump on error! const char *s = luaL_checklstring(L, 1, &ns); LuastatusBarlibData *bd = lua_touserdata(L, lua_upvalueindex(1)); @@ -181,10 +189,12 @@ l_pango_escape(lua_State *L) LSString *buf = &p->luabuf; LS_VECTOR_CLEAR(*buf); pango_ls_string_append_escaped_b(buf, s, ns); + // L: - lua_pushlstring(L, buf->data, buf->size); // L: string return 1; } +static void register_funcs(LuastatusBarlibData *bd, lua_State *L) { @@ -194,8 +204,11 @@ register_funcs(LuastatusBarlibData *bd, lua_State *L) ls_lua_rawsetf(L, "pango_escape"); // L: table } +// Appends a JSON segment to ((Priv *) bd->priv)->bufs[widget_idx] from the table at position +// table_pos on L's stack. +static bool -append_segment(LuastatusBarlibData *bd, lua_State *L, int table_idx, size_t widget_idx) +append_segment(LuastatusBarlibData *bd, lua_State *L, int table_pos, size_t widget_idx) { Priv *p = bd->priv; LSString *s = &p->bufs[widget_idx]; @@ -205,15 +218,15 @@ append_segment(LuastatusBarlibData *bd, lua_State *L, int table_idx, size_t widg } ls_string_append_f(s, "{\"name\":\"%zu\"", widget_idx); bool separator_key_found = false; - LS_LUA_TRAVERSE(L, table_idx) { - if (!lua_isstring(L, LS_LUA_TRAVERSE_KEY)) { - LUASTATUS_ERRF(bd, "segment key: expected string, found %s", luaL_typename(L, -2)); + LS_LUA_TRAVERSE(L, table_pos) { + if (!lua_isstring(L, LS_LUA_KEY)) { + LS_ERRF(bd, "segment key: expected string, found %s", luaL_typename(L, -2)); return false; } - const char *key = lua_tostring(L, LS_LUA_TRAVERSE_KEY); + const char *key = lua_tostring(L, LS_LUA_KEY); if (strcmp(key, "name") == 0) { - LUASTATUS_WARNF(bd, "segment: ignoring 'name', it is set automatically; use 'instance' " - "instead"); + LS_WARNF(bd, "segment: ignoring 'name', it is set automatically; use 'instance' " + "instead"); continue; } else if (strcmp(key, "separator") == 0) { separator_key_found = true; @@ -221,22 +234,22 @@ append_segment(LuastatusBarlibData *bd, lua_State *L, int table_idx, size_t widg ls_string_append_c(s, ','); json_ls_string_append_escaped_s(s, key); ls_string_append_c(s, ':'); - switch (lua_type(L, LS_LUA_TRAVERSE_VALUE)) { + switch (lua_type(L, LS_LUA_VALUE)) { case LUA_TNUMBER: - if (!json_ls_string_append_number(s, lua_tonumber(L, LS_LUA_TRAVERSE_VALUE))) { - LUASTATUS_ERRF(bd, "segment entry '%s': invalid number (NaN/Inf)", key); + if (!json_ls_string_append_number(s, lua_tonumber(L, LS_LUA_VALUE))) { + LS_ERRF(bd, "segment entry '%s': invalid number (NaN/Inf)", key); return false; } break; case LUA_TSTRING: - json_ls_string_append_escaped_s(s, lua_tostring(L, LS_LUA_TRAVERSE_VALUE)); + json_ls_string_append_escaped_s(s, lua_tostring(L, LS_LUA_VALUE)); break; case LUA_TBOOLEAN: - ls_string_append_s(s, lua_toboolean(L, LS_LUA_TRAVERSE_VALUE) ? "true" : "false"); + ls_string_append_s(s, lua_toboolean(L, LS_LUA_VALUE) ? "true" : "false"); break; default: - LUASTATUS_ERRF(bd, "segment entry '%s': expected string, number or boolean, found %s", - key, luaL_typename(L, LS_LUA_TRAVERSE_VALUE)); + LS_ERRF(bd, "segment entry '%s': expected string, number or boolean, found %s", + key, luaL_typename(L, LS_LUA_VALUE)); return false; } } @@ -247,82 +260,79 @@ append_segment(LuastatusBarlibData *bd, lua_State *L, int table_idx, size_t widg return true; } -LuastatusBarlibSetResult +static +int set(LuastatusBarlibData *bd, lua_State *L, size_t widget_idx) { Priv *p = bd->priv; LS_VECTOR_CLEAR(p->bufs[widget_idx]); - int type = lua_type(L, -1); - if (type == LUA_TNIL) { - goto done; - } else if (type != LUA_TTABLE) { - LUASTATUS_ERRF(bd, "expected table or nil, found %s", luaL_typename(L, -1)); - goto invalid_data; - } - - // L: table - lua_pushnil(L); // L: table nil - if (lua_next(L, -2)) { - // table is not empty - // L: table key value - bool is_array = lua_isnumber(L, -2); - lua_pop(L, 2); // L: table - if (is_array) { - LS_LUA_TRAVERSE(L, -1) { - if (!lua_istable(L, LS_LUA_TRAVERSE_VALUE)) { - LUASTATUS_ERRF(bd, "array value: expected table, found %s", - luaL_typename(L, LS_LUA_TRAVERSE_VALUE)); - goto invalid_data; + switch (lua_type(L, -1)) { + case LUA_TNIL: + break; + case LUA_TTABLE: + // L: table + lua_pushnil(L); // L: table nil + if (lua_next(L, -2)) { + // table is not empty + // L: table key value + bool is_array = lua_isnumber(L, -2); + lua_pop(L, 2); // L: table + if (is_array) { + LS_LUA_TRAVERSE(L, -1) { + if (!lua_istable(L, LS_LUA_VALUE)) { + LS_ERRF(bd, "array value: expected table, found %s", + luaL_typename(L, LS_LUA_VALUE)); + goto invalid_data; + } + if (!append_segment(bd, L, LS_LUA_VALUE, widget_idx)) { + goto invalid_data; + } } - if (!append_segment(bd, L, LS_LUA_TRAVERSE_VALUE, widget_idx)) { + } else { + if (!append_segment(bd, L, -1, widget_idx)) { goto invalid_data; } } - } else { - if (!append_segment(bd, L, -1, widget_idx)) { - goto invalid_data; - } - } - } // else: L: table + } // else: L: table + break; + default: + LS_ERRF(bd, "expected table or nil, found %s", luaL_typename(L, -1)); + goto invalid_data; + } -done: if (!redraw(bd)) { - return LUASTATUS_BARLIB_SET_RESULT_FATAL_ERR; + return LUASTATUS_ERR; } - return LUASTATUS_BARLIB_SET_RESULT_OK; + return LUASTATUS_OK; invalid_data: + // the buffer may contain an invalid JSON at this point; just clear it. LS_VECTOR_CLEAR(p->bufs[widget_idx]); - return LUASTATUS_BARLIB_SET_RESULT_NONFATAL_ERR; + return LUASTATUS_NONFATAL_ERR; } -LuastatusBarlibSetErrorResult +static +int set_error(LuastatusBarlibData *bd, size_t widget_idx) { Priv *p = bd->priv; LSString *s = &p->bufs[widget_idx]; - ls_string_assign_s(s, "{\"full_text\":\"(Error)\",\"color\":\"#ff0000\""); + ls_string_assign_s(s, "{\"full_text\":\"(Error)\",\"color\":\"#ff0000\"" + ",\"background\":\"#000000\""); if (p->noseps) { ls_string_append_s(s, ",\"separator\":false"); } ls_string_append_c(s, '}'); if (!redraw(bd)) { - return LUASTATUS_BARLIB_SET_ERROR_RESULT_FATAL_ERR; + return LUASTATUS_ERR; } - return LUASTATUS_BARLIB_SET_ERROR_RESULT_OK; -} - -void -destroy(LuastatusBarlibData *bd) -{ - priv_destroy(bd->priv); - free(bd->priv); + return LUASTATUS_OK; } -LuastatusBarlibIface luastatus_barlib_iface = { +LuastatusBarlibIface luastatus_barlib_iface_v1 = { .init = init, .register_funcs = register_funcs, .set = set, diff --git a/barlibs/i3/luastatus-i3-wrapper b/barlibs/i3/luastatus-i3-wrapper index 71b5afd..ece0d9d 100755 --- a/barlibs/i3/luastatus-i3-wrapper +++ b/barlibs/i3/luastatus-i3-wrapper @@ -1,2 +1,2 @@ #!/bin/sh -exec ${LUASTATUS:-luastatus} -b i3 3<&0 0&1 1>&2 -B out_fd=4 "$@" +exec ${LUASTATUS:-luastatus} -b ${LUASTATUS_I3_BARLIB:-i3} 3<&0 0&1 1>&2 -B out_fd=4 "$@" diff --git a/barlibs/i3/priv.h b/barlibs/i3/priv.h index d40a288..1ed076d 100644 --- a/barlibs/i3/priv.h +++ b/barlibs/i3/priv.h @@ -1,18 +1,32 @@ #ifndef priv_h_ #define priv_h_ -#include "libls/string.h" #include #include #include +#include "libls/string_.h" + +// Barlib's private data typedef struct { + // Number of widgets. size_t nwidgets; + + // Content of the widgets. LSString *bufs; + // Input file descriptor number. int in_fd; + + // /fdopen/'ed output file descritor. FILE *out; + + // Whether or not the /no_click_events/ option was specified. bool noclickev; + + // Whether or not the /no_separators/ option was specified bool noseps; + + // The buffer for the /pango_escape/ Lua function. LSString luabuf; } Priv; diff --git a/barlibs/lemonbar/CMakeLists.txt b/barlibs/lemonbar/CMakeLists.txt new file mode 100644 index 0000000..88571a9 --- /dev/null +++ b/barlibs/lemonbar/CMakeLists.txt @@ -0,0 +1,10 @@ +file (GLOB sources "*.c") +luastatus_add_barlib (lemonbar $ ${sources}) + +target_compile_definitions (lemonbar PUBLIC -D_POSIX_C_SOURCE=200809L) +luastatus_target_compile_with (lemonbar LUA) +target_include_directories (lemonbar PUBLIC "${PROJECT_SOURCE_DIR}") + +include(GNUInstallDirs) + +install (PROGRAMS luastatus-lemonbar-launcher DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/barlibs/lemonbar/README.md b/barlibs/lemonbar/README.md new file mode 100644 index 0000000..b5167f6 --- /dev/null +++ b/barlibs/lemonbar/README.md @@ -0,0 +1,43 @@ +This barlib talks with `lemonbar`. + +It joins all non-empty strings returned by widgets by a separator, which defaults to ` | `. + +Redirections and `luastatus-lemonbar-launcher` +=== +`lemonbar` is not capable of creating a bidirectional pipe itself; instead, it requires all the data to be written to its stdin and read from its stdout. +That’s why the input/output file descriptors of this barlib must be manually redirected. + +A launcher, `luastatus-lemonbar-launcher`, is shipped with it; it spawns `lemonbar` (or whatever is in `LEMONBAR` environment variable) connected to a bidirectional pipe and executes `luastatus` (or whatever is in `LUASTATUS` environment variable) with `-b lemonbar` (or whatever is in `LUASTATUS_LEMONBAR_BARLIB` environment variable), all the required `-B` options, and additional arguments passed by you. + +Pass each `lemonbar` argument with `-p`, then pass `--`, then pass luastatus arguments, e.g.: +````bash +luastatus-lemonbar-launcher -p -B#111111 -p -f'Droid Sans Mono for Powerline:pixelsize=12:weight=Bold' -- -Bseparator=' ' widget1.lua widget2.lua +```` + +`cb` return value +=== +A string with lemonbar markup, an array (that is, a table with numeric keys) of such strings (all non-empty elements of which are joined by the separator), or nil. Nil or an empty table is equivalent to an empty string. + +Functions +=== +* `escaped_str = luastatus.plugin.escape(str)` + + Escapes text for lemonbar markup. + +`event` argument +=== +A string with a name of the command. + +Options +=== +* `in_fd=` + + File descriptor to read `lemonbar` input from. Usually set by the launcher. + +* `out_fd=` + + File descriptor to write to. Usually set by the launcher. + +* `separator=` + + Set the separator. diff --git a/barlibs/lemonbar/lemonbar.c b/barlibs/lemonbar/lemonbar.c new file mode 100644 index 0000000..85b6242 --- /dev/null +++ b/barlibs/lemonbar/lemonbar.c @@ -0,0 +1,324 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "include/barlib_v1.h" +#include "include/sayf_macros.h" + +#include "libls/string_.h" +#include "libls/vector.h" +#include "libls/cstring_utils.h" +#include "libls/parse_int.h" +#include "libls/errno_utils.h" +#include "libls/io_utils.h" +#include "libls/lua_utils.h" +#include "libls/alloc_utils.h" + +#include "markup_utils.h" + +// Barlib's private data +typedef struct { + // Number of widgets. + size_t nwidgets; + + // Content of the widgets. + LSString *bufs; + + // A zero-terminated separator string. + char *sep; + + // /fdopen/'ed input file descriptor. + FILE *in; + + // /fdopen/'ed output file descriptor. + FILE *out; + + // Buffer for the /escape/ Lua function. + LSString luabuf; +} Priv; + +static +void +destroy(LuastatusBarlibData *bd) +{ + Priv *p = bd->priv; + for (size_t i = 0; i < p->nwidgets; ++i) { + LS_VECTOR_FREE(p->bufs[i]); + } + free(p->bufs); + free(p->sep); + if (p->in) { + fclose(p->in); + } + if (p->out) { + fclose(p->out); + } + LS_VECTOR_FREE(p->luabuf); + free(p); +} + +static +int +init(LuastatusBarlibData *bd, const char *const *opts, size_t nwidgets) +{ + Priv *p = bd->priv = LS_XNEW(Priv, 1); + *p = (Priv) { + .nwidgets = nwidgets, + .bufs = LS_XNEW(LSString, nwidgets), + .sep = NULL, + .in = NULL, + .out = NULL, + .luabuf = LS_VECTOR_NEW(), + }; + for (size_t i = 0; i < nwidgets; ++i) { + LS_VECTOR_INIT_RESERVE(p->bufs[i], 512); + } + + // All the options may be passed multiple times! + const char *sep = NULL; + int in_fd = -1; + int out_fd = -1; + for (const char *const *s = opts; *s; ++s) { + const char *v; + if ((v = ls_strfollow(*s, "in_fd="))) { + if ((in_fd = ls_full_parse_uint_s(v)) < 0) { + LS_FATALF(bd, "in_fd value is not a valid unsigned integer"); + goto error; + } + } else if ((v = ls_strfollow(*s, "out_fd="))) { + if ((out_fd = ls_full_parse_uint_s(v)) < 0) { + LS_FATALF(bd, "out_fd value is not a valid unsigned integer"); + goto error; + } + } else if ((v = ls_strfollow(*s, "separator="))) { + sep = v; + } else { + LS_FATALF(bd, "unknown option '%s'", *s); + goto error; + } + } + p->sep = ls_xstrdup(sep ? sep : " | "); + + // check + // we require in_fd/out_fd to be not less that 3 because making stdin/stdout/stderr CLOEXEC has + // very bad consequences, and we just don't want to complicate the logic. + if (in_fd < 3) { + LS_FATALF(bd, "in_fd is not specified or less than 3"); + goto error; + } + if (out_fd < 3) { + LS_FATALF(bd, "out_fd is not specified or less than 3"); + goto error; + } + + // open + if (!(p->in = fdopen(in_fd, "r"))) { + LS_WITH_ERRSTR(s, errno, + LS_FATALF(bd, "can't fdopen %d: %s", in_fd, s); + ); + goto error; + } + if (!(p->out = fdopen(out_fd, "w"))) { + LS_WITH_ERRSTR(s, errno, + LS_FATALF(bd, "can't fdopen %d: %s", out_fd, s); + ); + goto error; + } + + // make CLOEXEC + if (ls_make_cloexec(in_fd) < 0) { + LS_WITH_ERRSTR(s, errno, + LS_FATALF(bd, "can't make fd %d CLOEXEC: %s", in_fd, s); + ); + goto error; + } + if (ls_make_cloexec(out_fd) < 0) { + LS_WITH_ERRSTR(s, errno, + LS_FATALF(bd, "can't make fd %d CLOEXEC: %s", out_fd, s); + ); + goto error; + } + + return LUASTATUS_OK; + +error: + destroy(bd); + return LUASTATUS_ERR; +} + +static +int +l_escape(lua_State *L) +{ + size_t ns; + // WARNING: /luaL_check*()/ functions do a long jump on error! + const char *s = luaL_checklstring(L, 1, &ns); + + LuastatusBarlibData *bd = lua_touserdata(L, lua_upvalueindex(1)); + Priv *p = bd->priv; + + LSString *buf = &p->luabuf; + LS_VECTOR_CLEAR(*buf); + + lemonbar_ls_string_append_escaped_b(buf, s, ns); + + // L: - + lua_pushlstring(L, buf->data, buf->size); // L: string + return 1; +} + +static +void +register_funcs(LuastatusBarlibData *bd, lua_State *L) +{ + // L: table + lua_pushlightuserdata(L, bd); // L: table bd + lua_pushcclosure(L, l_escape, 1); // L: table bd l_escape + ls_lua_rawsetf(L, "escape"); // L: table +} + +static +bool +redraw(LuastatusBarlibData *bd) +{ + Priv *p = bd->priv; + FILE *out = p->out; + size_t n = p->nwidgets; + LSString *bufs = p->bufs; + const char *sep = p->sep; + + bool first = true; + for (size_t i = 0; i < n; ++i) { + if (bufs[i].size) { + if (!first) { + fputs(sep, out); + } + fwrite(bufs[i].data, 1, bufs[i].size, out); + first = false; + } + } + fputc('\n', out); + fflush(out); + if (ferror(out)) { + LS_WITH_ERRSTR(s, errno, + LS_FATALF(bd, "write error: %s", s); + ); + return false; + } + return true; +} + +static +int +set(LuastatusBarlibData *bd, lua_State *L, size_t widget_idx) +{ + Priv *p = bd->priv; + LSString *buf = &p->bufs[widget_idx]; + + LS_VECTOR_CLEAR(*buf); + switch (lua_type(L, -1)) { + case LUA_TNIL: + break; + case LUA_TSTRING: + { + size_t ns; + const char *s = lua_tolstring(L, -1, &ns); + lemonbar_ls_string_append_sanitized_b(buf, widget_idx, s, ns); + } + break; + case LUA_TTABLE: + { + const char *sep = p->sep; + LS_LUA_TRAVERSE(L, -1) { + if (!lua_isnumber(L, LS_LUA_KEY)) { + LS_ERRF(bd, "table key: expected number, found %s", + luaL_typename(L, LS_LUA_KEY)); + return LUASTATUS_NONFATAL_ERR; + } + if (!lua_isstring(L, LS_LUA_VALUE)) { + LS_ERRF(bd, "table value: expected string, found %s", + luaL_typename(L, LS_LUA_VALUE)); + return LUASTATUS_NONFATAL_ERR; + } + size_t ns; + const char *s = lua_tolstring(L, LS_LUA_VALUE, &ns); + if (buf->size && ns) { + ls_string_append_s(buf, sep); + } + lemonbar_ls_string_append_sanitized_b(buf, widget_idx, s, ns); + } + } + break; + default: + LS_ERRF(bd, "expected string or nil, found %s", luaL_typename(L, -1)); + return LUASTATUS_NONFATAL_ERR; + } + + if (!redraw(bd)) { + return LUASTATUS_ERR; + } + return LUASTATUS_OK; +} + +static +int +set_error(LuastatusBarlibData *bd, size_t widget_idx) +{ + Priv *p = bd->priv; + ls_string_assign_s(&p->bufs[widget_idx], "%{B#f00}%{F#fff}(Error)%{B-}%{F-}"); + if (!redraw(bd)) { + return LUASTATUS_ERR; + } + return LUASTATUS_OK; +} + +static +int +event_watcher(LuastatusBarlibData *bd, LuastatusBarlibEWFuncs funcs) +{ + Priv *p = bd->priv; + + char *buf = NULL; + size_t nbuf = 256; + + for (ssize_t nread; (nread = getline(&buf, &nbuf, p->in)) >= 0;) { + if (nread == 0 || buf[nread - 1] != '\n') { + continue; + } + size_t ncommand; + size_t widget_idx; + const char *command = lemonbar_parse_command(buf, nread - 1, &ncommand, &widget_idx); + if (!command) { + continue; + } + lua_State *L = funcs.call_begin(bd->userdata, widget_idx); + lua_pushlstring(L, command, ncommand); + funcs.call_end(bd->userdata, widget_idx); + } + + if (feof(p->in)) { + LS_ERRF(bd, "lemonbar closed its pipe end"); + } else { + LS_WITH_ERRSTR(s, errno, + LS_ERRF(bd, "read error: %s", s); + ); + } + + free(buf); + + return LUASTATUS_ERR; +} + +LuastatusBarlibIface luastatus_barlib_iface_v1 = { + .init = init, + .register_funcs = register_funcs, + .set = set, + .set_error = set_error, + .event_watcher = event_watcher, + .destroy = destroy, +}; diff --git a/barlibs/lemonbar/luastatus-lemonbar-launcher b/barlibs/lemonbar/luastatus-lemonbar-launcher new file mode 100755 index 0000000..5f0e3dd --- /dev/null +++ b/barlibs/lemonbar/luastatus-lemonbar-launcher @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +if (( BASH_VERSINFO[0] < 4 )); then + echo >&2 "bash 4.0 or higher is required (this one is $BASH_VERSION)." + exit 1 +fi + +usage() { + echo >&2 "$* +USAGE: ${0##*/} [-p lemonbar_argument [-p ...]] -- [luastatus_argument [...]] +Note that '--' is mandatory." + exit 2 +} + +lemonbar_args=() +# There is no easy way to require '--' with getopts, so we parse $@ manually. +while true; do + (( $# == 0 )) && usage "'--' argument not found." + case "$1" in + --) + shift + break + ;; + -p) + (( $# == 1 )) && usage "'-p' option requires an argument." + shift + lemonbar_args+=("$1") + ;; + -p*) + lemonbar_args+=("${1:2}") + ;; + *) + usage "Unexpected argument '$1' found before '--'." + ;; + esac + shift +done + +coproc ${LEMONBAR:-lemonbar} "${lemonbar_args[@]}" +eval "exec <&${COPROC[0]} >&${COPROC[1]}" +exec ${LUASTATUS:-luastatus} -b ${LUASTATUS_LEMONBAR_BARLIB:-lemonbar} 3<&0 0&1 1>&2 -B out_fd=4 "$@" diff --git a/barlibs/lemonbar/markup_utils.c b/barlibs/lemonbar/markup_utils.c new file mode 100644 index 0000000..8486ef8 --- /dev/null +++ b/barlibs/lemonbar/markup_utils.c @@ -0,0 +1,81 @@ +#include "markup_utils.h" + +#include "libls/string_.h" +#include "libls/parse_int.h" + +#include +#include +#include + +void +lemonbar_ls_string_append_escaped_b(LSString *buf, const char *s, size_t ns) +{ + // just replace all "%"s with "%%" + + // we have to check ns before calling memchr, see DOCS/c_notes/empty-ranges-and-c-stdlib.md + for (const char *t; ns && (t = memchr(s, '%', ns));) { + const size_t nseg = t - s + 1; + ls_string_append_b(buf, s, nseg); + ls_string_append_c(buf, '%'); + ns -= nseg; + s += nseg; + } + ls_string_append_b(buf, s, ns); +} + +void +lemonbar_ls_string_append_sanitized_b(LSString *buf, size_t widget_idx, const char *s, size_t ns) +{ + size_t prev = 0; + bool a_tag = false; + for (size_t i = 0; i < ns; ++i) { + switch (s[i]) { + case '\n': + ls_string_append_b(buf, s + prev, i - prev); + prev = i + 1; + break; + + case '%': + if (i + 1 < ns) { + if (s[i + 1] == '{' && i + 2 < ns && s[i + 2] == 'A') { + a_tag = true; + } else if (s[i + 1] == '%') { + ++i; + } + } + break; + + case ':': + if (a_tag) { + ls_string_append_b(buf, s + prev, i + 1 - prev); + ls_string_append_f(buf, "%zu_", widget_idx); + prev = i + 1; + a_tag = false; + } + break; + + case '}': + a_tag = false; + break; + } + } + ls_string_append_b(buf, s + prev, ns - prev); +} + +const char * +lemonbar_parse_command(const char *line, size_t nline, size_t *ncommand, size_t *widget_idx) +{ + const char *endptr; + const int idx = ls_parse_uint_b(line, nline, &endptr); + if (idx < 0 || + endptr == line || + endptr == line + nline || + *endptr != '_') + { + return NULL; + } + const char *command = endptr + 1; + *ncommand = nline - (command - line); + *widget_idx = idx; + return command; +} diff --git a/barlibs/lemonbar/markup_utils.h b/barlibs/lemonbar/markup_utils.h new file mode 100644 index 0000000..44857f5 --- /dev/null +++ b/barlibs/lemonbar/markup_utils.h @@ -0,0 +1,17 @@ +#ifndef markup_utils_h_ +#define markup_utils_h_ + +#include + +#include "libls/string_.h" + +void +lemonbar_ls_string_append_escaped_b(LSString *buf, const char *s, size_t ns); + +void +lemonbar_ls_string_append_sanitized_b(LSString *buf, size_t widget_idx, const char *s, size_t ns); + +const char * +lemonbar_parse_command(const char *line, size_t nline, size_t *ncommand, size_t *widget_idx); + +#endif diff --git a/contrib/luastatus-0.1.0-r1.ebuild b/contrib/luastatus-0.1.0-r1.ebuild new file mode 100644 index 0000000..338c43c --- /dev/null +++ b/contrib/luastatus-0.1.0-r1.ebuild @@ -0,0 +1,82 @@ +# Copyright 1999-2017 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 +# $Id$ + +EAPI=6 +CMAKE_IN_SOURCE_BUILD=1 +inherit cmake-utils + +DESCRIPTION="Universal status bar content generator" +HOMEPAGE="https://github.com/shdown/luastatus" + +if [[ ${PV} == *9999* ]]; then + inherit git-r3 + SRC_URI="" + EGIT_REPO_URI="https://github.com/shdown/${PN}.git" + KEYWORDS="~amd64 ~x86" +else + SRC_URI="https://github.com/shdown/${PN}/archive/v${PV}.tar.gz -> ${P}.tar.gz" + KEYWORDS="amd64 x86" +fi + +BARLIBS=" + ${PN}_barlibs_dwm + ${PN}_barlibs_i3 +" + +PLUGINS=" + +${PN}_plugins_alsa + +${PN}_plugins_fs + +${PN}_plugins_mpd + +${PN}_plugins_pipe + +${PN}_plugins_timer + +${PN}_plugins_xkb + +${PN}_plugins_xtitle +" + +LICENSE="LGPL-3+" +SLOT="0" +IUSE="examples luajit ${BARLIBS} ${PLUGINS}" + +DEPEND="" +RDEPEND="${DEPEND} + luajit? ( dev-lang/luajit:2 ) + !luajit? ( dev-lang/lua:0 ) + ${PN}_plugins_xtitle? ( x11-libs/libxcb x11-libs/xcb-util-wm ) + ${PN}_plugins_xkb? ( x11-libs/libX11 ) + ${PN}_plugins_alsa? ( media-libs/alsa-lib ) + ${PN}_barlibs_dwm? ( x11-libs/libxcb ) + ${PN}_barlibs_i3? ( >=dev-libs/yajl-2.1.0 ) +" + +src_configure() { + local mycmakeargs=( + $(use luajit && echo -DWITH_LUA_LIBRARY=luajit) + -DBUILD_BARLIB_DWM=$(usex ${PN}_barlibs_dwm) + -DBUILD_BARLIB_I3=$(usex ${PN}_barlibs_i3) + -DBUILD_PLUGIN_ALSA=$(usex ${PN}_plugins_alsa) + -DBUILD_PLUGIN_FS=$(usex ${PN}_plugins_fs) + -DBUILD_PLUGIN_MPD=$(usex ${PN}_plugins_mpd) + -DBUILD_PLUGIN_PIPE=$(usex ${PN}_plugins_pipe) + -DBUILD_PLUGIN_TIMER=$(usex ${PN}_plugins_timer) + -DBUILD_PLUGIN_XKB=$(usex ${PN}_plugins_xkb) + -DBUILD_PLUGIN_XTITLE=$(usex ${PN}_plugins_xtitle) + ) + cmake-utils_src_configure +} + +src_install() { + default + local i barlib + if use examples; then + dodir /usr/share/doc/${PF}/widget-examples + docinto widget-examples + for i in ${BARLIBS//+/}; do + if use ${i}; then + barlib=${i#${PN}_barlibs_} + dodoc -r contrib/widget-examples/${barlib} + docompress -x /usr/share/doc/${PF}/widget-examples/${barlib} + fi + done + fi +} diff --git a/contrib/luastatus-9999-r2.ebuild b/contrib/luastatus-9999-r2.ebuild new file mode 100644 index 0000000..f71247d --- /dev/null +++ b/contrib/luastatus-9999-r2.ebuild @@ -0,0 +1,106 @@ +# Copyright 1999-2017 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 +# $Id$ + +EAPI=6 +CMAKE_IN_SOURCE_BUILD=1 +inherit cmake-utils + +DESCRIPTION="Universal status bar content generator" +HOMEPAGE="https://github.com/shdown/luastatus" + +if [[ ${PV} == *9999* ]]; then + inherit git-r3 + SRC_URI="" + EGIT_REPO_URI="https://github.com/shdown/${PN}.git" + KEYWORDS="~amd64 ~x86" +else + SRC_URI="https://github.com/shdown/${PN}/archive/v${PV}.tar.gz -> ${P}.tar.gz" + KEYWORDS="amd64 x86" +fi + +BARLIBS=" + ${PN}_barlibs_dwm + ${PN}_barlibs_i3 + ${PN}_barlibs_lemonbar +" + +PLUGINS=" + +${PN}_plugins_alsa + +${PN}_plugins_fs + +${PN}_plugins_mpd + +${PN}_plugins_pipe + +${PN}_plugins_timer + +${PN}_plugins_xkb + +${PN}_plugins_xtitle + +${PN}_plugins_inotify +" + +LICENSE="LGPL-3+" +SLOT="0" +IUSE="doc examples luajit ${BARLIBS} ${PLUGINS}" + +DEPEND="" +RDEPEND="${DEPEND} + luajit? ( dev-lang/luajit:2 ) + !luajit? ( dev-lang/lua:0 ) + ${PN}_plugins_xtitle? ( x11-libs/libxcb x11-libs/xcb-util-wm ) + ${PN}_plugins_xkb? ( x11-libs/libX11 ) + ${PN}_plugins_alsa? ( media-libs/alsa-lib ) + ${PN}_barlibs_dwm? ( x11-libs/libxcb ) + ${PN}_barlibs_i3? ( >=dev-libs/yajl-2.1.0 ) +" + +src_configure() { + local mycmakeargs=( + $(use luajit && echo -DWITH_LUA_LIBRARY=luajit) + -DBUILD_BARLIB_DWM=$(usex ${PN}_barlibs_dwm) + -DBUILD_BARLIB_I3=$(usex ${PN}_barlibs_i3) + -DBUILD_BARLIB_LEMONBAR=$(usex ${PN}_barlibs_lemonbar) + -DBUILD_PLUGIN_ALSA=$(usex ${PN}_plugins_alsa) + -DBUILD_PLUGIN_FS=$(usex ${PN}_plugins_fs) + -DBUILD_PLUGIN_MPD=$(usex ${PN}_plugins_mpd) + -DBUILD_PLUGIN_PIPE=$(usex ${PN}_plugins_pipe) + -DBUILD_PLUGIN_TIMER=$(usex ${PN}_plugins_timer) + -DBUILD_PLUGIN_XKB=$(usex ${PN}_plugins_xkb) + -DBUILD_PLUGIN_XTITLE=$(usex ${PN}_plugins_xtitle) + -DBUILD_PLUGIN_INOTIFY=$(usex ${PN}_plugins_inotify) + ) + cmake-utils_src_configure +} + +src_install() { + default + local i barlib plugin + if use examples; then + dodir /usr/share/doc/${PF}/widget-examples + docinto widget-examples + for i in ${BARLIBS//+/}; do + if use ${i}; then + barlib=${i#${PN}_barlibs_} + dodoc -r contrib/widget-examples/${barlib} + docompress -x /usr/share/doc/${PF}/widget-examples/${barlib} + fi + done + fi + + if use doc; then + for i in ${PLUGINS//+/}; do + if use ${i}; then + plugin=${i#${PN}_plugins_} + dodir /usr/share/doc/${PF}/plugins/${plugin} + docinto plugins/${plugin} + dodoc plugins/${plugin}/README.md + fi + done + + for i in ${BARLIBS//+/}; do + if use ${i}; then + barlib=${i#${PN}_barlibs_} + dodir /usr/share/doc/${PF}/barlibs/${barlib} + docinto barlibs/${barlib} + dodoc barlibs/${barlib}/README.md + fi + done + fi +} diff --git a/contrib/luastatus.spec b/contrib/luastatus.spec new file mode 100644 index 0000000..5b8b781 --- /dev/null +++ b/contrib/luastatus.spec @@ -0,0 +1,50 @@ +Name: luastatus +Version: 0.1.0 +Release: 1%{?dist} +Summary: Lua status + +License: LGPL3+ +URL: https://github.com/shdown/luastatus +Source0: https://github.com/shdown/luastatus/archive/v%version.tar.gz + +BuildRequires: cmake +BuildRequires: luajit-devel +BuildRequires: libxcb-devel +BuildRequires: yajl-devel +BuildRequires: alsa-lib-devel +BuildRequires: xcb-util-wm-devel +BuildRequires: xcb-util-devel + +%description +Lua status daemon + +%package plugins +Summary: Lua status plugins +Requires: %{name} = %{version}-%{release} + +%description plugins +Lua status plugins + +%prep +%setup -q + +%build +%cmake -DWITH_LUA_LIBRARY=luajit . + +%make_build + +%install +%make_install + +%files +%doc LICENSE.txt README.md +%{_bindir}/luastatus +%{_bindir}/luastatus-i3-wrapper +%{_mandir}/man1/luastatus.1* +%{_libdir}/luastatus/barlibs/*.so + +%files plugins +%{_libdir}/luastatus/plugins/*.so + +%changelog + diff --git a/contrib/widget-examples/alsa.lua b/contrib/widget-examples/dwm/alsa.lua similarity index 57% rename from contrib/widget-examples/alsa.lua rename to contrib/widget-examples/dwm/alsa.lua index 8a8f049..054a806 100644 --- a/contrib/widget-examples/alsa.lua +++ b/contrib/widget-examples/dwm/alsa.lua @@ -2,10 +2,10 @@ widget = { plugin = 'alsa', cb = function(t) if t.mute then - return {full_text='[mute]', color='#e03838'} + return '[mute]' else local percent = (t.vol.cur - t.vol.min) / (t.vol.max - t.vol.min) * 100 - return {full_text=string.format('[%3d%%]', 0.5 + percent), color='#718ba6'} + return string.format('[%3d%%]', math.floor(0.5 + percent)) end end, } diff --git a/contrib/widget-examples/dwm/time-battery.lua b/contrib/widget-examples/dwm/time-battery.lua new file mode 100644 index 0000000..15a092a --- /dev/null +++ b/contrib/widget-examples/dwm/time-battery.lua @@ -0,0 +1,35 @@ +function get_bat_seg() + local f = io.open('/sys/class/power_supply/BAT0/uevent', 'r') + if not f then + return '[----]' + end + bat_uev:seek('set', 0) -- yes, seeking on sysfs files is OK + local status, capa + for line in bat_uev:lines() do + local key, value = line:match('(.-)=(.*)') + if key == 'POWER_SUPPLY_STATUS' then + status = value + elseif key == 'POWER_SUPPLY_CAPACITY' then + capa = tonumber(value) + end + end + f:close() + if status == 'Unknown' or status == 'Full' then + return nil + end + local sym = '?' + if status == 'Discharging' then + sym = '↓' + elseif status == 'Charging' then + sym = '↑' + end + return string.format('[%3d%%%s]', capa, sym) +end + +widget = { + plugin = 'timer', + opts = { period = 2 }, + cb = function(t) + return {os.date('[%H:%M]'), get_bat_seg()} + end, +} diff --git a/contrib/widget-examples/xkb.lua b/contrib/widget-examples/dwm/xkb.lua similarity index 50% rename from contrib/widget-examples/xkb.lua rename to contrib/widget-examples/dwm/xkb.lua index aae6efb..d9a93ae 100644 --- a/contrib/widget-examples/xkb.lua +++ b/contrib/widget-examples/dwm/xkb.lua @@ -2,11 +2,11 @@ widget = { plugin = 'xkb', cb = function(t) if t.name == 'us' then - return {full_text='[En]', color='#9c9c9c'} + return '[En]' elseif t.name == 'ru(winkeys)' then - return {full_text='[Ru]', color='#eab93d'} + return '[Ru]' else - return {full_text='[' .. t.id .. ']'} + return '[??]' end end, } diff --git a/contrib/widget-examples/README.md b/contrib/widget-examples/i3/README.md similarity index 100% rename from contrib/widget-examples/README.md rename to contrib/widget-examples/i3/README.md diff --git a/contrib/widget-examples/i3/alsa.lua b/contrib/widget-examples/i3/alsa.lua new file mode 100644 index 0000000..6445211 --- /dev/null +++ b/contrib/widget-examples/i3/alsa.lua @@ -0,0 +1,11 @@ +widget = { + plugin = 'alsa', + cb = function(t) + if t.mute then + return {full_text = '[mute]', color = '#e03838'} + else + local percent = (t.vol.cur - t.vol.min) / (t.vol.max - t.vol.min) * 100 + return {full_text = string.format('[%3d%%]', math.floor(0.5 + percent)), color = '#718ba6'} + end + end, +} diff --git a/contrib/widget-examples/i3/cpu-usage.lua b/contrib/widget-examples/i3/cpu-usage.lua new file mode 100644 index 0000000..5b7b462 --- /dev/null +++ b/contrib/widget-examples/i3/cpu-usage.lua @@ -0,0 +1,41 @@ +cur, priv = {}, nil + +function wrap0(x) + return x < 0 and 0 or x +end + +widget = { + plugin = 'timer', + opts = {period = 1}, + cb = function() + local f = assert(io.open('/proc/stat', 'r')) + cur.user, cur.nice, cur.system, cur.idle, cur.iowait, cur.irq, cur.softirq, cur.steal, + cur.guest, cur.guest_nice = string.match(f:read('*line'), + 'cpu +(.*) +(.*) +(.*) +(.*) +(.*) +(.*) +(.*) +(.*) +(.*) +(.*)') + f:close() + + cur.user = cur.user - cur.guest + cur.nice = cur.nice - cur.guest_nice + + cur.IdleAll = cur.idle + cur.iowait + cur.SysAll = cur.system + cur.irq + cur.softirq + cur.VirtAll = cur.guest + cur.guest_nice + cur.Total = cur.user + cur.nice + cur.SysAll + cur.IdleAll + cur.steal + cur.VirtAll + + local num, denom = 0, 0 + if prev then + num = wrap0(cur.user - prev.user) + + wrap0(cur.nice - prev.nice) + + wrap0(cur.SysAll - prev.SysAll) + + wrap0(cur.steal - prev.steal) + + wrap0(cur.guest - prev.guest) + denom = wrap0(cur.Total - prev.Total) + end + + prev, cur = cur, {} + + if denom ~= 0 then + return {full_text = string.format('[%5.1f%%]', num / denom * 100)} + end + end, +} diff --git a/contrib/widget-examples/i3/mem-free.lua b/contrib/widget-examples/i3/mem-free.lua new file mode 100644 index 0000000..40cb384 --- /dev/null +++ b/contrib/widget-examples/i3/mem-free.lua @@ -0,0 +1,20 @@ +widget = { + plugin = 'timer', + opts = {period = 2}, + cb = function(t) + local f = assert(io.open('/proc/meminfo', 'r')) + local value, unit + for line in f:lines() do + value, unit = line:match('MemAvailable: +(.*) +(.*)') + if value then + break + end + end + f:close() + if unit == 'kB' then + value = value / 1024 / 1024 + unit = 'GiB' + end + return {full_text = string.format('[%3.2f %s]', value, unit), color = '#af8ec3'} + end, +} diff --git a/contrib/widget-examples/time-battery.lua b/contrib/widget-examples/i3/time-battery.lua similarity index 61% rename from contrib/widget-examples/time-battery.lua rename to contrib/widget-examples/i3/time-battery.lua index 640e83e..f8f4f64 100644 --- a/contrib/widget-examples/time-battery.lua +++ b/contrib/widget-examples/i3/time-battery.lua @@ -1,12 +1,10 @@ -bat_uev = io.open('/sys/class/power_supply/BAT0/uevent', 'r') - function get_bat_seg() - if not bat_uev then - return {full_text='[-?-]'} + local f = io.open('/sys/class/power_supply/BAT0/uevent', 'r') + if not f then + return {full_text = '[----]'} end - bat_uev:seek('set', 0) -- yes, seeking on sysfs files is OK local status, capa - for line in bat_uev:lines() do + for line in f:lines() do local key, value = string.match(line, '(.-)=(.*)') if key == 'POWER_SUPPLY_STATUS' then status = value @@ -14,6 +12,7 @@ function get_bat_seg() capa = tonumber(value) end end + f:close() if status == 'Unknown' or status == 'Full' then return nil end @@ -25,13 +24,13 @@ function get_bat_seg() sym = '↑' color = '#60b48a' end - return {full_text=string.format('[%3d%%%s]', capa, sym), color=color} + return {full_text = string.format('[%3d%%%s]', capa, sym), color = color} end widget = { plugin = 'timer', - opts = { period = 2 }, + opts = {period = 2}, cb = function(t) - return {{full_text=os.date('[%H:%M]'), color='#dc8cc3'}, get_bat_seg()} + return {{full_text = os.date('[%H:%M]'), color = '#dc8cc3'}, get_bat_seg()} end, } diff --git a/contrib/widget-examples/i3/update-on-click.lua b/contrib/widget-examples/i3/update-on-click.lua new file mode 100644 index 0000000..d46aeb0 --- /dev/null +++ b/contrib/widget-examples/i3/update-on-click.lua @@ -0,0 +1,25 @@ +-- A Rube Goldberg machine widget that updates itself on click. + +fifo_path = '~/.luastatus-timer-fifo' +assert(os.execute('set -e; F=' .. fifo_path .. '; rm -f -- $F; mkfifo -m600 -- $F')) + +widget = { + plugin = 'timer', + opts = { + fifo = fifo_path, + period = 2.0, + }, + cb = function(t) + if t == 'fifo' then + return {full_text = 'Thanks!'} + else + return {full_text = 'Click me'} + end + end, + event = function(t) + -- opening the FIFO for writing here may, in some rare cases, block (and doing this from + -- this process would lead to a deadlock), so we spawn another process to do it and do not + -- wait for its termination (which would also lead to a deadlock). + os.execute('exec touch -- ' .. fifo_path .. '&') + end, +} diff --git a/contrib/widget-examples/i3/xkb.lua b/contrib/widget-examples/i3/xkb.lua new file mode 100644 index 0000000..deec0c8 --- /dev/null +++ b/contrib/widget-examples/i3/xkb.lua @@ -0,0 +1,12 @@ +widget = { + plugin = 'xkb', + cb = function(t) + if t.name == 'us' then + return {full_text = '[En]', color = '#9c9c9c'} + elseif t.name == 'ru(winkeys)' then + return {full_text = '[Ru]', color = '#eab93d'} + else + return {full_text = '[' .. (t.name or '? ID ' .. t.id) .. ']'} + end + end, +} diff --git a/contrib/widget-examples/lemonbar/bspwm.lua b/contrib/widget-examples/lemonbar/bspwm.lua new file mode 100644 index 0000000..c2ab219 --- /dev/null +++ b/contrib/widget-examples/lemonbar/bspwm.lua @@ -0,0 +1,11 @@ +lib = require 'lib' + +widget = { + plugin = 'pipe', + opts = { + args = {'bspc', 'control', '--subscribe'}, + }, + cb = function(t) + return lib.pager(t) + end, +} diff --git a/contrib/widget-examples/lemonbar/clock.lua b/contrib/widget-examples/lemonbar/clock.lua new file mode 100644 index 0000000..52e5c13 --- /dev/null +++ b/contrib/widget-examples/lemonbar/clock.lua @@ -0,0 +1,36 @@ +lib = require 'lib' + +days = { + 'Воскресенье', + 'Понедельник', + 'Вторник', + 'Среда', + 'Четверг', + 'Пятница', + 'Суббота', +} + +months = { + 'января', + 'февраля', + 'марта', + 'апреля', + 'мая', + 'июня', + 'июля', + 'августа', + 'сентрября', + 'октября', + 'ноября', + 'декабря', +} + +widget = { + plugin = 'timer', + opts = {period = 5}, + cb = function(t) + local d = os.date('*t') + local text = string.format(' %s, %d %s, %02d:%02d ', days[d.wday], d.day, months[d.month], d.hour, d.min) + return lib.colorize(text, '#eee', '#000') + end +} diff --git a/contrib/widget-examples/lemonbar/lang.lua b/contrib/widget-examples/lemonbar/lang.lua new file mode 100644 index 0000000..377d276 --- /dev/null +++ b/contrib/widget-examples/lemonbar/lang.lua @@ -0,0 +1,12 @@ +lib = require 'lib' + +widget = { + plugin = 'xkb', + cb = function(t) + local text = ({ + ['us'] = ' En ', + ['ru'] = ' Ru ', + })[t.name] or ' ?? ' + return lib.colorize(text, '#fff', '#0a0') + end, +} diff --git a/contrib/widget-examples/lemonbar/lib.lua b/contrib/widget-examples/lemonbar/lib.lua new file mode 100644 index 0000000..d5ad531 --- /dev/null +++ b/contrib/widget-examples/lemonbar/lib.lua @@ -0,0 +1,34 @@ +local lib = {} + +function lib.colorize(text, fg, bg, u) + text = fg and string.format('%%{F%s}%s%%{F-}', fg, text) or text + text = bg and string.format('%%{B%s}%s%%{B-}', bg, text) or text + text = u and string.format('%%{U%s}%s%%{U-}', u, text) or text + return text +end + +local colors = { + M = {fg = '#000000', bg = '#ff5900'}, + m = {fg = '#ff5900', bg = '#000000'}, + O = {fg = '#fdf6e3', bg = '#808080'}, + o = {fg = '#fdf6e3', bg = '#000000'}, + F = {fg = '#002b36', bg = '#808080'}, + f = {fg = '#586e57', bg = '#000000'}, + U = {fg = '#000000', bg = '#dc322f'}, + u = {fg = '#000000', bg = '#dc322f'}, + L = {fg = '#ffffff', bg = '#000000'}, +} + +function lib.pager(status) + local text = '' + for block in status:sub(2):gmatch('[^:]+') do + text = text .. lib.colorize( + string.format(' %s ', block:sub(2)), + colors[block:sub(1, 1)].fg, + colors[block:sub(1, 1)].bg + ) + end + return text +end + +return lib diff --git a/contrib/widget-examples/lemonbar/title.lua b/contrib/widget-examples/lemonbar/title.lua new file mode 100644 index 0000000..9c5df68 --- /dev/null +++ b/contrib/widget-examples/lemonbar/title.lua @@ -0,0 +1,12 @@ +lib = require 'lib' +utf8 = require 'utf8' -- git@github.com:Stepets/utf8.lua.git + +widget = { + plugin = 'xtitle', + cb = function(t) + t = t or '' + t = (utf8.len(t) < 100) and t or (utf8.sub(t, 1, 97) .. '...') + t = luastatus.barlib.escape(t) + return '%{c}' .. colorize(t, '#ffe') .. '%{r}' + end, +} diff --git a/include/barlib.h b/include/barlib.h deleted file mode 100644 index cfd104c..0000000 --- a/include/barlib.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef luastatus_include_barlib_h_ -#define luastatus_include_barlib_h_ - -#include -#include "barlib_data.h" - -const int LUASTATUS_BARLIB_LUA_VERSION_NUM = LUA_VERSION_NUM; - -LuastatusBarlibIface luastatus_barlib_iface; - -#endif diff --git a/include/barlib_data.h b/include/barlib_data.h index 9ba3fe8..3cdbf20 100644 --- a/include/barlib_data.h +++ b/include/barlib_data.h @@ -4,152 +4,128 @@ #include #include -#include "loglevel.h" +#include "common.h" -#ifndef LUASTATUS_BARLIB_LOGF_ATTRIBUTE +#ifndef LUASTATUS_BARLIB_SAYF_ATTRIBUTE # ifdef __GNUC__ -# define LUASTATUS_BARLIB_LOGF_ATTRIBUTE __attribute__((format(printf, 3, 4))) +# define LUASTATUS_BARLIB_SAYF_ATTRIBUTE __attribute__((format(printf, 3, 4))) # else -# define LUASTATUS_BARLIB_LOGF_ATTRIBUTE /*nothing*/ +# define LUASTATUS_BARLIB_SAYF_ATTRIBUTE /*nothing*/ # endif #endif -typedef LUASTATUS_BARLIB_LOGF_ATTRIBUTE void LuastatusBarlibLogf(void *userdata, - LuastatusLogLevel level, - const char *fmt, ...); +typedef LUASTATUS_BARLIB_SAYF_ATTRIBUTE void LuastatusBarlibSayf_v1( + void *userdata, int level, const char *fmt, ...); typedef struct { + // This barlib's private data. void *priv; - void *userdata; - LuastatusBarlibLogf *logf; -} LuastatusBarlibData; - -typedef lua_State *LuastatusBarlibEWCallBegin(void *userdata, size_t widget_idx); -typedef void LuastatusBarlibEWCallEnd(void *userdata, size_t widget_idx); -typedef enum { - LUASTATUS_BARLIB_INIT_RESULT_OK, - LUASTATUS_BARLIB_INIT_RESULT_ERR, -} LuastatusBarlibInitResult; + // A black-box user data. + void *userdata; -typedef enum { - LUASTATUS_BARLIB_SET_RESULT_OK, - LUASTATUS_BARLIB_SET_RESULT_NONFATAL_ERR, - LUASTATUS_BARLIB_SET_RESULT_FATAL_ERR, -} LuastatusBarlibSetResult; + // Should be used for logging. + LuastatusBarlibSayf_v1 *sayf; -typedef enum { - LUASTATUS_BARLIB_SET_ERROR_RESULT_OK, - LUASTATUS_BARLIB_SET_ERROR_RESULT_FATAL_ERR, -} LuastatusBarlibSetErrorResult; + // See DOCS/design/map_get.md. + void ** (*map_get)(void *userdata, const char *key); +} LuastatusBarlibData_v1; -typedef enum { - LUASTATUS_BARLIB_EW_RESULT_NO_MORE_EVENTS, - LUASTATUS_BARLIB_EW_RESULT_FATAL_ERR, -} LuastatusBarlibEWResult; +typedef struct { + lua_State *(*call_begin) (void *userdata, size_t widget_idx); + void (*call_end) (void *userdata, size_t widget_idx); + void (*call_cancel)(void *userdata, size_t widget_idx); +} LuastatusBarlibEWFuncs_v1; typedef struct { - // This function must initialize a barlib by assigning something to bd->priv. - // + // This function should initialize a barlib by assigning something to bd->priv. // You would typically do: // typedef struct { // ... // } Priv; // ... // static - // LuastatusBarlibInitResult + // int // init(LuastatusBarlibData *bd, const char *const *opts, size_t nwidgets) // { - // Priv *p = bd->priv = LS_NEW(Priv, 1); + // Priv *p = bd->priv = LS_XNEW(Priv, 1); // ... // - // opts are options passed to luastatus with -B switches, last element is NULL. + // opts are options passed to luastatus with -B switches, with a sentinel NULL entry. // // nwidgets is the number of widgets. It stays unchanged during the entire life cycle of a // barlib. // - // It must return: + // It should return: // - // LUASTATUS_BARLIB_INIT_RESULT_ERR on failure; + // LUASTATUS_ERR on failure; // - // LUASTATUS_BARLIB_INIT_RESULT_OK on success. + // LUASTATUS_OK on success. // - LuastatusBarlibInitResult (*init)(LuastatusBarlibData *bd, const char *const *opts, - size_t nwidgets); + int (*init)(LuastatusBarlibData_v1 *bd, const char *const *opts, size_t nwidgets); - // This function must assign Lua functions that the barlib provides to the table on the top of - // L's stack. + // This function should register Lua functions provided by the barlib into the table on the top + // of L's stack. // // It is guaranteed that L's stack has at least 15 free slots. // // May be NULL. // - void (*register_funcs)(LuastatusBarlibData *bd, lua_State *L); + void (*register_funcs)(LuastatusBarlibData_v1 *bd, lua_State *L); - // This function must update the widget with index widget_idx. The data is on the top of L's - // stack. The format of the data is defined by the barlib itself. + // This function should update the widget with index widget_idx. The data is located on the top + // of L's stack; its format defined by the barlib itself. // - // This function is allowed to push elements onto L's stack to iterate over tables, but is not - // allowed to modify stack elements, read elements below the initial top, or interact with L in - // any other way. + // This function may push elements onto L's stack (to iterate over tables), but should not + // modify elements below the initial top, or interact with L in any other way. // // It is guaranteed that L's stack has at least 15 free slots. // // It must return: // - // LUASTATUS_BARLIB_SET_RESULT_OK on success. + // LUASTATUS_OK on success. // In this case, L's stack must not contain any extra elements pushed onto it; // - // LUASTATUS_BARLIB_SET_RESULT_NONFATAL_ERR on a non-fatal error, e.g., if the format of the - // data on top of L's stack is invalid. In this case, L's stack may contain extra elements - // pushed onto it; + // LUASTATUS_NONFATAL_ERR on a non-fatal error, e.g., if the format of the data on top of L's + // stack is invalid. In this case, L's stack may contain extra elements pushed onto it; // - // LUASTATUS_BARLIB_SET_ERROR_FATAL_ERR on a fatal error, e.g. if the connection to the - // display has been lost. In this case, L's stack may contain extra elements pushed onto it. + // LUASTATUS_ERR on a fatal error, e.g. if the connection to the display has been lost. In this + // case, L's stack may contain extra elements pushed onto it. // - LuastatusBarlibSetResult (*set)(LuastatusBarlibData *bd, lua_State *L, size_t widget_idx); + int (*set)(LuastatusBarlibData_v1 *bd, lua_State *L, size_t widget_idx); - // This function must update the widget with index widget_idx and set its - // content to something that indicates an error. + // This function should update the widget with index widget_idx and set its content to something + // that indicates an error. // // It must return: // - // LUASTATUS_BARLIB_SET_ERROR_RESULT_OK on success; + // LUASTATUS_OK on success; // - // LUASTATUS_BARLIB_SET_ERROR_FATAL_ERR on a fatal error, e.g. if the connection to the - // display has been lost. + // LUASTATUS_ERR on a fatal error, e.g. if the connection to the display has been lost. // - LuastatusBarlibSetErrorResult (*set_error)(LuastatusBarlibData *bd, size_t widget_idx); + int (*set_error)(LuastatusBarlibData_v1 *bd, size_t widget_idx); - // This function must launch barlib's event watcher. Once the barlib wants to report an event - // occurred on widget with index widget_idx, it must call call_begin(widget_idx, bd->userdata), - // thus obtaining a lua_State* object (it is guaranteed that its stack has at least 15 free - // slots). - // Then it must push exactly one value onto its stack and call call_end(widget_idx, - // bd->userdata). - // It must not call call_begin() and return without calling call_end(). + // This function should run barlib's event watcher. Once the barlib wants to report an event + // occurred on widget with index i, it should call call_begin(i, bd->userdata), thus obtaining a + // lua_State* object (let's call it L). Then it must push exactly one value onto L's stack and + // call call_end(i, bd->userdata). + // + // It is guaranteed that L's stack has at least 15 free slots. + // + // It is explicitly allowed to call call_begin() and return without calling call_end(), leaving + // arbitrary number of extra elements on L's stack. // // It must return: - // LUASTATUS_BARLIB_EW_RESULT_NO_MORE_EVENTS if it is clear that no events will be reported - // anymore; + // LUASTATUS_NONFATAL_ERR if it is clear that no events will be reported anymore; // - // LUASTATUS_BARLIB_EW_RESULT_FATAL_ERR on a fatal error, e.g. if the connection to the - // display has been lost. + // LUASTATUS_ERR on a fatal error, e.g. if the connection to the display has been lost. // // May be NULL (and should be NULL if events are not supported). // - LuastatusBarlibEWResult (*event_watcher)(LuastatusBarlibData *bd, - LuastatusBarlibEWCallBegin call_begin, - LuastatusBarlibEWCallEnd call_end); + int (*event_watcher)(LuastatusBarlibData_v1 *bd, LuastatusBarlibEWFuncs_v1 funcs); // This function must destroy a previously successfully initialized barlib. - void (*destroy)(LuastatusBarlibData *bd); - - // See ../WRITING_BARLIB_OR_PLUGIN.md. - // - // May be NULL. - // - const char *const *taints; -} LuastatusBarlibIface; + void (*destroy)(LuastatusBarlibData_v1 *bd); +} LuastatusBarlibIface_v1; #endif diff --git a/include/barlib_data_v1.h b/include/barlib_data_v1.h new file mode 100644 index 0000000..78b4d25 --- /dev/null +++ b/include/barlib_data_v1.h @@ -0,0 +1,11 @@ +#ifndef luastatus_include_barlib_data_v1_h_ +#define luastatus_include_barlib_data_v1_h_ + +#include "barlib_data.h" + +#define LuastatusBarlibIface LuastatusBarlibIface_v1 +#define LuastatusBarlibSayf LuastatusBarlibSayf_v1 +#define LuastatusBarlibData LuastatusBarlibData_v1 +#define LuastatusBarlibEWFuncs LuastatusBarlibEWFuncs_v1 + +#endif diff --git a/include/barlib_logf_macros.h b/include/barlib_logf_macros.h deleted file mode 100644 index 4c4f4e2..0000000 --- a/include/barlib_logf_macros.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef luastatus_include_barlib_logf_macros_ -#define luastatus_include_barlib_logf_macros_ - -#define LUASTATUS_FATALF(Data_, ...) Data_->logf(Data_->userdata, LUASTATUS_FATAL, __VA_ARGS__) -#define LUASTATUS_ERRF(Data_, ...) Data_->logf(Data_->userdata, LUASTATUS_ERR, __VA_ARGS__) -#define LUASTATUS_WARNF(Data_, ...) Data_->logf(Data_->userdata, LUASTATUS_WARN, __VA_ARGS__) -#define LUASTATUS_INFOF(Data_, ...) Data_->logf(Data_->userdata, LUASTATUS_INFO, __VA_ARGS__) -#define LUASTATUS_VERBOSEF(Data_, ...) Data_->logf(Data_->userdata, LUASTATUS_VERBOSE, __VA_ARGS__) -#define LUASTATUS_DEBUGF(Data_, ...) Data_->logf(Data_->userdata, LUASTATUS_DEBUG, __VA_ARGS__) -#define LUASTATUS_TRACEF(Data_, ...) Data_->logf(Data_->userdata, LUASTATUS_TRACE, __VA_ARGS__) - -#endif diff --git a/include/barlib_v1.h b/include/barlib_v1.h new file mode 100644 index 0000000..ab3faf2 --- /dev/null +++ b/include/barlib_v1.h @@ -0,0 +1,13 @@ +#ifndef luastatus_include_barlib_v1_h_ +#define luastatus_include_barlib_v1_h_ + +#include + +#include "barlib_data_v1.h" +#include "common.h" + +const int LUASTATUS_BARLIB_LUA_VERSION_NUM = LUA_VERSION_NUM; + +extern LuastatusBarlibIface_v1 luastatus_barlib_iface_v1; + +#endif diff --git a/include/common.h b/include/common.h new file mode 100644 index 0000000..d62f5e4 --- /dev/null +++ b/include/common.h @@ -0,0 +1,22 @@ +#ifndef luastatus_include_common_h_ +#define luastatus_include_common_h_ + +enum { + LUASTATUS_OK, + LUASTATUS_ERR, + LUASTATUS_NONFATAL_ERR, +}; + +enum { + LUASTATUS_LOG_FATAL, + LUASTATUS_LOG_ERR, + LUASTATUS_LOG_WARN, + LUASTATUS_LOG_INFO, + LUASTATUS_LOG_VERBOSE, + LUASTATUS_LOG_DEBUG, + LUASTATUS_LOG_TRACE, + + LUASTATUS_LOG_LAST, +}; + +#endif diff --git a/include/loglevel.h b/include/loglevel.h deleted file mode 100644 index 54f1506..0000000 --- a/include/loglevel.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef luastatus_include_loglevel_h_ -#define luastatus_include_loglevel_h_ - -typedef enum { - LUASTATUS_FATAL, - LUASTATUS_ERR, - LUASTATUS_WARN, - LUASTATUS_INFO, - LUASTATUS_VERBOSE, - LUASTATUS_DEBUG, - LUASTATUS_TRACE, - - LUASTATUS_LOGLEVEL_LAST, -} LuastatusLogLevel; - -#endif diff --git a/include/plugin.h b/include/plugin.h deleted file mode 100644 index f3a4a34..0000000 --- a/include/plugin.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef luastatus_include_plugin_h_ -#define luastatus_include_plugin_h_ - -#include -#include "plugin_data.h" - -const int LUASTATUS_PLUGIN_LUA_VERSION_NUM = LUA_VERSION_NUM; - -LuastatusPluginIface luastatus_plugin_iface; - -#endif diff --git a/include/plugin_data.h b/include/plugin_data.h index baf7c47..4ab06fa 100644 --- a/include/plugin_data.h +++ b/include/plugin_data.h @@ -4,36 +4,41 @@ #include #include -#include "loglevel.h" +#include "common.h" -#ifndef LUASTATUS_PLUGIN_LOGF_ATTRIBUTE +#ifndef LUASTATUS_PLUGIN_SAYF_ATTRIBUTE # ifdef __GNUC__ -# define LUASTATUS_PLUGIN_LOGF_ATTRIBUTE __attribute__((format(printf, 3, 4))) +# define LUASTATUS_PLUGIN_SAYF_ATTRIBUTE __attribute__((format(printf, 3, 4))) # else -# define LUASTATUS_PLUGIN_LOGF_ATTRIBUTE /*nothing*/ +# define LUASTATUS_PLUGIN_SAYF_ATTRIBUTE /*nothing*/ # endif #endif -typedef LUASTATUS_PLUGIN_LOGF_ATTRIBUTE void LuastatusPluginLogf(void *userdata, - LuastatusLogLevel level, - const char *fmt, ...); +typedef LUASTATUS_PLUGIN_SAYF_ATTRIBUTE void LuastatusPluginSayf_v1( + void *userdata, int level, const char *fmt, ...); typedef struct { + // This widget's private data. void *priv; + + // A black-box user data. void *userdata; - LuastatusPluginLogf *logf; -} LuastatusPluginData; -typedef lua_State *LuastatusPluginCallBegin(void *userdata); -typedef void LuastatusPluginCallEnd(void *userdata); + // Should be used for logging. + LuastatusPluginSayf_v1 *sayf; + + // See DOCS/design/map_get.md. + void ** (*map_get)(void *userdata, const char *key); +} LuastatusPluginData_v1; -typedef enum { - LUASTATUS_PLUGIN_INIT_RESULT_OK, - LUASTATUS_PLUGIN_INIT_RESULT_ERR, -} LuastatusPluginInitResult; +typedef struct { + lua_State *(*call_begin) (void *userdata); + void (*call_end) (void *userdata); + void (*call_cancel)(void *userdata); +} LuastatusPluginRunFuncs_v1; typedef struct { - // This function must initialize a widget by assigning something to pd->priv. + // This function should initialize a widget by assigning something to pd->priv. // You would typically do: // // typedef struct { @@ -41,56 +46,51 @@ typedef struct { // } Priv; // ... // static - // LuastatusPluginInitResult + // int // init(LuastatusPluginData *pd, lua_State *L) { - // Priv *p = pd->priv = LS_NEW(Priv, 1); + // Priv *p = pd->priv = LS_XNEW(Priv, 1); // ... // // The options table is on the top of L's stack. // - // This function is allowed to push elements onto L's stack to iterate over tables, but is not - // allowed to modify stack elements, read elements below the initial top, or interact with L in - // any other way. + // This function may push elements onto L's stack to iterate over tables, but should not modify + // elements below the initial top, or interact with L in any other way. // // It is guaranteed that L's stack has at least 15 free slots. // - // It must return: + // It should return: // - // LUASTATUS_PLUGIN_INIT_RESULT_OK on success. - // In this case, L's stack must not contain any extra elements pushed onto it; + // LUASTATUS_RES_OK on success. + // In this case, L's stack should not contain any extra elements pushed onto it; // - // LUASTATUS_PLUGIN_INIT_RESULT_ERR on failure. + // LUASTATUS_RES_ERR on failure. // In this case, L's stack may contain extra elements pushed onto it. // - LuastatusPluginInitResult (*init)(LuastatusPluginData *pd, lua_State *L); + int (*init)(LuastatusPluginData_v1 *pd, lua_State *L); - // This function must assign Lua functions that the plugin provides to the table on the top of - // L's stack. + // This function should register Lua functions provided by the plugin into table on top of L's + // stack. // // It is guaranteed that L's stack has at least 15 free slots. // // May be NULL. - void (*register_funcs)(LuastatusPluginData *pd, lua_State *L); + void (*register_funcs)(LuastatusPluginData_v1 *pd, lua_State *L); - // This function must launch a widget. Once the plugin wants to update the widget, it must - // call call_begin(pd->userdata), thus obtaining a lua_State* object (it is guaranteed that its - // stack has at least 15 free slots). - // Then it must push exactly one value onto its stack and call call_end(pd->userdata). + // This function should run a widget. Once the plugin wants to update the widget, it should call + // call_begin(pd->userdata), thus obtaining a lua_State* object (let's call it L). Then it + // should push exactly one value onto L's stack and call call_end(pd->userdata). + // + // It is guaranteed that L's stack has at least 15 free slots. // - // It must not call_begin() and return without calling call_end(). + // It should only return on an unrecoverable failure. // - // It must only return on an unrecoverable failure. + // It is explicitly allowed to call call_begin() and return without calling call_end(), leaving + // arbitrary number of extra elements on L's stack. // - void (*run)(LuastatusPluginData *pd, LuastatusPluginCallBegin call_begin, - LuastatusPluginCallEnd call_end); - - // This function must destroy a previously successfully initialized widget. - void (*destroy)(LuastatusPluginData *pd); + void (*run)(LuastatusPluginData_v1 *pd, LuastatusPluginRunFuncs_v1 funcs); - // See ../WRITING_BARLIB_OR_PLUGIN.md. - // - // May be NULL. - const char *const *taints; -} LuastatusPluginIface; + // This function should destroy a previously successfully initialized widget. + void (*destroy)(LuastatusPluginData_v1 *pd); +} LuastatusPluginIface_v1; #endif diff --git a/include/plugin_data_v1.h b/include/plugin_data_v1.h new file mode 100644 index 0000000..b238d65 --- /dev/null +++ b/include/plugin_data_v1.h @@ -0,0 +1,11 @@ +#ifndef luastatus_include_plugin_data_v1_h_ +#define luastatus_include_plugin_data_v1_h_ + +#include "plugin_data.h" + +#define LuastatusPluginIface LuastatusPluginIface_v1 +#define LuastatusPluginSayf LuastatusPluginSayf_v1 +#define LuastatusPluginData LuastatusPluginData_v1 +#define LuastatusPluginRunFuncs LuastatusPluginRunFuncs_v1 + +#endif diff --git a/include/plugin_logf_macros.h b/include/plugin_logf_macros.h deleted file mode 100644 index 558bea2..0000000 --- a/include/plugin_logf_macros.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef luastatus_include_plugin_logf_macros_h_ -#define luastatus_include_plugin_logf_macros_h_ - -#define LUASTATUS_FATALF(Data_, ...) Data_->logf(Data_->userdata, LUASTATUS_FATAL, __VA_ARGS__) -#define LUASTATUS_ERRF(Data_, ...) Data_->logf(Data_->userdata, LUASTATUS_ERR, __VA_ARGS__) -#define LUASTATUS_WARNF(Data_, ...) Data_->logf(Data_->userdata, LUASTATUS_WARN, __VA_ARGS__) -#define LUASTATUS_INFOF(Data_, ...) Data_->logf(Data_->userdata, LUASTATUS_INFO, __VA_ARGS__) -#define LUASTATUS_VERBOSEF(Data_, ...) Data_->logf(Data_->userdata, LUASTATUS_VERBOSE, __VA_ARGS__) -#define LUASTATUS_DEBUGF(Data_, ...) Data_->logf(Data_->userdata, LUASTATUS_DEBUG, __VA_ARGS__) -#define LUASTATUS_TRACEF(Data_, ...) Data_->logf(Data_->userdata, LUASTATUS_TRACE, __VA_ARGS__) - -#endif diff --git a/include/plugin_utils.h b/include/plugin_utils.h index ccfa1d6..9d4faea 100644 --- a/include/plugin_utils.h +++ b/include/plugin_utils.h @@ -5,29 +5,31 @@ #include #include #include + #include "libls/lua_utils.h" -#include "include/loglevel.h" -#ifndef PU_L_NAME -#define PU_L_NAME L +#include "common.h" + +#ifndef PU_L +# define PU_L L #endif -#ifndef PU_PD_NAME -#define PU_PD_NAME pd +#ifndef PU_PD +# define PU_PD pd #endif #ifndef PU_ON_ERROR -#define PU_ON_ERROR goto error; +# define PU_ON_ERROR goto error; #endif //------------------------------------------------------------------------------ #define PU_CHECK_TYPE_AT(StackIndex_, StringRepr_, LuaType_) \ do { \ - if (lua_type(PU_L_NAME, StackIndex_) != LuaType_) { \ - PU_PD_NAME->logf(PU_PD_NAME->userdata, LUASTATUS_FATAL, "%s: expected %s, found %s", \ - StringRepr_, lua_typename(PU_L_NAME, LuaType_), \ - luaL_typename(PU_L_NAME, StackIndex_)); \ + if (lua_type(PU_L, StackIndex_) != LuaType_) { \ + PU_PD->sayf(PU_PD->userdata, LUASTATUS_LOG_FATAL, "%s: expected %s, found %s", \ + StringRepr_, lua_typename(PU_L, LuaType_), \ + luaL_typename(PU_L, StackIndex_)); \ PU_ON_ERROR \ } \ } while (0) @@ -37,7 +39,7 @@ #define PU_VISIT_STR_AT(StackIndex_, StringRepr_, Var_, ...) \ do { \ PU_CHECK_TYPE_AT(StackIndex_, StringRepr_, LUA_TSTRING); \ - const char *Var_ = lua_tostring(PU_L_NAME, StackIndex_); \ + const char *Var_ = lua_tostring(PU_L, StackIndex_); \ __VA_ARGS__ \ } while (0) @@ -45,35 +47,35 @@ do { \ PU_CHECK_TYPE_AT(StackIndex_, StringRepr_, LUA_TSTRING); \ size_t NVar_; \ - const char *SVar_ = lua_tolstring(PU_L_NAME, StackIndex_, &NVar_); \ + const char *SVar_ = lua_tolstring(PU_L, StackIndex_, &NVar_); \ __VA_ARGS__ \ } while (0) #define PU_VISIT_NUM_AT(StackIndex_, StringRepr_, Var_, ...) \ do { \ PU_CHECK_TYPE_AT(StackIndex_, StringRepr_, LUA_TNUMBER); \ - lua_Number Var_ = lua_tonumber(PU_L_NAME, StackIndex_); \ + lua_Number Var_ = lua_tonumber(PU_L, StackIndex_); \ __VA_ARGS__ \ } while (0) #define PU_VISIT_BOOL_AT(StackIndex_, StringRepr_, Var_, ...) \ do { \ PU_CHECK_TYPE_AT(StackIndex_, StringRepr_, LUA_TBOOLEAN); \ - bool Var_ = lua_toboolean(PU_L_NAME, StackIndex_); \ + bool Var_ = lua_toboolean(PU_L, StackIndex_); \ __VA_ARGS__ \ } while (0) -#define PU_VISIT_TABLE_AT(StackIndex_, StringRepr_, IndexVar_, ...) \ +// May be useful when you want not to just traverse a table. Dunno. +#define PU_VISIT_TABLE_AT(StackIndex_, StringRepr_, ...) \ do { \ PU_CHECK_TYPE_AT(StackIndex_, StringRepr_, LUA_TTABLE); \ - int IndexVar_ = (StackIndex_); \ __VA_ARGS__ \ } while (0) #define PU_TRAVERSE_TABLE_AT(StackIndex_, StringRepr_, ...) \ do { \ PU_CHECK_TYPE_AT(StackIndex_, StringRepr_, LUA_TTABLE); \ - LS_LUA_TRAVERSE(PU_L_NAME, StackIndex_) { \ + LS_LUA_TRAVERSE(PU_L, StackIndex_) { \ __VA_ARGS__ \ } \ } while (0) @@ -83,19 +85,19 @@ // do not use directly. #define PU__EXPAND_AT_KEY(Key_, AtMacro_, ...) \ do { \ - ls_lua_rawgetf(PU_L_NAME, Key_); \ + ls_lua_rawgetf(PU_L, Key_); \ AtMacro_(-1, Key_, __VA_ARGS__); \ - lua_pop(PU_L_NAME, 1); \ + lua_pop(PU_L, 1); \ } while (0) // do not use directly. #define PU__MAYBE_EXPAND_AT_KEY(Key_, AtMacro_, ...) \ do { \ - ls_lua_rawgetf(PU_L_NAME, Key_); \ - if (!lua_isnil(PU_L_NAME, -1)) { \ + ls_lua_rawgetf(PU_L, Key_); \ + if (!lua_isnil(PU_L, -1)) { \ AtMacro_(-1, Key_, __VA_ARGS__); \ } \ - lua_pop(PU_L_NAME, 1); \ + lua_pop(PU_L, 1); \ } while (0) //------------------------------------------------------------------------------ @@ -132,6 +134,9 @@ #define PU_VISIT_BOOL(Key_, Var_, ...) \ PU__EXPAND_AT_KEY(Key_, PU_VISIT_BOOL_AT, Var_, __VA_ARGS__) +#define PU_TRAVERSE_TABLE(Key_, ...) \ + PU__EXPAND_AT_KEY(Key_, PU_TRAVERSE_TABLE_AT, __VA_ARGS__) + #define PU_VISIT_TABLE(Key_, IndexVar_, ...) \ PU__EXPAND_AT_KEY(Key_, PU_VISIT_TABLE_AT, IndexVar_, __VA_ARGS__) diff --git a/include/plugin_v1.h b/include/plugin_v1.h new file mode 100644 index 0000000..3d5ed3e --- /dev/null +++ b/include/plugin_v1.h @@ -0,0 +1,13 @@ +#ifndef luastatus_include_plugin_v1_h_ +#define luastatus_include_plugin_v1_h_ + +#include + +#include "plugin_data_v1.h" +#include "common.h" + +const int LUASTATUS_PLUGIN_LUA_VERSION_NUM = LUA_VERSION_NUM; + +extern LuastatusPluginIface_v1 luastatus_plugin_iface_v1; + +#endif diff --git a/include/sayf_macros.h b/include/sayf_macros.h new file mode 100644 index 0000000..5d9b392 --- /dev/null +++ b/include/sayf_macros.h @@ -0,0 +1,12 @@ +#ifndef luastatus_include_sayf_macros_ +#define luastatus_include_sayf_macros_ + +#define LS_FATALF(Data_, ...) Data_->sayf(Data_->userdata, LUASTATUS_LOG_FATAL, __VA_ARGS__) +#define LS_ERRF(Data_, ...) Data_->sayf(Data_->userdata, LUASTATUS_LOG_ERR, __VA_ARGS__) +#define LS_WARNF(Data_, ...) Data_->sayf(Data_->userdata, LUASTATUS_LOG_WARN, __VA_ARGS__) +#define LS_INFOF(Data_, ...) Data_->sayf(Data_->userdata, LUASTATUS_LOG_INFO, __VA_ARGS__) +#define LS_VERBOSEF(Data_, ...) Data_->sayf(Data_->userdata, LUASTATUS_LOG_VERBOSE, __VA_ARGS__) +#define LS_DEBUGF(Data_, ...) Data_->sayf(Data_->userdata, LUASTATUS_LOG_DEBUG, __VA_ARGS__) +#define LS_TRACEF(Data_, ...) Data_->sayf(Data_->userdata, LUASTATUS_LOG_TRACE, __VA_ARGS__) + +#endif diff --git a/libls/CMakeLists.txt b/libls/CMakeLists.txt index 2414d0b..dfe91e0 100644 --- a/libls/CMakeLists.txt +++ b/libls/CMakeLists.txt @@ -7,10 +7,10 @@ check_symbol_exists (pipe2 "fcntl.h;unistd.h" LS_HAVE_GNU_PIPE2) check_symbol_exists (SOCK_CLOEXEC "sys/socket.h" LS_HAVE_GNU_SOCK_CLOEXEC) set (CMAKE_REQUIRED_DEFINITIONS "-D_POSIX_C_SOURCE=200809L") check_symbol_exists (posix_close "unistd.h" LS_HAVE_POSIX_CLOSE) -configure_file ("probes.h.in" "${PROJECT_BINARY_DIR}/probes.h") +configure_file ("probes.in.h" "probes.generated.h") target_compile_definitions (ls PUBLIC -D_POSIX_C_SOURCE=200809L) luastatus_target_compile_with (ls LUA) -target_include_directories (ls PUBLIC "${PROJECT_SOURCE_DIR}" "${PROJECT_BINARY_DIR}") +target_include_directories (ls PUBLIC "${PROJECT_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}") set_target_properties (ls PROPERTIES POSITION_INDEPENDENT_CODE ON) diff --git a/libls/algo.h b/libls/algo.h index 0d5e733..aff1e08 100644 --- a/libls/algo.h +++ b/libls/algo.h @@ -1,10 +1,10 @@ #ifndef ls_algo_h_ #define ls_algo_h_ -// minimum of {A_, B_} +// Minimum of /A_/ and /B_/. #define LS_MIN(A_, B_) ((A_) < (B_) ? (A_) : (B_)) -// maximum of {A_, B_} +// Maximum of /A_/ and /B_/. #define LS_MAX(A_, B_) ((A_) > (B_) ? (A_) : (B_)) #endif diff --git a/libls/alloc_utils.h b/libls/alloc_utils.h index cb0e797..a00b95e 100644 --- a/libls/alloc_utils.h +++ b/libls/alloc_utils.h @@ -8,13 +8,15 @@ #include "compdep.h" -// The behaviour is same as casting the result of ls_xmalloc(NElems_, sizeof(Type_)) to pointer to -// Type_. -#define LS_XNEW(Type_, NElems_) ((Type_*) ls_xmalloc(NElems_, sizeof(Type_))) +// The behaviour is same as casting the result of +// ls_xmalloc(NElems_, sizeof(Type_)) +// to a pointer to @Type_. +#define LS_XNEW(Type_, NElems_) ((Type_ *) ls_xmalloc(NElems_, sizeof(Type_))) -// The behaviour is same as casting the result of ls_xcalloc(NElems_, sizeof(Type_)) to pointer to -// Type_. -#define LS_XNEW0(Type_, NElems_) ((Type_*) ls_xcalloc(NElems_, sizeof(Type_))) +// The behaviour is same as casting the result of +// ls_xcalloc(NElems_, sizeof(Type_)) +// to a pointer to @Type_. +#define LS_XNEW0(Type_, NElems_) ((Type_ *) ls_xcalloc(NElems_, sizeof(Type_))) // Out-of-memory handler, called when allocation fails. // @@ -23,10 +25,10 @@ LS_ATTR_NORETURN void ls_oom(void); -// The behaviour is same as calling malloc(nelems * elemsz), except when: -// 1. multiplication overflows, or; -// 2. allocation fails. -// In these cases, this function panics. +// The behaviour is same as calling +// malloc(nelems * elemsz), +// except when the multiplication overflows, or the allocation fails. In these cases, this function +// panics. LS_INHEADER LS_ATTR_MALLOC LS_ATTR_ALLOC_SIZE2(1, 2) void * ls_xmalloc(size_t nelems, size_t elemsz) @@ -52,8 +54,9 @@ ls_xmalloc(size_t nelems, size_t elemsz) ls_oom(); } -// The behaviour is same as calling calloc(nelems, elemsz), except when allocation fails. In that -// case, this function panics. +// The behaviour is same as calling +// calloc(nelems, elemsz), +// except when the allocation fails. In that case, this function panics. LS_INHEADER LS_ATTR_MALLOC LS_ATTR_ALLOC_SIZE2(1, 2) void * ls_xcalloc(size_t nelems, size_t elemsz) @@ -65,10 +68,10 @@ ls_xcalloc(size_t nelems, size_t elemsz) return r; } -// The behaviour is same as calling realloc(p, nelems * elemsz), except when: -// 1. multiplication overflows, or; -// 2. reallocation fails. -// In these cases, this function panics. +// The behaviour is same as calling +// realloc(p, nelems * elemsz), +// except when the multiplication overflows, or the reallocation fails. In these cases, this +// function panics. LS_INHEADER LS_ATTR_ALLOC_SIZE2(2, 3) void * ls_xrealloc(void *p, size_t nelems, size_t elemsz) @@ -96,10 +99,10 @@ ls_xrealloc(void *p, size_t nelems, size_t elemsz) // The behaviour is same as calling // realloc(p, (*pnelems = F(*pnelems)) * elemsz), -// where F(n) = max(1, 2*n), except when: -// 1. multiplication overflows, or; -// 2. reallocation fails. -// In these cases, this function panics. +// where +// F(n) = max(1, 2*n), +// except when the multiplication overflows, or the reallocation fails. In these cases, this +// function panics. LS_INHEADER void * ls_x2realloc(void *p, size_t *pnelems, size_t elemsz) @@ -140,8 +143,24 @@ ls_x2realloc(void *p, size_t *pnelems, size_t elemsz) ls_oom(); } -// The behaviour is same as calling strdup(s), except when allocation fails. In that case, this -// function panics. +// Duplicates (as if with malloc) @n bytes of memory at address @p. Panics on failure. +LS_INHEADER LS_ATTR_MALLOC +void * +ls_xmemdup(const void *p, size_t n) +{ + void *r = malloc(n); + if (n) { + if (!r) { + ls_oom(); + } + memcpy(r, p, n); + } + return r; +} + +// The behaviour is same as calling +// strdup(s), +// except when the allocation fails. In that case, this function panics. LS_INHEADER LS_ATTR_MALLOC char * ls_xstrdup(const char *s) diff --git a/libls/compdep.h b/libls/compdep.h index 84fd38a..cf78f99 100644 --- a/libls/compdep.h +++ b/libls/compdep.h @@ -13,6 +13,7 @@ #define LS_ATTR_MALLOC /*nothing*/ #define LS_ATTR_ALLOC_SIZE1(ArgN_) /*nothing*/ #define LS_ATTR_ALLOC_SIZE2(Arg1N_, Arg2N_) /*nothing*/ +#define LS_ATTR_SENTINEL(Arg_) /*nothing*/ #define LS_HAS_BUILTIN_UNREACHABLE (0) #define LS_HAS_BUILTIN_OVERFLOW(BuiltIn_) (0) @@ -37,6 +38,9 @@ # undef LS_ATTR_ALLOC_SIZE2 # define LS_ATTR_ALLOC_SIZE2(Arg1N_, Arg2N_) __attribute__((alloc_size(Arg1N_, Arg2N_))) + +# undef LS_ATTR_SENTINEL +# define LS_ATTR_SENTINEL(Arg_) __attribute__((sentinel(Arg_))) #endif #if __GNUC__ >= 3 diff --git a/libls/cstring_utils.h b/libls/cstring_utils.h index b3d0398..5b9bc0e 100644 --- a/libls/cstring_utils.h +++ b/libls/cstring_utils.h @@ -3,13 +3,13 @@ #include #include + #include "compdep.h" #include "algo.h" -// Copies up to nbuf - 1 characters from the string src to dst, zero-terminating the result if nbuf -// is not 0. -// -// Returns strlen(src). +// If /nbuf/ is not /0/, copies /min(strlen(src), nbuf - 1)/ characters from /src/ to /buf/, +// zero-terminating the result. +// Returns /strlen(src)/. LS_INHEADER size_t ls_strlcpy(char *buf, const char *src, size_t nbuf) @@ -17,31 +17,16 @@ ls_strlcpy(char *buf, const char *src, size_t nbuf) const size_t nsrc = strlen(src); if (nbuf) { const size_t ncopy = LS_MIN(nsrc, nbuf - 1); + // both /buf/ and /src/ are safe to dereference here, so it's safe to call memcpy without + // additional checks (see /DOCS/c_notes/empty-ranges-and-c-stdlib.md). memcpy(buf, src, ncopy); buf[ncopy] = '\0'; } return nsrc; } -// Appends string src to the end of buf. It will append at most nbuf - strlen(buf) - 1 characters. -// It will then zero-terminate, unless nbuf is 0 or the original buf string was longer than nbuf -// (in practice this should not happen as it means that either nbuf is incorrect or that buf is not -// a proper string). -// -// If the src and buf strings overlap, the behavior is undefined. -// -// Returns strnlen(buf, nbuf). -LS_INHEADER -size_t -ls_strlcat(char *buf, const char *src, size_t nbuf) -{ - const size_t nbufstr = strnlen(buf, nbuf); - return nbufstr + ls_strlcpy(buf + nbufstr, src, nbuf - nbufstr); -} - -// If zero-terminated string a starts with zero-terminated string b, returns a + strlen(b). -// -// Otherwise, returns NULL. +// If zero-terminated string /a/ starts with zero-terminated string /b/, returns /a + strlen(b)/. +// Otherwise, returns /NULL/. LS_INHEADER const char * ls_strfollow(const char *a, const char *b) diff --git a/libls/errno_utils.h b/libls/errno_utils.h index 8a9ec3c..49db6a6 100644 --- a/libls/errno_utils.h +++ b/libls/errno_utils.h @@ -7,20 +7,19 @@ #include "cstring_utils.h" #include "compdep.h" -// Like strerror_r, but always fills buf with something (and is GNU-safe). -// -// May alter errno. +// Like /strerror_r/, but always fills /buf/ with something (and is GNU-safe). +// May alter /errno/. LS_INHEADER void ls_strerror_r(int errnum, char *buf, size_t nbuf) { #if (_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && ! defined(_GNU_SOURCE) - // XSI-compliant strerror_r + // XSI-compliant /strerror_r/ if (strerror_r(errnum, buf, nbuf) != 0) { ls_strlcpy(buf, "Unknown error", nbuf); } #else - // GNU-specific strerror_r + // GNU-specific /strerror_r/ const char *s = strerror_r(errnum, buf, nbuf); if (s != buf) { ls_strlcpy(buf, s, nbuf); @@ -28,10 +27,10 @@ ls_strerror_r(int errnum, char *buf, size_t nbuf) #endif } -// Defines a char[] variable named BufVar_ in a new block, fill it with description of error -// with number ErrNum_, and then expands __VA_ARGS__. +// Defines a /char/ array variable named /BufVar_/ in a new block, fills it with description of +// error with number /ErrNum_/, and then expands /__VA_ARGS__/. // -// May alter errno before or after expanding __VA_ARGS__. +// May alter /errno/ before or after expanding /__VA_ARGS__/. #define LS_WITH_ERRSTR(BufVar_, ErrNum_, ...) \ do { \ char BufVar_[256]; \ diff --git a/libls/getenv_r.c b/libls/getenv_r.c index 5158f67..282f462 100644 --- a/libls/getenv_r.c +++ b/libls/getenv_r.c @@ -3,14 +3,16 @@ #include #include +extern char **environ; + const char * -ls_getenv_r(const char *name, char *const *env) +ls_getenv_r(const char *name) { if ((strchr(name, '='))) { return NULL; } size_t nname = strlen(name); - for (char *const *s = env; *s; ++s) { + for (char **s = environ; *s; ++s) { const char *entry = *s; if (strncmp(entry, name, nname) == 0 && entry[nname] == '=') { return entry + nname + 1; diff --git a/libls/getenv_r.h b/libls/getenv_r.h index ea450f3..f1f62c5 100644 --- a/libls/getenv_r.h +++ b/libls/getenv_r.h @@ -1,8 +1,8 @@ #ifndef ls_getenv_r_h_ #define ls_getenv_r_h_ -// Thread-safe getenv. Pass environ as the second argument. +// Thread-safe /getenv/. const char * -ls_getenv_r(const char *name, char *const *env); +ls_getenv_r(const char *name); #endif diff --git a/libls/io_utils.c b/libls/io_utils.c deleted file mode 100644 index de7afb6..0000000 --- a/libls/io_utils.c +++ /dev/null @@ -1,73 +0,0 @@ -#include "io_utils.h" - -#include -#include -#include -#include "alloc_utils.h" -#include "osdep.h" - -extern char **environ; - -ssize_t -ls_full_read_append(int fd, char **pbuf, size_t *psize, size_t *palloc) -{ - size_t nread = 0; - while (1) { - if (*psize == *palloc) { - *pbuf = ls_x2realloc(*pbuf, palloc, 1); - } - ssize_t r = read(fd, *pbuf + *psize, *palloc - *psize); - if (r < 0) { - return -1; - } else if (r == 0) { - break; - } else { - *psize += r; - nread += r; - } - } - return nread; -} - -pid_t -ls_spawnp_pipe(const char *file, int *pipe_fd, char *const *argv) -{ - pid_t pid = -1; - int pipe_fds[2] = {-1, -1}; - posix_spawn_file_actions_t fa; - posix_spawn_file_actions_t *p_inited_fa = NULL; - int saved_errno; - - if (pipe_fd) { - if (ls_cloexec_pipe(pipe_fds) < 0) { - goto cleanup; - } - if ((errno = posix_spawn_file_actions_init(&fa))) { - goto cleanup; - } - p_inited_fa = &fa; - if ((errno = posix_spawn_file_actions_adddup2(p_inited_fa, pipe_fds[1], 1))) { - goto cleanup; - } - } - - if ((errno = posix_spawnp(&pid, file, p_inited_fa, NULL, argv, environ))) { - pid = -1; - goto cleanup; - } - - if (pipe_fd) { - *pipe_fd = pipe_fds[0]; - pipe_fds[0] = -1; - } - -cleanup: - saved_errno = errno; - ls_close(pipe_fds[0]); - ls_close(pipe_fds[1]); - if (p_inited_fa) { - posix_spawn_file_actions_destroy(p_inited_fa); - } - errno = saved_errno; - return pid; -} diff --git a/libls/io_utils.h b/libls/io_utils.h index 54b7c0e..4e40b37 100644 --- a/libls/io_utils.h +++ b/libls/io_utils.h @@ -1,34 +1,13 @@ #ifndef ls_io_utils_h_ #define ls_io_utils_h_ -#include -#include #include -#include "compdep.h" - -ssize_t -ls_full_read_append(int fd, char **pbuf, size_t *psize, size_t *palloc); -// Spawns a process and returns its PID. -// -// If pipe_fd is NULL, the behaviour is same as: -// 1. forking, and; -// 2. in child process, calling execvp(file, argv), and exiting with code 127 if it fails, and; -// 3. returning PID of spawned process, or (pid_t) -1 if fork() fails. -// -// If pipe_fd is not NULL, the behaviour differs in that: -// 1. a pipe is created, and; -// 2. child process' stdout is redirected to its write end, and; -// 3. its read end is returned as *pipe_fd, and; -// 4. (pid_t) -1 is also returned if some of the actions described above fails. -// -// If (pid_t) -1 is returned, errno is set by a failed function. -// -// This function is thread-safe. -pid_t -ls_spawnp_pipe(const char *file, int *pipe_fd, char *const *argv); +#include "compdep.h" -// Makes a file descriptor CLOEXEC. +// Makes a file descriptor close-on-exec. +// On success, /fd/ is returned. +// On failure, /-1/ is returned and /errno/ is set. LS_INHEADER int ls_make_cloexec(int fd) @@ -40,4 +19,18 @@ ls_make_cloexec(int fd) return fd; } +// Makes a file descriptor non-blocking. +// On success, /fd/ is returned. +// On failure, /-1/ is returned and /errno/ is set. +LS_INHEADER +int +ls_make_nonblock(int fd) +{ + int flags = fcntl(fd, F_GETFL); + if (flags < 0 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { + return -1; + } + return fd; +} + #endif diff --git a/libls/lua_utils.h b/libls/lua_utils.h index dfc2074..da59b3f 100644 --- a/libls/lua_utils.h +++ b/libls/lua_utils.h @@ -6,20 +6,22 @@ #include "compdep.h" // Traverse a table. -// Before leaving this cycle, call LS_LUA_TRAVERSE_BREAK(L_). -// if StackIndex_ is relative to the top, decrease it by one because the previous key will be -// pushed onto the stack each time lua_next() is called. +// Before using /break/ in this cycle, call /LS_LUA_BREAK(L_)/. #define LS_LUA_TRAVERSE(L_, StackIndex_) \ for (lua_pushnil(L_); \ + /* if /StackIndex_/ is relative to the top, decrease it by one because the previous key + * will be pushed onto the stack each time lua_next() is called. */ \ lua_next(L_, (StackIndex_) < 0 ? (StackIndex_) - 1 : (StackIndex_)); \ lua_pop(L_, 1)) -#define LS_LUA_TRAVERSE_KEY (-2) -#define LS_LUA_TRAVERSE_VALUE (-1) +// Stack index of the key when in a LS_LUA_TRAVERSE cycle. +#define LS_LUA_KEY (-2) +// Stack index of the value when in a LS_LUA_TRAVERSE cycle. +#define LS_LUA_VALUE (-1) +// Call before using /break/ to leave a LS_LUA_TRAVERSE cycle. +#define LS_LUA_BREAK(L_) lua_pop(L_, 2) -#define LS_LUA_TRAVERSE_BREAK(L_) lua_pop(L_, 2) - -// The behaviour is same as calling lua_getfield(L, -1, key), except that it does not invoke +// The behaviour is same as calling /lua_getfield(L, -1, key)/, except that it does not invoke // metamethods. LS_INHEADER void @@ -29,7 +31,7 @@ ls_lua_rawgetf(lua_State *L, const char *key) lua_rawget(L, -2); } -// The behaviour is same as calling lua_setfield(L, -3, key), except that it does not invoke +// The behaviour is same as calling /lua_setfield(L, -2, key)/, except that it does not invoke // metamethods. LS_INHEADER void @@ -40,12 +42,12 @@ ls_lua_rawsetf(lua_State *L, const char *key) lua_rawset(L, -3); } -// Pushes the global table onto the stack. The behaviour is same as calling lua_pushglobaltable(L_) -// in Lua >=5.2. +// Pushes the global table onto the stack. The behaviour is same as calling +// /lua_pushglobaltable(L_)/ in Lua >=5.2. #if LUA_VERSION_NUM >= 502 -# define ls_lua_pushglobaltable lua_pushglobaltable +# define ls_lua_pushg lua_pushglobaltable #else -# define ls_lua_pushglobaltable(L_) lua_pushvalue(L_, LUA_GLOBALSINDEX) +# define ls_lua_pushg(L_) lua_pushvalue(L_, LUA_GLOBALSINDEX) #endif #endif diff --git a/libls/osdep.c b/libls/osdep.c index a5ce449..7a8882a 100644 --- a/libls/osdep.c +++ b/libls/osdep.c @@ -5,8 +5,23 @@ #include #include #include + #include "io_utils.h" -#include "probes.h" +#include "probes.generated.h" + +int +ls_close(int fd) +{ +#if LS_HAVE_POSIX_CLOSE + return posix_close(fd, 0); +#elif defined(__hpux) + int r; + while ((r = close(fd)) < 0 && errno == EINTR) {} + return r; +#else + return close(fd); +#endif +} int ls_cloexec_pipe(int pipefd[2]) diff --git a/libls/osdep.h b/libls/osdep.h index 35cf2a3..7bd5673 100644 --- a/libls/osdep.h +++ b/libls/osdep.h @@ -1,31 +1,26 @@ #ifndef ls_osdep_h_ #define ls_osdep_h_ -// In no event should _GNU_SOURCE or something similar be defined here! +// We can't define /_GNU_SOURCE/ here, nor can we include "probes.generated.h", as +// it may be located outside of the source tree: /libls/ is built with /libls/'s +// binary directory included, unlike everything that includes its headers. +// So no in-header functions, unfortunately. -#include -#include -#include "compdep.h" -#include "probes.h" - -#if LS_HAVE_POSIX_CLOSE -# define ls_close(FD_) posix_close(FD_, 0) -#elif defined (__hpux) -LS_INHEADER +// /EINTR/-safe (as possible) /close()/. Please read: +// 1. http://www.daemonology.net/blog/2011-12-17-POSIX-close-is-broken.html +// 2. https://news.ycombinator.com/item?id=3363819 +// 3. https://sourceware.org/bugzilla/show_bug.cgi?id=16302 int -ls_close(int fd) -{ - int r; - while ((r = close(fd)) < 0 && errno == EINTR) {} - return r; -} -#else -# define ls_close close -#endif +ls_close(int fd); +// The behaviour is same as calling /pipe(pipefd)/, except that both file descriptors are made +// close-on-exec. If the latter fails, the pipe is destroyed, /-1/ is returned and /errno/ is set. int ls_cloexec_pipe(int pipefd[2]); +// The behaviour is same as calling /socket(domain, type, protocol)/, except that the file +// descriptor is make close-on-exec. If the latter fails, the pipe is destroyed, /-1/ is reurned and +// /errno/ is set. int ls_cloexec_socket(int domain, int type, int protocol); diff --git a/libls/parse_int.c b/libls/parse_int.c index 090ddd6..e54a7f9 100644 --- a/libls/parse_int.c +++ b/libls/parse_int.c @@ -5,7 +5,7 @@ #include int -ls_parse_uint(const char *s, size_t ns, char const **endptr) +ls_parse_uint_b(const char *s, size_t ns, char const **endptr) { int ret = 0; size_t i = 0; @@ -33,13 +33,13 @@ ls_parse_uint(const char *s, size_t ns, char const **endptr) } int -ls_full_parse_uint(const char *s, size_t ns) +ls_full_parse_uint_b(const char *s, size_t ns) { if (!ns) { return -EINVAL; } const char *endptr; - int r = ls_parse_uint(s, ns, &endptr); + int r = ls_parse_uint_b(s, ns, &endptr); if (r < 0) { return r; } @@ -50,7 +50,7 @@ ls_full_parse_uint(const char *s, size_t ns) } int -ls_full_parse_uint_cstr(const char *s) +ls_full_parse_uint_s(const char *s) { - return ls_full_parse_uint(s, strlen(s)); + return ls_full_parse_uint_b(s, strlen(s)); } diff --git a/libls/parse_int.h b/libls/parse_int.h index 01e114e..1fc2916 100644 --- a/libls/parse_int.h +++ b/libls/parse_int.h @@ -3,13 +3,27 @@ #include +// Parses (locale-independently) a decimal unsigned integer, inspecting no more than first /ns/ +// characters of /s/. Once this limit is reached, or a non-digit character is found, this function +// stops, writes the current position to /*endptr/, and returns what has been parsed insofar (if +// nothing, /0/ is returned). +// +// If an overflow happens, /-ERANGE/ is returned. int -ls_parse_uint(const char *s, size_t ns, char const **endptr); +ls_parse_uint_b(const char *s, size_t ns, char const **endptr); +// Parses (locale-independently) a decimal unsigned integer using first /ns/ characters of /s/. +// +// If a non-digit character is found among them, or if /ns/ is /0/, /-EINVAL/ is returned. +// If an overflow happens, /-ERANGE/ is returned. int -ls_full_parse_uint(const char *s, size_t ns); +ls_full_parse_uint_b(const char *s, size_t ns); +// Parses (locale-independently) a decimal unsigned integer using zero-terminated string /s/. +// +// If a non-digit character is found in /s/, or if /s/ is empty, /-EINVAL/ is returned. +// If an overflow happens, /-ERANGE/ is returned. int -ls_full_parse_uint_cstr(const char *s); +ls_full_parse_uint_s(const char *s); #endif diff --git a/libls/probes.h.in b/libls/probes.in.h similarity index 100% rename from libls/probes.h.in rename to libls/probes.in.h diff --git a/libls/sprintf_utils.c b/libls/sprintf_utils.c index d48c2ec..e90c3c1 100644 --- a/libls/sprintf_utils.c +++ b/libls/sprintf_utils.c @@ -1,9 +1,10 @@ #include "sprintf_utils.h" #include +#include #include -char* +char * ls_vasprintf(const char *fmt, va_list vl) { char *ret = NULL; diff --git a/libls/sprintf_utils.h b/libls/sprintf_utils.h index a8e9caa..2159b9f 100644 --- a/libls/sprintf_utils.h +++ b/libls/sprintf_utils.h @@ -3,64 +3,23 @@ #include #include -#include -#include -#include +#include "alloc_utils.h" #include "compdep.h" -#include "cstring_utils.h" -#include "errno_utils.h" -LS_INHEADER -void -ls_svsnprintf(char *buf, size_t nbuf, const char *fmt, va_list vl) -{ - if (vsnprintf(buf, nbuf, fmt, vl) < 0) { - ls_strlcpy(buf, "(vsnprintf failed)", nbuf); - } -} - -LS_INHEADER LS_ATTR_PRINTF(3, 4) -void -ls_ssnprintf(char *buf, size_t nbuf, const char *fmt, ...) -{ - va_list vl; - va_start(vl, fmt); - ls_svsnprintf(buf, nbuf, fmt, vl); - va_end(vl); -} - -LS_INHEADER -void -ls_xvsnprintf(char *buf, size_t nbuf, const char *fmt, va_list vl) -{ - int r = vsnprintf(buf, nbuf, fmt, vl); - if (r < 0) { - LS_WITH_ERRSTR(s, errno, - fprintf(stderr, "ls_xvsnprintf: %s\n", s); - ); - abort(); - } else if ((size_t) r >= nbuf) { - fputs("ls_xvsnprintf: insufficient buffer space\n", stderr); - abort(); - } -} - -LS_INHEADER LS_ATTR_PRINTF(3, 4) -void -ls_xsnprintf(char *buf, size_t nbuf, const char *fmt, ...) -{ - va_list vl; - va_start(vl, fmt); - ls_xvsnprintf(buf, nbuf, fmt, vl); - va_end(vl); -} - -char* +// Tries to allocate (as if with /malloc/) a buffer of a sufficient size and +// /vsnprintf(, fmt, vl)/ into it. On success, returns the buffer. +// +// If an encoding error occurs, /NULL/ is returned and /errno/ is set by /vsnprintf/. +// +// If allocation fails, /NULL/ is returned and /errno/ is set to /ENOMEM/. +char * ls_vasprintf(const char *fmt, va_list vl); +// The same as /ls_vasprintf/, except that this function is called with a variable number of +// arguments instead of a /va_list/. LS_INHEADER LS_ATTR_PRINTF(1, 2) -char* +char * ls_asprintf(const char *fmt, ...) { va_list vl; @@ -72,28 +31,31 @@ ls_asprintf(const char *fmt, ...) return r; } +// The behaviour is same as calling /ls_vasprintf(fmt, vl)/, except when allocation fails. In that +// case, this function panics. LS_INHEADER -char* +char * ls_xvasprintf(const char *fmt, va_list vl) { char *r = ls_vasprintf(fmt, vl); - if (!r) { - LS_WITH_ERRSTR(s, errno, - fprintf(stderr, "ls_xvasprintf: %s\n", s); - ); - abort(); + if (!r && errno == ENOMEM) { + ls_oom(); } return r; } +// The behaviour is same as calling /ls_asprintf(fmt, ...)/, except when allocation fails. In that +// case, this function panics. LS_INHEADER LS_ATTR_PRINTF(1, 2) -char* +char * ls_xasprintf(const char *fmt, ...) { va_list vl; va_start(vl, fmt); char *r = ls_xvasprintf(fmt, vl); + int saved_errno = errno; va_end(vl); + errno = saved_errno; return r; } diff --git a/libls/strarr.h b/libls/strarr.h index 9f14eae..4d8486f 100644 --- a/libls/strarr.h +++ b/libls/strarr.h @@ -2,59 +2,99 @@ #define ls_strarr_h_ #include -#include "string.h" + +#include "string_.h" #include "vector.h" +#include "compdep.h" + +// An array of constant strings on a single buffer. Panics on allocation failure. typedef struct { - LSString _buf; - LS_VECTOR_OF(size_t) _offsets; + LSString buf_; + LS_VECTOR_OF(size_t) offsets_; } LSStringArray; -#define LS_STRARR_INITIALIZER {._buf = LS_VECTOR_NEW(), ._offsets = LS_VECTOR_NEW()} +// Creates an empty string array. +LS_INHEADER +LSStringArray +ls_strarr_new(void) +{ + return (LSStringArray) { + .buf_ = LS_VECTOR_NEW(), + .offsets_ = LS_VECTOR_NEW(), + }; +} + +// Creates an empty string array with a space for /nelems/ elements with total length of /totlen/ +// reserved. +LS_INHEADER +LSStringArray +ls_strarr_new_reserve(size_t totlen, size_t nelems) +{ + return (LSStringArray) { + .buf_ = LS_VECTOR_NEW_RESERVE(char, totlen), + .offsets_ = LS_VECTOR_NEW_RESERVE(size_t, nelems), + }; +} +// Appends a buffer /buf/ of size /nbuf/ to a string array to a string array /sa/. LS_INHEADER void ls_strarr_append(LSStringArray *sa, const char *buf, size_t nbuf) { - LS_VECTOR_PUSH(sa->_offsets, sa->_buf.size); - ls_string_append_b(&sa->_buf, buf, nbuf); + LS_VECTOR_PUSH(sa->offsets_, sa->buf_.size); + ls_string_append_b(&sa->buf_, buf, nbuf); } +// Returns the size of a string array /sa/. LS_INHEADER size_t ls_strarr_size(LSStringArray sa) { - return sa._offsets.size; + return sa.offsets_.size; } +// Returns a pointer to the start of the string at specified index in a string array /sa/. If /n/ is +// not /NULL/, /*n/ is set to the size of that string. LS_INHEADER const char * ls_strarr_at(LSStringArray sa, size_t index, size_t *n) { - const size_t begin = sa._offsets.data[index]; - const size_t end = index + 1 == sa._offsets.size - ? sa._buf.size - : sa._offsets.data[index + 1]; + const size_t begin = sa.offsets_.data[index]; + const size_t end = index + 1 == sa.offsets_.size + ? sa.buf_.size + : sa.offsets_.data[index + 1]; if (n) { *n = end - begin; } - return sa._buf.data + begin; + return sa.buf_.data + begin; } +// Clears a string array /sa/. LS_INHEADER void ls_strarr_clear(LSStringArray *sa) { - LS_VECTOR_CLEAR(sa->_buf); - LS_VECTOR_CLEAR(sa->_offsets); + LS_VECTOR_CLEAR(sa->buf_); + LS_VECTOR_CLEAR(sa->offsets_); +} + +// Shrinks a string array /sa/ to its real size. +LS_INHEADER +void +ls_strarr_shrink(LSStringArray *sa) +{ + LS_VECTOR_SHRINK(sa->buf_); + LS_VECTOR_SHRINK(sa->offsets_); } +// Destroys a (previously initialized) string array /sa/. LS_INHEADER void ls_strarr_destroy(LSStringArray sa) { - LS_VECTOR_FREE(sa._buf); - LS_VECTOR_FREE(sa._offsets); + LS_VECTOR_FREE(sa.buf_); + LS_VECTOR_FREE(sa.offsets_); } #endif diff --git a/libls/string.h b/libls/string.h deleted file mode 100644 index 74dd22f..0000000 --- a/libls/string.h +++ /dev/null @@ -1,97 +0,0 @@ -#ifndef ls_string_h_ -#define ls_string_h_ - -#include -#include -#include -#include - -#include "vector.h" -#include "compdep.h" - -typedef LS_VECTOR_OF(char) LSString; - -LS_INHEADER -void -ls_string_assign_b(LSString *s, const char *buf, size_t nbuf) -{ - LS_VECTOR_ENSURE(*s, nbuf); - if (nbuf) { - memcpy(s->data, buf, nbuf); - } - s->size = nbuf; -} - -LS_INHEADER -void -ls_string_assign_s(LSString *s, const char *cstr) -{ - ls_string_assign_b(s, cstr, strlen(cstr)); -} - -LS_INHEADER -void -ls_string_append_b(LSString *s, const char *buf, size_t nbuf) -{ - LS_VECTOR_ENSURE(*s, s->size + nbuf); - if (nbuf) { - memcpy(s->data + s->size, buf, nbuf); - } - s->size += nbuf; -} - -LS_INHEADER -void -ls_string_append_s(LSString *s, const char *cstr) -{ - ls_string_append_b(s, cstr, strlen(cstr)); -} - -LS_INHEADER -void -ls_string_append_c(LSString *s, char c) -{ - LS_VECTOR_PUSH(*s, c); -} - -LS_INHEADER -void -ls_string_assign_c(LSString *s, char c) -{ - ls_string_assign_b(s, &c, 1); -} - -bool -ls_string_append_vf(LSString *s, const char *fmt, va_list vl); - -LS_INHEADER LS_ATTR_PRINTF(2, 3) -bool -ls_string_append_f(LSString *s, const char *fmt, ...) -{ - va_list vl; - va_start(vl, fmt); - bool r = ls_string_append_vf(s, fmt, vl); - va_end(vl); - return r; -} - -LS_INHEADER -bool -ls_string_assign_vf(LSString *s, const char *fmt, va_list vl) -{ - LS_VECTOR_CLEAR(*s); - return ls_string_append_vf(s, fmt, vl); -} - -LS_INHEADER LS_ATTR_PRINTF(2, 3) -bool -ls_string_assign_f(LSString *s, const char *fmt, ...) -{ - va_list vl; - va_start(vl, fmt); - bool r = ls_string_assign_vf(s, fmt, vl); - va_end(vl); - return r; -} - -#endif diff --git a/libls/string.c b/libls/string_.c similarity index 97% rename from libls/string.c rename to libls/string_.c index 48b14c7..f0da169 100644 --- a/libls/string.c +++ b/libls/string_.c @@ -1,13 +1,13 @@ -#include "string.h" +#include "string_.h" #include #include #include - -#include "libls/vector.h" #include #include +#include "libls/vector.h" + bool ls_string_append_vf(LSString *s, const char *fmt, va_list vl) { diff --git a/libls/string_.h b/libls/string_.h new file mode 100644 index 0000000..af65cd7 --- /dev/null +++ b/libls/string_.h @@ -0,0 +1,146 @@ +#ifndef ls_string_h_ +#define ls_string_h_ + +#include +#include +#include +#include + +#include "vector.h" +#include "compdep.h" + +// /LSString/ is a string defined as /LS_VECTOR_OF(char)/. It is guaranteed to be defined in this +// way, so that all the /LS_VECTOR_*/ macros work with /LSString/s. +typedef LS_VECTOR_OF(char) LSString; + +// Assigns a string value to /s/. If /nbuf/ is /0/, /buf/ is not required to be dereferencable. +LS_INHEADER +void +ls_string_assign_b(LSString *s, const char *buf, size_t nbuf) +{ + LS_VECTOR_ENSURE(*s, nbuf); + // see DOCS/c_notes/empty-ranges-and-c-stdlib.md + if (nbuf) { + memcpy(s->data, buf, nbuf); + } + s->size = nbuf; +} + +// Assigns a zero-terminated value to /s/. +LS_INHEADER +void +ls_string_assign_s(LSString *s, const char *cstr) +{ + ls_string_assign_b(s, cstr, strlen(cstr)); +} + +// Assigns a char value to /s/. +LS_INHEADER +void +ls_string_assign_c(LSString *s, char c) +{ + ls_string_assign_b(s, &c, 1); +} + +// Appends a string to /s/. If /nbuf/ is /0/, /buf/ is not required to be dereferencable. +LS_INHEADER +void +ls_string_append_b(LSString *s, const char *buf, size_t nbuf) +{ + LS_VECTOR_ENSURE(*s, s->size + nbuf); + // see DOCS/c_notes/empty-ranges-and-c-stdlib.md + if (nbuf) { + memcpy(s->data + s->size, buf, nbuf); + } + s->size += nbuf; +} + +// Appends a zero-terminated string to /s/. +LS_INHEADER +void +ls_string_append_s(LSString *s, const char *cstr) +{ + ls_string_append_b(s, cstr, strlen(cstr)); +} + +// Appends a char to /s/. +LS_INHEADER +void +ls_string_append_c(LSString *s, char c) +{ + LS_VECTOR_PUSH(*s, c); +} + +// Appends a formatted string to /s/. Returns /true/ on success or /false/ if an encoding error +// occurs. +bool +ls_string_append_vf(LSString *s, const char *fmt, va_list vl); + +// Appends a formatted string to /s/. Returns /true/ on success or /false/ if an encoding error +// occurs. +LS_INHEADER LS_ATTR_PRINTF(2, 3) +bool +ls_string_append_f(LSString *s, const char *fmt, ...) +{ + va_list vl; + va_start(vl, fmt); + bool r = ls_string_append_vf(s, fmt, vl); + va_end(vl); + return r; +} + +// Assigns a formatted string value to /s/. Returns /true/ on success or /false/ if an encoding +// error occurs. +LS_INHEADER +bool +ls_string_assign_vf(LSString *s, const char *fmt, va_list vl) +{ + LS_VECTOR_CLEAR(*s); + return ls_string_append_vf(s, fmt, vl); +} + +// Assigns a formatted string value to /s/. Returns /true/ on success or /false/ if an encoding +// error occurs. +LS_INHEADER LS_ATTR_PRINTF(2, 3) +bool +ls_string_assign_f(LSString *s, const char *fmt, ...) +{ + va_list vl; + va_start(vl, fmt); + bool r = ls_string_assign_vf(s, fmt, vl); + va_end(vl); + return r; +} + +// Constructs a new /LSString/ initialized with a value of the zero-terminated string /cstr/. +LS_INHEADER +LSString +ls_string_new_from_s(const char *cstr) +{ + LSString r = LS_VECTOR_NEW(); + ls_string_assign_s(&r, cstr); + return r; +} + +// Constructs a new /LSString/ initialized with a value of the buffer given. If /nbuf/ is /0/, /buf/ +// is not required to be dereferencable. +LS_INHEADER +LSString +ls_string_new_from_b(const char *buf, size_t nbuf) +{ + LSString r = LS_VECTOR_NEW(); + ls_string_assign_b(&r, buf, nbuf); + return r; +} + +// Constructs a new /LSString/ initialized with a value of the char given. +LS_INHEADER +LSString +ls_string_new_from_c(char c) +{ + LSString r = LS_VECTOR_NEW(); + LS_VECTOR_PUSH(r, c); + return r; +} + +#endif diff --git a/libls/time_utils.c b/libls/time_utils.c new file mode 100644 index 0000000..1caec42 --- /dev/null +++ b/libls/time_utils.c @@ -0,0 +1,42 @@ +#include "time_utils.h" + +#include +#include +#include + +// /time_t/ can't be less than 31 bits, can it?... +static const double TIME_T_LIMIT = 2147483647.; + +struct timespec +ls_timespec_from_seconds(double seconds) +{ + if (seconds < 0) { + errno = EDOM; + return ls_timespec_invalid; + } + if (seconds > TIME_T_LIMIT) { + errno = ERANGE; + return ls_timespec_invalid; + } + return (struct timespec) { + .tv_sec = seconds, + .tv_nsec = (seconds - (time_t) seconds) * 1e9, + }; +} + +struct timeval +ls_timeval_from_seconds(double seconds) +{ + if (seconds < 0) { + errno = EDOM; + return ls_timeval_invalid; + } + if (seconds > TIME_T_LIMIT) { + errno = ERANGE; + return ls_timeval_invalid; + } + return (struct timeval) { + .tv_sec = seconds, + .tv_usec = (seconds - (time_t) seconds) * 1e6, + }; +} diff --git a/libls/time_utils.h b/libls/time_utils.h index c2f4f8d..aa27399 100644 --- a/libls/time_utils.h +++ b/libls/time_utils.h @@ -4,7 +4,7 @@ #include #include #include -#include + #include "compdep.h" static const struct timespec ls_timespec_invalid = {0, -1}; @@ -24,42 +24,10 @@ ls_timeval_is_invalid(struct timeval tv) return tv.tv_usec == -1; } -static const double TIME_T_LIMIT = 2147483647.; - -LS_INHEADER struct timespec -ls_timespec_from_seconds(double seconds) -{ - if (seconds < 0) { - errno = EDOM; - return ls_timespec_invalid; - } - if (seconds > TIME_T_LIMIT) { - errno = ERANGE; - return ls_timespec_invalid; - } - return (struct timespec) { - .tv_sec = seconds, - .tv_nsec = (seconds - (time_t) seconds) * 1e9, - }; -} +ls_timespec_from_seconds(double seconds); -LS_INHEADER struct timeval -ls_timeval_from_seconds(double seconds) -{ - if (seconds < 0) { - errno = EDOM; - return ls_timeval_invalid; - } - if (seconds > TIME_T_LIMIT) { - errno = ERANGE; - return ls_timeval_invalid; - } - return (struct timeval) { - .tv_sec = seconds, - .tv_usec = (seconds - (time_t) seconds) * 1e6, - }; -} +ls_timeval_from_seconds(double seconds); #endif diff --git a/libls/vector.h b/libls/vector.h index 9f62abd..a08d17d 100644 --- a/libls/vector.h +++ b/libls/vector.h @@ -2,6 +2,7 @@ #define ls_vector_h_ #include + #include "alloc_utils.h" #define LS_VECTOR_OF(Type_) \ @@ -64,6 +65,16 @@ (Vec_).data[(Vec_).size++] = (Elem_); \ } while (0) +#define LS_VECTOR_REMOVE(Vec_, StartPos_, NRemove_) \ + do { \ + if ((Vec_).size != (StartPos_) + (NRemove_)) { \ + memmove((Vec_).data + (StartPos_), \ + (Vec_).data + (StartPos_) + (NRemove_), \ + ((Vec_).size - (StartPos_) - (NRemove_)) * sizeof(*(Vec_).data)); \ + } \ + (Vec_).size -= (NRemove_); \ + } while (0) + #define LS_VECTOR_FREE(Vec_) free((Vec_).data) #endif diff --git a/libls/wakeup_fifo.c b/libls/wakeup_fifo.c index 4c0e3a3..fddc070 100644 --- a/libls/wakeup_fifo.c +++ b/libls/wakeup_fifo.c @@ -2,26 +2,39 @@ #include #include +#include + #include "osdep.h" +#include "time_utils.h" -void -ls_wakeup_fifo_init(LSWakeupFifo *w) +int +ls_wakeup_fifo_init(LSWakeupFifo *w, const char *fifo, struct timespec timeout, sigset_t *sigmask) { - FD_ZERO(&w->fds); - w->fd = -1; + w->fifo = fifo; + w->timeout = timeout; + FD_ZERO(&w->fds_); + w->fd_ = -1; + if (sigmask) { + w->sigmask = *sigmask; + } else { + if (sigfillset(&w->sigmask) < 0) { + return -1; + } + } + return 0; } int ls_wakeup_fifo_open(LSWakeupFifo *w) { - if (w->fd < 0 && w->fifo) { - w->fd = open(w->fifo, O_RDONLY | O_NONBLOCK | O_CLOEXEC); - if (w->fd < 0) { + if (w->fd_ < 0 && w->fifo) { + w->fd_ = open(w->fifo, O_RDONLY | O_CLOEXEC | O_NONBLOCK); + if (w->fd_ < 0) { return -1; } - if (w->fd >= FD_SETSIZE) { - ls_close(w->fd); - w->fd = -1; + if (w->fd_ >= FD_SETSIZE) { + ls_close(w->fd_); + w->fd_ = -1; errno = EMFILE; return -1; } @@ -31,29 +44,31 @@ ls_wakeup_fifo_open(LSWakeupFifo *w) } int -ls_wakeup_fifo_pselect(LSWakeupFifo *w) +ls_wakeup_fifo_wait(LSWakeupFifo *w) { - const int fd = w->fd; - - if (fd >= 0) { - FD_SET(fd, &w->fds); + if (w->fd_ >= 0) { + FD_SET(w->fd_, &w->fds_); } - const int r = pselect(fd >= 0 ? fd + 1 : 0, &w->fds, NULL, NULL, w->timeout, w->sigmask); + const int r = pselect( + w->fd_ >= 0 ? w->fd_ + 1 : 0, + &w->fds_, NULL, NULL, + ls_timespec_is_invalid(w->timeout) ? NULL : &w->timeout, + &w->sigmask); if (r < 0) { int saved_errno = errno; - if (fd >= 0) { - FD_CLR(fd, &w->fds); + if (w->fd_ >= 0) { + FD_CLR(w->fd_, &w->fds_); } - ls_close(fd); - w->fd = -1; + ls_close(w->fd_); + w->fd_ = -1; errno = saved_errno; return -1; } else if (r == 0) { return 0; } else { - FD_CLR(fd, &w->fds); - ls_close(fd); - w->fd = -1; + FD_CLR(w->fd_, &w->fds_); + ls_close(w->fd_); + w->fd_ = -1; return 1; } } @@ -61,5 +76,5 @@ ls_wakeup_fifo_pselect(LSWakeupFifo *w) void ls_wakeup_fifo_destroy(LSWakeupFifo *w) { - ls_close(w->fd); + ls_close(w->fd_); } diff --git a/libls/wakeup_fifo.h b/libls/wakeup_fifo.h index 466f22f..9abe2b8 100644 --- a/libls/wakeup_fifo.h +++ b/libls/wakeup_fifo.h @@ -5,23 +5,65 @@ #include #include +// An interface to perform a timed wait on a FIFO. +// Usage: +// +// LSWakeupFifo w; +// if (ls_wakeup_fifo_init(&w) < 0) { +// /* ... (errno is set) */ +// goto error; +// } +// w.fifo = /* ... (NULL means don't wait for FIFO events) */; +// w.timeout = /* ... (ls_timespec_invalid means don't wait for timeout events) */; +// w.sigmask = /* ... (usually you don't need to do this) */; +// +// /* ... */ +// +// while (1) { +// +// /* ... */ +// +// if (ls_wakeup_fifo_open(&w) < 0) { +// /* ... (errno is set) */ +// goto error; +// } +// switch (ls_wakeup_fifo_wait(&w)) { +// case -1: +// /* ... (errno is set) */ +// goto error; +// break; +// case 0: +// /* ... (timeout) */ +// break; +// case 1: +// default: +// /* ... (FIFO has been touched) */ +// break; +// } +// } +// +// /* ... */ +// +// error: +// ls_wakeup_fifo_destroy(&w); +// /* ... */ + typedef struct { const char *fifo; - const struct timespec *timeout; - const sigset_t *sigmask; - - fd_set fds; - int fd; + struct timespec timeout; + sigset_t sigmask; + fd_set fds_; + int fd_; } LSWakeupFifo; -void -ls_wakeup_fifo_init(LSWakeupFifo *w); +int +ls_wakeup_fifo_init(LSWakeupFifo *w, const char *fifo, struct timespec timeout, sigset_t *sigmask); int ls_wakeup_fifo_open(LSWakeupFifo *w); int -ls_wakeup_fifo_pselect(LSWakeupFifo *w); +ls_wakeup_fifo_wait(LSWakeupFifo *w); void ls_wakeup_fifo_destroy(LSWakeupFifo *w); diff --git a/luastatus/.gitignore b/luastatus/.gitignore new file mode 100644 index 0000000..d42db2d --- /dev/null +++ b/luastatus/.gitignore @@ -0,0 +1 @@ +luastatus diff --git a/luastatus/CMakeLists.txt b/luastatus/CMakeLists.txt index ee778c5..21ffe50 100644 --- a/luastatus/CMakeLists.txt +++ b/luastatus/CMakeLists.txt @@ -1,20 +1,18 @@ if (NOT luastatus_POSIX_SH_PATH) set (luastatus_POSIX_SH_PATH "/bin/sh") endif () -configure_file ("config.h.in" "${PROJECT_BINARY_DIR}/config.h") +set (luastatus_VERSION "${CMAKE_CURRENT_SOURCE_DIR}/version.sh") +configure_file ("config.in.h" "config.generated.h") -file (GLOB sources "*.c") -add_executable (luastatus $ ${sources}) +#execute_process (COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/version_c_generate.sh" "${PROJECT_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}") +add_executable (luastatus $ "luastatus.c") #"${CMAKE_CURRENT_BINARY_DIR}/version.generated.c") target_compile_definitions (luastatus PUBLIC -D_POSIX_C_SOURCE=200809L) luastatus_target_build_with (luastatus LUA) -target_include_directories (luastatus PUBLIC "${PROJECT_SOURCE_DIR}" "${PROJECT_BINARY_DIR}") +target_include_directories (luastatus PUBLIC "${PROJECT_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}") target_link_libraries (luastatus dl pthread) -add_custom_target (version - ./version_h_generate.sh "${PROJECT_SOURCE_DIR}" "${PROJECT_BINARY_DIR}" - VERBATIM) -add_dependencies (luastatus version) +include (GNUInstallDirs) -install (TARGETS luastatus DESTINATION bin) -install (FILES luastatus.1 DESTINATION man/man1) +install (TARGETS luastatus DESTINATION ${CMAKE_INSTALL_BINDIR}) +install (FILES luastatus.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) diff --git a/luastatus/barlib.c b/luastatus/barlib.c deleted file mode 100644 index 4b47908..0000000 --- a/luastatus/barlib.c +++ /dev/null @@ -1,90 +0,0 @@ -#include "barlib.h" - -#include -#include -#include -#include -#include "libls/compdep.h" -#include "include/loglevel.h" -#include "sane_dlerror.h" -#include "log.h" -#include "pth_check.h" - -bool -barlib_load(Barlib *b, const char *filename) -{ - b->dlhandle = NULL; - - (void) dlerror(); // clear any last error - if (!(b->dlhandle = dlopen(filename, RTLD_NOW))) { - internal_logf(LUASTATUS_ERR, "dlopen: %s: %s", filename, sane_dlerror()); - goto error; - } - int *p_lua_ver = dlsym(b->dlhandle, "LUASTATUS_BARLIB_LUA_VERSION_NUM"); - if (!p_lua_ver) { - internal_logf(LUASTATUS_ERR, "dlsym: LUASTATUS_BARLIB_LUA_VERSION_NUM: %s", - sane_dlerror()); - goto error; - } - if (*p_lua_ver != LUA_VERSION_NUM) { - internal_logf(LUASTATUS_ERR, - "barlib '%s' was compiled with LUA_VERSION_NUM=%d and luastatus with %d", - filename, *p_lua_ver, LUA_VERSION_NUM); - goto error; - } - LuastatusBarlibIface *p_iface = dlsym(b->dlhandle, "luastatus_barlib_iface"); - if (!p_iface) { - internal_logf(LUASTATUS_ERR, "dlsym: luastatus_barlib_iface: %s", sane_dlerror()); - goto error; - } - b->iface = *p_iface; - PTH_CHECK(pthread_mutex_init(&b->set_mtx, NULL)); - - b->state = BARLIB_STATE_LOADED; - return true; - -error: - if (b->dlhandle) { - dlclose(b->dlhandle); - } - return false; -} - -bool -barlib_init(Barlib *b, LuastatusBarlibData data, const char *const *opts, size_t nwidgets) -{ - assert(b->state == BARLIB_STATE_LOADED); - - b->data = data; - switch (b->iface.init(&b->data, opts, nwidgets)) { - case LUASTATUS_BARLIB_INIT_RESULT_OK: - b->state = BARLIB_STATE_INITED; - return true; - case LUASTATUS_BARLIB_INIT_RESULT_ERR: - internal_logf(LUASTATUS_ERR, "barlib's init() failed"); - return false; - } - LS_UNREACHABLE(); -} - -void -barlib_uninit(Barlib *b) -{ - assert(b->state == BARLIB_STATE_INITED); - - b->iface.destroy(&b->data); - b->state = BARLIB_STATE_LOADED; -} - -void -barlib_unload(Barlib *b) -{ - switch (b->state) { - case BARLIB_STATE_INITED: - barlib_uninit(b); - /* fallthru */ - case BARLIB_STATE_LOADED: - dlclose(b->dlhandle); - PTH_CHECK(pthread_mutex_destroy(&b->set_mtx)); - } -} diff --git a/luastatus/barlib.h b/luastatus/barlib.h deleted file mode 100644 index bee626a..0000000 --- a/luastatus/barlib.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef barlib_h_ -#define barlib_h_ - -#include -#include -#include -#include "include/barlib_data.h" - -typedef enum { - BARLIB_STATE_LOADED, - BARLIB_STATE_INITED, -} BarlibState; - -typedef struct { - LuastatusBarlibIface iface; - LuastatusBarlibData data; - pthread_mutex_t set_mtx; - void *dlhandle; - BarlibState state; -} Barlib; - -bool -barlib_load(Barlib *b, const char *filename); - -bool -barlib_init(Barlib *b, LuastatusBarlibData data, const char *const *opts, size_t nwidgets); - -void -barlib_uninit(Barlib *b); - -void -barlib_unload(Barlib *b); - -#endif diff --git a/luastatus/check_lua_call.c b/luastatus/check_lua_call.c deleted file mode 100644 index 057f0a2..0000000 --- a/luastatus/check_lua_call.c +++ /dev/null @@ -1,46 +0,0 @@ -#include "check_lua_call.h" - -#include -#include -#include -#include "include/loglevel.h" -#include "log.h" - -bool -check_lua_call(lua_State *L, int retval) -{ - const char *prefix; - switch (retval) { - case 0: - return true; - case LUA_ERRRUN: - case LUA_ERRSYNTAX: - prefix = "(lua) "; - break; - case LUA_ERRMEM: - prefix = "(lua) out of memory: "; - break; - case LUA_ERRERR: - prefix = "(lua) error while running error handler: "; - break; - case LUA_ERRFILE: - // Lua itself prepends an error message with "cannot open : " - prefix = "(lua) "; - break; -#ifdef LUA_ERRGCMM - // first introduced in Lua 5.2 - case LUA_ERRGCMM: - prefix = "(lua) error while running __gc metamethod: "; - break; -#endif - default: - prefix = "unknown Lua error code (please report!), message is: "; - } - const char *msg = lua_tostring(L, -1); - if (!msg) { - msg = "(error object can't be converted to string)"; - } - internal_logf(LUASTATUS_ERR, "%s%s", prefix, msg); - lua_pop(L, 1); - return false; -} diff --git a/luastatus/check_lua_call.h b/luastatus/check_lua_call.h deleted file mode 100644 index e878d55..0000000 --- a/luastatus/check_lua_call.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef check_lua_call_h_ -#define check_lua_call_h_ - -#include -#include - -bool -check_lua_call(lua_State *L, int retval); - -#endif diff --git a/luastatus/config.h.in b/luastatus/config.in.h similarity index 82% rename from luastatus/config.h.in rename to luastatus/config.in.h index 0a05bca..d845039 100644 --- a/luastatus/config.h.in +++ b/luastatus/config.in.h @@ -4,5 +4,6 @@ #define LUASTATUS_POSIX_SH_PATH "@luastatus_POSIX_SH_PATH@" #define LUASTATUS_PLUGINS_DIR "@PLUGINS_DIR@" #define LUASTATUS_BARLIBS_DIR "@BARLIBS_DIR@" +#define LUASTATUS_VERSION "@VERSION@" #endif diff --git a/luastatus/load_by_name.c b/luastatus/load_by_name.c deleted file mode 100644 index fb44704..0000000 --- a/luastatus/load_by_name.c +++ /dev/null @@ -1,33 +0,0 @@ -#include "load_by_name.h" - -#include -#include -#include - -#include "config.h" -#include "luastatus/barlib.h" -#include "luastatus/plugin.h" -#include "libls/alloc_utils.h" -#include "libls/sprintf_utils.h" - -bool -load_plugin_by_name(Plugin *p, const char *name) -{ - char *filename = strchr(name, '/') - ? ls_xstrdup(name) - : ls_xasprintf("%s/%s.so", LUASTATUS_PLUGINS_DIR, name); - bool r = plugin_load(p, filename, name); - free(filename); - return r; -} - -bool -load_barlib_by_name(Barlib *b, const char *name) -{ - char *filename = strchr(name, '/') - ? ls_xstrdup(name) - : ls_xasprintf("%s/%s.so", LUASTATUS_BARLIBS_DIR, name); - bool r = barlib_load(b, filename); - free(filename); - return r; -} diff --git a/luastatus/load_by_name.h b/luastatus/load_by_name.h deleted file mode 100644 index c9d6ea5..0000000 --- a/luastatus/load_by_name.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef load_by_name_h_ -#define load_by_name_h_ - -#include -#include "plugin.h" -#include "barlib.h" - -bool -load_plugin_by_name(Plugin *p, const char *name); - -bool -load_barlib_by_name(Barlib *b, const char *name); - -#endif diff --git a/luastatus/log.c b/luastatus/log.c deleted file mode 100644 index 474776b..0000000 --- a/luastatus/log.c +++ /dev/null @@ -1,71 +0,0 @@ -#include "log.h" - -#include -#include -#include -#include "libls/sprintf_utils.h" -#include "libls/compdep.h" - -LuastatusLogLevel global_loglevel = DEFAULT_LOGLEVEL; - -#define EXPAND_LOGLEVELS() \ - X(LUASTATUS_FATAL, "fatal") \ - X(LUASTATUS_ERR, "error") \ - X(LUASTATUS_WARN, "warning") \ - X(LUASTATUS_INFO, "info") \ - X(LUASTATUS_VERBOSE, "verbose") \ - X(LUASTATUS_DEBUG, "debug") \ - X(LUASTATUS_TRACE, "trace") - -const char * -loglevel_tostr(LuastatusLogLevel level) -{ - switch (level) { -#define X(Level_, Name_) case Level_: return Name_; - EXPAND_LOGLEVELS() -#undef X - case LUASTATUS_LOGLEVEL_LAST: - break; - } - LS_UNREACHABLE(); -} - -LuastatusLogLevel -loglevel_fromstr(const char *s) -{ -#define X(Level_, Name_) \ - if (strcmp(s, Name_) == 0) { \ - return Level_; \ - } - EXPAND_LOGLEVELS() -#undef X - return LUASTATUS_LOGLEVEL_LAST; -} - -#undef EXPAND_LOGLEVELS - -void -common_logf(LuastatusLogLevel level, const char *subsystem, const char *fmt, va_list vl) -{ - if (level > global_loglevel) { - return; - } - - char buf[1024]; - ls_svsnprintf(buf, sizeof(buf), fmt, vl); - - if (subsystem) { - fprintf(stderr, "luastatus: (%s) %s: %s\n", subsystem, loglevel_tostr(level), buf); - } else { - fprintf(stderr, "luastatus: %s: %s\n", loglevel_tostr(level), buf); - } -} - -void -internal_logf(LuastatusLogLevel level, const char *fmt, ...) -{ - va_list vl; - va_start(vl, fmt); - common_logf(level, NULL, fmt, vl); - va_end(vl); -} diff --git a/luastatus/log.h b/luastatus/log.h deleted file mode 100644 index 892c1ec..0000000 --- a/luastatus/log.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef log_h_ -#define log_h_ - -#include -#include -#include "include/loglevel.h" -#include "libls/compdep.h" - -enum { - DEFAULT_LOGLEVEL = LUASTATUS_INFO, -}; - -extern LuastatusLogLevel global_loglevel; - -const char * -loglevel_tostr(LuastatusLogLevel level); - -LuastatusLogLevel -loglevel_fromstr(const char *s); - -void -common_logf(LuastatusLogLevel level, const char *subsystem, const char *fmt, va_list vl); - -LS_ATTR_PRINTF(2, 3) -void -internal_logf(LuastatusLogLevel level, const char *fmt, ...); - -#endif diff --git a/luastatus/lua_libs.c b/luastatus/lua_libs.c deleted file mode 100644 index 5912959..0000000 --- a/luastatus/lua_libs.c +++ /dev/null @@ -1,286 +0,0 @@ -#include "lua_libs.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "libls/lua_utils.h" -#include "libls/getenv_r.h" -#include "libls/alloc_utils.h" -#include "libls/vector.h" -#include "libls/string.h" -#include "libls/io_utils.h" -#include "libls/sprintf_utils.h" -#include "libls/errno_utils.h" -#include "libls/osdep.h" -#include "libls/io_utils.h" -#include "include/loglevel.h" -#include "log.h" -#include "config.h" -#include "barlib.h" -#include "widget.h" - -extern char **environ; - -static -int -l_os_execute(lua_State *L) -{ - return luaL_error(L, "os.execute isn't thread-safe; use luastatus.rc or luastatus.spawn " - "instead"); -} - -static -int -l_os_exit(lua_State *L) -{ - return luaL_error(L, "os.exit isn't thread-safe; don't use it"); -} - -static -int -l_os_setlocale(lua_State *L) -{ - return luaL_error(L, "os.setlocale would affect all threads and probably break something; " - "don't use it"); -} - -static -int -l_os_getenv(lua_State *L) -{ - const char *r = ls_getenv_r(luaL_checkstring(L, 1), environ); - if (r) { - lua_pushstring(L, r); - } else { - lua_pushnil(L); - } - return 1; -} - -static -int -get_rc(int status) -{ - if (WIFEXITED(status)) { - return WEXITSTATUS(status); - } else if (WIFSIGNALED(status)) { - return 128 + WTERMSIG(status); - } else { - return 128; - } -} - -static -size_t -dollar_len(const char *s, size_t ns) -{ - while (ns && s[ns - 1] == '\n') { - --ns; - } - return ns; -} - -static -int -lspawn_func(lua_State *L, const char *funcname, bool return_output, bool return_exitcode) -{ - int init_top = lua_gettop(L); - LS_VECTOR_OF(char*) argv = LS_VECTOR_NEW_RESERVE(char*, 8); - LSString output_buf = LS_VECTOR_NEW(); - int pipe_fd = -1; - pid_t pid = -1; - bool luaerr = false; - char luaerr_msg[1024]; - -#define USAGE_ERR(...) \ - do { \ - ls_ssnprintf(luaerr_msg, sizeof(luaerr_msg), __VA_ARGS__); \ - luaerr = true; \ - goto cleanup; \ - } while (0) - -#define SPAWN_ERR() \ - do { \ - lua_settop(L, init_top); \ - if (return_output) { \ - lua_pushstring(L, ""); \ - } \ - if (return_exitcode) { \ - lua_pushinteger(L, 127); \ - } \ - goto cleanup; \ - } while (0) - - if (!return_output && !return_exitcode) { - // Yes, we invoke shell to spawn a process without waiting for its - // termination. Doing otherwise would hilariously increase the - // complexity, or leak zombie processes (execvp() is not async-safe, so - // forking twice means reimplementing its job of searching for the - // binary over $PATH). - LS_VECTOR_PUSH(argv, ls_xstrdup(LUASTATUS_POSIX_SH_PATH)); - LS_VECTOR_PUSH(argv, ls_xstrdup("-c")); - LS_VECTOR_PUSH(argv, ls_xstrdup("exec \"$@\"&")); - LS_VECTOR_PUSH(argv, ls_xstrdup("")); // this will be $0 - } - - if (init_top != 1) { - USAGE_ERR("%s: expected exactly one argument, found %d", funcname, init_top); - } - if (!lua_istable(L, 1)) { - USAGE_ERR("%s: expected table, found %s", funcname, luaL_typename(L, 1)); - } - size_t nrealargs = 0; - LS_LUA_TRAVERSE(L, 1) { - if (!lua_isnumber(L, LS_LUA_TRAVERSE_KEY)) { - USAGE_ERR("%s: table key: expected number, found %s", funcname, luaL_typename(L, -2)); - } - if (!lua_isstring(L, LS_LUA_TRAVERSE_VALUE)) { - USAGE_ERR("%s: table element: expected string, found %s", funcname, - luaL_typename(L, LS_LUA_TRAVERSE_VALUE)); - } - LS_VECTOR_PUSH(argv, ls_xstrdup(lua_tostring(L, LS_LUA_TRAVERSE_VALUE))); - ++nrealargs; - } - if (!nrealargs) { - USAGE_ERR("%s: empty table", funcname); - } - - LS_VECTOR_PUSH(argv, NULL); - - if ((pid = ls_spawnp_pipe(argv.data[0], return_output ? &pipe_fd : NULL, argv.data)) < 0) { - pipe_fd = -1; - LS_WITH_ERRSTR(s, errno, - internal_logf(LUASTATUS_ERR, "%s: can't spawn child process: %s", funcname, s); - ); - SPAWN_ERR(); - } - - if (return_output) { - LS_VECTOR_RESERVE(output_buf, 1024); - if (ls_full_read_append(pipe_fd, &output_buf.data, &output_buf.size, - &output_buf.capacity) < 0) - { - LS_WITH_ERRSTR(s, errno, - internal_logf(LUASTATUS_ERR, "%s: read: %s", funcname, s); - ); - SPAWN_ERR(); - } - lua_pushlstring(L, output_buf.data, dollar_len(output_buf.data, output_buf.size)); - } - - if (return_exitcode) { - int status; - int r = waitpid(pid, &status, 0); - pid = -1; - if (r < 0) { - LS_WITH_ERRSTR(s, errno, - internal_logf(LUASTATUS_ERR, "%s: waitpid: %s", funcname, s); - ); - SPAWN_ERR(); - } - lua_pushinteger(L, get_rc(status)); - } - -cleanup: - for (size_t i = 0; i < argv.size; ++i) { - free(argv.data[i]); - } - LS_VECTOR_FREE(argv); - LS_VECTOR_FREE(output_buf); - ls_close(pipe_fd); - waitpid(pid, NULL, 0); - if (luaerr) { - return luaL_error(L, "%s", luaerr_msg); - } else { - return lua_gettop(L) - init_top; - } -} - -static -int -l_dollar(lua_State *L) -{ - return lspawn_func(L, "luastatus.dollar", true, true); -} - -static -int -l_rc(lua_State *L) -{ - return lspawn_func(L, "luastatus.rc", false, true); -} - -static -int -l_spawn(lua_State *L) -{ - return lspawn_func(L, "luastatus.spawn", false, false); -} - -void -lualibs_inject(lua_State *L) -{ - ls_lua_pushglobaltable(L); // L: _G - ls_lua_rawgetf(L, "os"); // L: _G os - - lua_pushcfunction(L, l_os_execute); // L: _G os l_os_execute - ls_lua_rawsetf(L, "execute"); // L: _G os - - lua_pushcfunction(L, l_os_exit); // L: _G os l_os_exit - ls_lua_rawsetf(L, "exit"); // L: _G os - - lua_pushcfunction(L, l_os_getenv); // L: _G os l_os_getenv - ls_lua_rawsetf(L, "getenv"); // L: _G os - - lua_pushcfunction(L, l_os_setlocale); // L: _G os l_os_setlocale - ls_lua_rawsetf(L, "setlocale"); // L: _G os - - lua_pop(L, 1); // L: _G - lua_newtable(L); // L: _G table - - lua_pushcfunction(L, l_dollar); // L: _G table l_dollar - ls_lua_rawsetf(L, "dollar"); // L: _G table - - lua_pushcfunction(L, l_rc); // L: _G table l_rc - ls_lua_rawsetf(L, "rc"); // L: _G table - - lua_pushcfunction(L, l_spawn); // L: table l_spawn - ls_lua_rawsetf(L, "spawn"); // L: _G table - - ls_lua_rawsetf(L, "luastatus"); // L: _G - lua_pop(L, 1); // L: - -} - -void -lualibs_register_funcs(Widget *w, Barlib *barlib) -{ - lua_State *L = w->L; // L: - - ls_lua_pushglobaltable(L); // L: _G - ls_lua_rawgetf(L, "luastatus"); // L: _G luastatus - if (!lua_istable(L, -1)) { - internal_logf(LUASTATUS_WARN, - "widget '%s': 'luastatus' is not a table anymore, will not register " - "plugin/barlib functions", w->filename); - goto done; - } - if (w->plugin.iface.register_funcs) { - lua_newtable(L); // L: _G luastatus table - w->plugin.iface.register_funcs(&w->data, L); - assert(lua_gettop(L) == 3); // L: _G luastatus table - ls_lua_rawsetf(L, "plugin"); // L: _G luastatus - } - if (barlib->iface.register_funcs) { - lua_newtable(L); // L: _G luastatus table - barlib->iface.register_funcs(&barlib->data, L); - assert(lua_gettop(L) == 3); // L: _G luastatus table - ls_lua_rawsetf(L, "barlib"); // L: _G luastatus - } -done: - lua_pop(L, 2); // L: - -} diff --git a/luastatus/lua_libs.h b/luastatus/lua_libs.h deleted file mode 100644 index f44ce10..0000000 --- a/luastatus/lua_libs.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef lua_libs_h_ -#define lua_libs_h_ - -#include -#include "widget.h" -#include "barlib.h" - -void -lualibs_inject(lua_State *L); - -void -lualibs_register_funcs(Widget *w, Barlib *barlib); - -#endif diff --git a/luastatus/luastatus.c b/luastatus/luastatus.c index ecd0893..d146d75 100644 --- a/luastatus/luastatus.c +++ b/luastatus/luastatus.c @@ -1,174 +1,1016 @@ #include #include #include -#include +#include #include +#include #include +#include #include -#include -#include #include #include #include #include -#include "include/loglevel.h" +#include +#include +#include + #include "include/barlib_data.h" #include "include/plugin_data.h" -#include "libls/sprintf_utils.h" -#include "libls/compdep.h" +#include "include/common.h" + #include "libls/alloc_utils.h" +#include "libls/compdep.h" #include "libls/errno_utils.h" +#include "libls/getenv_r.h" +#include "libls/lua_utils.h" +#include "libls/sprintf_utils.h" +#include "libls/cstring_utils.h" #include "libls/vector.h" -#include "barlib.h" -#include "widget.h" -#include "log.h" -#include "pth_check.h" -#include "check_lua_call.h" -#include "load_by_name.h" -#include "taints.h" -#include "lua_libs.h" -#include "config.h" -#include "version.h" - -Barlib barlib; -Widget *widgets; -size_t nwidgets; - -#define LOCK_B() PTH_CHECK(pthread_mutex_lock(&barlib.set_mtx)) -#define UNLOCK_B() PTH_CHECK(pthread_mutex_unlock(&barlib.set_mtx)) -#define LOCK_L(W_) PTH_CHECK(pthread_mutex_lock(&(W_)->lua_mtx)) -#define UNLOCK_L(W_) PTH_CHECK(pthread_mutex_unlock(&(W_)->lua_mtx)) - -#define WIDGET_INDEX(W_) ((W_) - widgets) - -LS_ATTR_NORETURN + +#include "config.generated.h" + +// Assert that a /pthread_*/ function call was successful. +#define PTH_ASSERT(Expr_) pth_assert_impl(Expr_, #Expr_, __FILE__, __LINE__) + +// Logging macros. +#define FATALF(...) sayf(LUASTATUS_LOG_FATAL, __VA_ARGS__) +#define ERRF(...) sayf(LUASTATUS_LOG_ERR, __VA_ARGS__) +#define WARNF(...) sayf(LUASTATUS_LOG_WARN, __VA_ARGS__) +#define INFOF(...) sayf(LUASTATUS_LOG_INFO, __VA_ARGS__) +#define VERBOSEF(...) sayf(LUASTATUS_LOG_VERBOSE, __VA_ARGS__) +#define DEBUGF(...) sayf(LUASTATUS_LOG_DEBUG, __VA_ARGS__) +#define TRACEF(...) sayf(LUASTATUS_LOG_TRACE, __VA_ARGS__) + +// These ones are implemented as macros so that /pth_assert_impl/ calls receive the correct line +// they are called at. +#define LOCK_B() PTH_ASSERT(pthread_mutex_lock(&barlib.set_mtx)) +#define UNLOCK_B() PTH_ASSERT(pthread_mutex_unlock(&barlib.set_mtx)) + +#define LOCK_L(W_) PTH_ASSERT(pthread_mutex_lock(&(W_)->L_mtx)) +#define UNLOCK_L(W_) PTH_ASSERT(pthread_mutex_unlock(&(W_)->L_mtx)) + +#define LOCK_E(W_) PTH_ASSERT(pthread_mutex_lock(widget_event_L_mtx(W_))) +#define UNLOCK_E(W_) PTH_ASSERT(pthread_mutex_unlock(widget_event_L_mtx(W_))) + +typedef struct { + // The interface loaded from this plugin's shared library file. + LuastatusPluginIface_v1 iface; + + // An allocated zero-terminated string with plugin name, as specified in widget's + // /widget.plugin/ string. + char *name; + + // A handle returned from /dlopen/ for this plugin's shared library file. + void *dlhandle; +} Plugin; + +// If any step of widget's initialization fails, the widget is not removed from the /widgets/ +// buffer, but is, instead, unloaded and becomes *stillborn*; barlib's /set_error()/ is called on +// such a widget, and a separate "runner" thread simply does not get spawned for it. +// +// However, barlib's /event_watcher()/ may still report events on such a widget. +// Possible solutions to this are: +// 1. Allow the event watcher's /call_begin/ function (/ew_call_begin/) to return /NULL/ to tell +// the event watcher that we are not interested in this event, and that it should be skipped. +// Complicates the API and event watcher's logic. +// 2. Initialize each stillborn widget's /L/ with an empty Lua state, and provide it to the event +// watcher each time it generates an event on this widget. +// 3. If there is at least one stillborn widget, initialize the *separate state* (see below), and +// provide /sepstate.L/ to the event watcher. A slight benefit over the second one is that only +// one extra initialized Lua state is required. +// +// We choose the third one, and thus require stillborn widgets to have: +// 1. /sepstate_event/ field set to /true/ so that /ew_call_begin/ and /ew_call_end/ functions +// would operate on /sepstate/'s Lua state and mutex guarding it, instead of widget's ones +// (which are not initialized in the case of a stillborn widget); +// 2. /lref_event/ field set to /LUA_REFNIL/ so that /ew_call_end/ function would simply discard +// the object generated by barlib's event watcher. + +typedef struct { + // Normal: an initialized plugin. + // Stillborn: undefined. + Plugin plugin; + + // Normal: /plugin/'s data for this widget. + // Stillborn: undefined. + LuastatusPluginData_v1 data; + + // Normal: this widget's Lua interpreter instance. + // Stillborn: /NULL/ (used to check if the widget is stillborn). + lua_State *L; + + // Normal: a mutex guarding /L/. + // Stillborn: undefined. + pthread_mutex_t L_mtx; + + // Normal: Lua reference (in /L/'s registry) to this widget's /widget.cb/ function. + // Stillborn: undefined. + int lref_cb; + + // Normal: + // if /sepstate_event/ is false, Lua reference (in /L/'s registry) to this widget's + // /widget.event/ function (is /LUA_REFNIL/ if the latter is /nil/); + // if /sepstate_event/ is true, Lua reference (in /sepstate.L/'s registry) to the compiled + // /widget.event/ function of this widget. + // Stillborn: /LUA_REFNIL/. + int lref_event; + + // Normal: whether /lref_event/ is a reference in /sepstate.L/'s registry, as opposed to + // /L/'s one. + // Stillborn: /true/. + bool sepstate_event; + + // Normal: an allocated zero-terminated string with widget's file name. + // Stillborn: undefined. + char *filename; +} Widget; + +// Current log level. May only be changed once, when parsing command-line arguments. +static int loglevel = LUASTATUS_LOG_INFO; + +static struct { + // The interface loaded from this barlib's shared library file. + LuastatusBarlibIface_v1 iface; + + // This barlib's data. + LuastatusBarlibData_v1 data; + + // A mutex guarding calls to /iface.set()/ and /iface.set_error()/. + pthread_mutex_t set_mtx; + + // A handle required from /dlopen/ for this barlib's shared library file. + void *dlhandle; +} barlib; + +// These two are initially (explicitly) set to /NULL/ and /0/ correspondingly, so that the +// destruction function (/widgets_destroy()/) can be invoked at any time (that is, before or after +// their initialization with actual values, not in the middle of it). +// +// This requires a little care with initialization; /widgets_init()/ should be used for it. +static Widget *widgets = NULL; +static size_t nwidgets = 0; + +// This "separate state" thing serves two purposes: +// 1. If a widget has a /widget.event/ variable of string type, it is compiled in /sepstate.L/ Lua +// interpreter instance as a function; a reference to it is stored in that widget's +// /lref_event/ field; and the /sepstate_event/ field of that widget is set to /true/. +// 2. As has been already described above, /sepstate.L/ is provided to barlib's /event_watcher()/ +// each time it attempts to generate an event on a stillborn widget; the event object is then +// simply discarded. +static struct { + // Separate state's Lua interpreter instance. Initially is (explicitly) set to /NULL/, which + // indicates that the separate state was not initialized yet. + lua_State *L; + + // A mutex guarding /L/. + pthread_mutex_t L_mtx; +} sepstate = {NULL}; + +// See DOCS/design/map_get.md +// +// Basically, it is a string-to-pointer mapping used by plugins and barlibs for synchronization. +// +// We use the search tree interface from //, which, in any sane implementation, should be +// of logarithmic time complexity for search, insert and delete operations. + +// A structure we store in the nodes of the tree. +typedef struct { + // The pointer value of this entry. + void *value; + + // A flexible array member containing the zero-terminated key string of this entry. + char key[]; +} MapEntry; + +// An (opaque) pointer to the root node of the search tree. A /NULL/ represents an empty tree. +static void *map_root = NULL; + +// An implementation part for the /PTH_ASSERT/ macro. +static void -fatal_error_reported(void) +pth_assert_impl(int ret, const char *expr, const char *file, int line) { - _exit(EXIT_FAILURE); + if (ret) { + LS_WITH_ERRSTR(s, ret, + fprintf(stderr, "PTH_ASSERT(%s) failed at %s:%d\nReason: %s\n", expr, file, line, s); + ); + abort(); + } +} + +// This function exists because /dlerror()/ may return /NULL/ even if /dlsym()/ returned /NULL/. +static inline +const char * +safe_dlerror(void) +{ + const char *err = dlerror(); + return err ? err : "(no error, but the symbol is NULL)"; +} + +#define XPAND_LOGLEVELS() \ + X(LUASTATUS_LOG_FATAL, "fatal") \ + X(LUASTATUS_LOG_ERR, "error") \ + X(LUASTATUS_LOG_WARN, "warning") \ + X(LUASTATUS_LOG_INFO, "info") \ + X(LUASTATUS_LOG_VERBOSE, "verbose") \ + X(LUASTATUS_LOG_DEBUG, "debug") \ + X(LUASTATUS_LOG_TRACE, "trace") \ + /* end of the list */ + +// Returns a name of the given log level. If /level/ is not a correct log level, the behaviour is +// undefined. +static +const char * +loglevel_tostr(int level) +{ + switch (level) { +#define X(Level_, Name_) case Level_: return Name_; + XPAND_LOGLEVELS() +#undef X + } + LS_UNREACHABLE(); +} + +// Returns a log level number by its name /str/, or returns /LUASTATUS_LOG_LAST/ if no such log +// level was found. +static +int +loglevel_fromstr(const char *str) +{ +#define X(Level_, Name_) \ + if (strcmp(str, Name_) == 0) { \ + return Level_; \ + } + XPAND_LOGLEVELS() +#undef X + return LUASTATUS_LOG_LAST; +} +#undef XPAND_LOGLEVELS + +// The generic logging function: generates a log message with level /level/ from a given /subsystem/ +// (either a plugin or a barlib name; or /NULL/, which means the message is from the luastatus +// program itself) using the format string /fmt/ and variable arguments supplied as /vl/, as if with +// /vsnprintf(, fmt, vl)/. +static +void +common_vsayf(int level, const char *subsystem, const char *fmt, va_list vl) +{ + if (level > loglevel) { + return; + } + + char buf[1024]; + if (vsnprintf(buf, sizeof(buf), fmt, vl) < 0) { + ls_strlcpy(buf, "(vsnprintf failed)", sizeof(buf)); + } + + if (subsystem) { + fprintf(stderr, "luastatus: (%s) %s: %s\n", subsystem, loglevel_tostr(level), buf); + } else { + fprintf(stderr, "luastatus: %s: %s\n", loglevel_tostr(level), buf); + } +} + +// The "internal" logging function: generates a log message from the luastatus program itself with +// level /level/ using the format string /fmt/ and the variable arguments supplied as /.../, as if +// with /vsnprintf(, fmt, <... variable arguments>)/. +static inline LS_ATTR_PRINTF(2, 3) +void +sayf(int level, const char *fmt, ...) +{ + va_list vl; + va_start(vl, fmt); + common_vsayf(level, NULL, fmt, vl); + va_end(vl); } +// An "external" logging function: generates a log message from the subsystem denoted by /userdata/ +// with level /level/ using the format string /fmt/ and the variable arguments supplied as /.../, as +// if with /vsnprintf(, fmt, <... variable arguments>/. +static void -external_logf(void *userdata, LuastatusLogLevel level, const char *fmt, ...) +external_sayf(void *userdata, int level, const char *fmt, ...) { va_list vl; va_start(vl, fmt); if (userdata) { Widget *w = userdata; char who[1024]; - ls_ssnprintf(who, sizeof(who), "%s@%s", w->plugin.name, w->filename); - common_logf(level, who, fmt, vl); + snprintf(who, sizeof(who), "%s@%s", w->plugin.name, w->filename); + common_vsayf(level, who, fmt, vl); } else { - common_logf(level, "barlib", fmt, vl); + common_vsayf(level, "barlib", fmt, vl); } va_end(vl); } +// Compares two map entries by key. +static +int +map_entry_cmp(const void *a, const void *b) +{ + return strcmp(((const MapEntry *) a)->key, ((const MapEntry *) b)->key); +} + +// Returns a pointer to the value of the entry with key /key/. +static +void ** +map_get(void *userdata, const char *key) +{ + TRACEF("map_get(userdata=%p, key='%s')", userdata, key); + + const size_t nkey = strlen(key); + MapEntry *alloc = ls_xmalloc(sizeof(MapEntry) + nkey + 1, 1); + alloc->value = NULL; + memcpy(alloc->key, key, nkey + 1); + + void *node = tsearch(alloc, &map_root, map_entry_cmp); + if (!node) { + ls_oom(); + } + MapEntry *in_tree = *(MapEntry **) node; + if (in_tree != alloc) { + free(alloc); + } + return &in_tree->value; +} + +// Destroys the map. +static void -set_error_unlocked(size_t widget_idx) +map_destroy(void) +{ + while (map_root) { + MapEntry *ent = *(MapEntry **) map_root; + tdelete(ent, &map_root, map_entry_cmp); + free(ent); + } +} + +// Loads /barlib/ from a file /filename/ and initializes with options /opts/ and the number of +// widgets /nwidgets/ (a global variable). +static +bool +barlib_init(const char *filename, const char *const *opts) +{ + DEBUGF("initializing barlib from file '%s', opts=[", filename); + for (const char *const *s = opts; *s; ++s) { + DEBUGF(" '%s',", *s); + } + DEBUGF("]"); + + barlib.dlhandle = NULL; // this is an indicator whether or not to call /dlclose()/ on error. + + (void) dlerror(); // clear last error + if (!(barlib.dlhandle = dlopen(filename, RTLD_NOW | RTLD_LOCAL))) { + ERRF("dlopen: %s: %s", filename, safe_dlerror()); + goto error; + } + int *p_lua_ver = dlsym(barlib.dlhandle, "LUASTATUS_BARLIB_LUA_VERSION_NUM"); + if (!p_lua_ver) { + ERRF("dlsym: LUASTATUS_BARLIB_LUA_VERSION_NUM: %s", safe_dlerror()); + goto error; + } + if (*p_lua_ver != LUA_VERSION_NUM) { + ERRF("barlib '%s' was compiled with LUA_VERSION_NUM=%d and luastatus with %d", + filename, *p_lua_ver, LUA_VERSION_NUM); + goto error; + } + LuastatusBarlibIface_v1 *p_iface = dlsym(barlib.dlhandle, "luastatus_barlib_iface_v1"); + if (!p_iface) { + ERRF("dlsym: luastatus_barlib_iface_v1: %s", safe_dlerror()); + goto error; + } + barlib.iface = *p_iface; + barlib.data = (LuastatusBarlibData_v1) { + .userdata = NULL, + .sayf = external_sayf, + .map_get = map_get, + }; + + if (barlib.iface.init(&barlib.data, opts, nwidgets) == LUASTATUS_ERR) { + ERRF("barlib's init() failed"); + goto error; + } + + PTH_ASSERT(pthread_mutex_init(&barlib.set_mtx, NULL)); + + DEBUGF("barlib successfully initialized"); + return true; + +error: + if (barlib.dlhandle) { + dlclose(barlib.dlhandle); + } + return false; +} + +// The result is same to calling /barlib_init(, opts)/, where // is the file +// name guessed for name /name/. +static +bool +barlib_init_by_name(const char *name, const char *const *opts) +{ + if ((strchr(name, '/'))) { + return barlib_init(name, opts); + } else { + char *filename = ls_xasprintf("%s/%s.so", LUASTATUS_BARLIBS_DIR, name); + bool r = barlib_init(filename, opts); + free(filename); + return r; + } +} + +// Destroys /barlib/. +static +void +barlib_destroy(void) +{ + barlib.iface.destroy(&barlib.data); + dlclose(barlib.dlhandle); + PTH_ASSERT(pthread_mutex_destroy(&barlib.set_mtx)); +} + +// Loads a plugin /p/ with name /name/ from a file /filename/. +static +bool +plugin_load(Plugin *p, const char *filename, const char *name) +{ + DEBUGF("loading plugin from file '%s'", filename); + + p->dlhandle = NULL; // this is an indicator whether or not to call /dlclose()/ on error. + + (void) dlerror(); // clear last error + if (!(p->dlhandle = dlopen(filename, RTLD_NOW | RTLD_LOCAL))) { + ERRF("dlopen: %s: %s", filename, safe_dlerror()); + goto error; + } + int *p_lua_ver = dlsym(p->dlhandle, "LUASTATUS_PLUGIN_LUA_VERSION_NUM"); + if (!p_lua_ver) { + ERRF("dlsym: LUASTATUS_PLUGIN_LUA_VERSION_NUM: %s", safe_dlerror()); + goto error; + } + if (*p_lua_ver != LUA_VERSION_NUM) { + ERRF("plugin '%s' was compiled with LUA_VERSION_NUM=%d and luastatus with %d", + filename, *p_lua_ver, LUA_VERSION_NUM); + goto error; + } + LuastatusPluginIface_v1 *p_iface = dlsym(p->dlhandle, "luastatus_plugin_iface_v1"); + if (!p_iface) { + ERRF("dlsym: luastatus_plugin_iface_v1: %s", safe_dlerror()); + goto error; + } + p->iface = *p_iface; + + p->name = ls_xstrdup(name); + DEBUGF("plugin successfully loaded"); + return true; + +error: + if (p->dlhandle) { + dlclose(p->dlhandle); + } + return false; +} + +// The result is same to calling /plugin_load(p, , name)/, where // is the file +// name guessed for name /name/. +static +bool +plugin_load_by_name(Plugin *p, const char *name) { - switch (barlib.iface.set_error(&barlib.data, widget_idx)) { - case LUASTATUS_BARLIB_SET_ERROR_RESULT_OK: + if ((strchr(name, '/'))) { + return plugin_load(p, name, name); + } else { + char *filename = ls_xasprintf("%s/%s.so", LUASTATUS_PLUGINS_DIR, name); + bool r = plugin_load(p, filename, name); + free(filename); + return r; + } +} + +// Unloads a plugin /p/. +static +void +plugin_unload(Plugin *p) +{ + free(p->name); + dlclose(p->dlhandle); +} + +// Checks a /lua_*/ call that returns a /LUA_*/ error code, performed on a Lua interpreter instance +// /L/. /ret/ is the return value of the call. +// +// If /ret/ is /LUA_OK/, returns /true/; otherwise, logs the error and returns /false/. +static +bool +check_lua_call(lua_State *L, int ret) +{ + const char *prefix; + switch (ret) { + case LUA_OK: + return true; + case LUA_ERRRUN: + case LUA_ERRSYNTAX: + case LUA_ERRMEM: // Lua itself produces a meaningful error message in this case + case LUA_ERRFILE: // ditto + prefix = "(lua) "; break; - case LUASTATUS_BARLIB_SET_ERROR_RESULT_FATAL_ERR: - internal_logf(LUASTATUS_FATAL, "barlib's set_error() reported fatal error"); - fatal_error_reported(); + case LUA_ERRERR: + prefix = "(lua) error while running error handler: "; + break; +#ifdef LUA_ERRGCMM + // first introduced in Lua 5.2 + case LUA_ERRGCMM: + prefix = "(lua) error while running __gc metamethod: "; break; +#endif + default: + prefix = "unknown Lua error code (please report!), message is: "; + } + const char *msg = lua_tostring(L, -1); + if (!msg) { + msg = "(error object can't be converted to string)"; + } + ERRF("%s%s", prefix, msg); + lua_pop(L, 1); + return false; +} + +// Replacement for Lua's /os.exit()/: a simple /exit()/ used by Lua is not thread-safe in Linux. +static +int +l_os_exit(lua_State *L) +{ + int code = luaL_optinteger(L, 1, /*default value*/ EXIT_SUCCESS); + fflush(NULL); + _exit(code); +} + +// Replacement for Lua's /os.getenv()/: a simple /getenv()/ used by Lua is not guaranteed by POSIX +// to be thread-safe. +static +int +l_os_getenv(lua_State *L) +{ + const char *r = ls_getenv_r(luaL_checkstring(L, 1)); + if (r) { + lua_pushstring(L, r); + } else { + lua_pushnil(L); + } + return 1; +} + +// Replacement for Lua's /os.setlocale()/: this thing is inherently thread-unsafe. +static +int +l_os_setlocale(lua_State *L) +{ + lua_pushnil(L); + return 1; +} + +// Replaces some of the functions in /L/'s Lua libraries with their thread-safe counterparts. +void +inject_libs(lua_State *L) +{ +#define REG(Name_, Ptr_) \ + do { \ + /* L: table */ \ + lua_pushcfunction(L, Ptr_); /* L: table ptr */ \ + ls_lua_rawsetf(L, Name_); /* L: table */ \ + } while (0) + + lua_getglobal(L, "os"); // L: os + REG("exit", l_os_exit); + REG("getenv", l_os_getenv); + REG("setlocale", l_os_setlocale); + lua_pop(L, 1); // L: - +#undef REG +} + +// Registers /barlib/'s function at /L/. +// If /w/ is not /NULL/, registers /w/'s plugin's functions at /L/. +static +void +register_funcs(lua_State *L, Widget *w) +{ + lua_newtable(L); // L: table + if (barlib.iface.register_funcs) { + lua_newtable(L); // L: table table + barlib.iface.register_funcs(&barlib.data, L); // L: table table + assert(lua_gettop(L) == 2); + ls_lua_rawsetf(L, "barlib"); // L: table + } + if (w && w->plugin.iface.register_funcs) { + lua_newtable(L); // L: table table + w->plugin.iface.register_funcs(&w->data, L); // L: table table + assert(lua_gettop(L) == 2); + ls_lua_rawsetf(L, "plugin"); // L: table + } + lua_setglobal(L, "luastatus"); // L: - +} + +// Initializes, if not already initialized, the separate state. +static +void +sepstate_maybe_init(void) +{ + if (sepstate.L) { + // already initialized + return; + } + if (!(sepstate.L = luaL_newstate())) { + ls_oom(); + } + luaL_openlibs(sepstate.L); + inject_libs(sepstate.L); + PTH_ASSERT(pthread_mutex_init(&sepstate.L_mtx, NULL)); +} + +// Destroys, if needed, the separate state. +static +void +sepstate_maybe_destroy(void) +{ + if (!sepstate.L) { + // hasn't been initialized + return; + } + lua_close(sepstate.L); + PTH_ASSERT(pthread_mutex_destroy(&sepstate.L_mtx)); +} + +// Inspects the 'plugin' field of /w/'s /widget/ table, which is assumed to be on top of /w.L/'s +// stack (which is unchanged on return). +static +bool +widget_init_inspect_plugin(Widget *w) +{ + lua_State *L = w->L; + // L: widget + ls_lua_rawgetf(L, "plugin"); // L: widget plugin + if (!lua_isstring(L, -1)) { + ERRF("'widget.plugin': expected string, found %s", luaL_typename(L, -1)); + return false; + } + if (!plugin_load_by_name(&w->plugin, lua_tostring(L, -1))) { + ERRF("cannot load plugin '%s'", lua_tostring(L, -1)); + return false; + } + lua_pop(L, 1); // L: widget + return true; +} + +// As /widget_init_inspect_plugin/, but inspects the 'cb' field instead. +static +bool +widget_init_inspect_cb(Widget *w) +{ + lua_State *L = w->L; + // L: widget + ls_lua_rawgetf(L, "cb"); // L: widget plugin + if (!lua_isfunction(L, -1)) { + ERRF("'widget.cb': expected function, found %s", luaL_typename(L, -1)); + return false; + } + w->lref_cb = luaL_ref(L, LUA_REGISTRYINDEX); // L: widget + return true; +} + +// As /widget_init_inspect_plugin/, but inspects the 'event' field instead. +static +bool +widget_init_inspect_event(Widget *w, const char *filename) +{ + lua_State *L = w->L; + // L: widget + ls_lua_rawgetf(L, "event"); // L: widget event + switch (lua_type(L, -1)) { + case LUA_TNIL: + case LUA_TFUNCTION: + w->lref_event = luaL_ref(L, LUA_REGISTRYINDEX); // L: widget + w->sepstate_event = false; + return true; + case LUA_TSTRING: + { + sepstate_maybe_init(); + size_t ncode; + const char *code = lua_tolstring(w->L, -1, &ncode); + char *chunkname = ls_xasprintf("widget.event of %s", filename); + bool r = check_lua_call(sepstate.L, luaL_loadbuffer(sepstate.L, + code, ncode, chunkname)); + free(chunkname); + if (!r) { + return false; + } + // sepstate.L: chunk + w->lref_event = luaL_ref(sepstate.L, LUA_REGISTRYINDEX); // sepstate.L: - + w->sepstate_event = true; + lua_pop(L, 1); // L: widget + return true; + } + default: + ERRF("'widget.event': expected function, nil, or string, found %s", luaL_typename(L, -1)); + return false; + } +} + +// Inspects the 'opts' field of /w/'s /widget/ table, which is assumed to be on top of /w.L/'s +// stack. +// Additionally, pushes the 'opts' field to /w.L/'s stack. +static +bool +widget_init_inspect_keep_opts(Widget *w) +{ + lua_State *L = w->L; + ls_lua_rawgetf(L, "opts"); // L: widget opts + switch (lua_type(L, -1)) { + case LUA_TTABLE: + return true; + case LUA_TNIL: + lua_pop(L, 1); // L: widget + lua_newtable(L); // L: widget table + return true; + default: + ERRF("'widget.opts': expected function or nil, found %s", luaL_typename(L, -1)); + return false; + } +} + +// Initializes widget /w/ from file /filename/. +static +bool +widget_init(Widget *w, const char *filename) +{ + DEBUGF("initializing widget '%s'", filename); + + lua_State *L = w->L = luaL_newstate(); + PTH_ASSERT(pthread_mutex_init(&w->L_mtx, NULL)); + w->filename = ls_xstrdup(filename); + bool plugin_loaded = false; + + if (!L) { + ls_oom(); + } + + luaL_openlibs(L); + // L: - + inject_libs(L); // L: - + + DEBUGF("running file '%s'", filename); + if (!check_lua_call(L, luaL_loadfile(L, filename)) || // L: chunk + !check_lua_call(L, lua_pcall(L, 0, 0, 0))) + { + goto error; + } + // L: - + + lua_getglobal(L, "widget"); // L: widget + if (!lua_istable(L, -1)) { + ERRF("'widget': expected table, found %s", luaL_typename(L, -1)); + goto error; + } + + if (!widget_init_inspect_plugin(w)) { + goto error; + } + plugin_loaded = true; + if (!widget_init_inspect_cb(w) || + !widget_init_inspect_event(w, filename) || + !widget_init_inspect_keep_opts(w)) + { + goto error; + } + // L: widget opts + + w->data = (LuastatusPluginData_v1) { + .userdata = w, + .sayf = external_sayf, + .map_get = map_get, + }; + + if (w->plugin.iface.init(&w->data, L) == LUASTATUS_ERR) { + ERRF("plugin's init() failed"); + goto error; + } + assert(lua_gettop(L) == 2); // L: widget opts + lua_pop(L, 2); // L: - + + DEBUGF("widget successfully initialized"); + return true; + +error: + lua_close(L); + PTH_ASSERT(pthread_mutex_destroy(&w->L_mtx)); + free(w->filename); + if (plugin_loaded) { + plugin_unload(&w->plugin); + } + return false; +} + +// Initializes widget /w/ and makes it stillborn. +static +void +widget_init_stillborn(Widget *w) +{ + sepstate_maybe_init(); + w->L = NULL; + w->lref_event = LUA_REFNIL; + w->sepstate_event = true; +} + +// Checks whether a widget /w/ is stillborn. +static inline +bool +widget_is_stillborn(Widget *w) +{ + return !w->L; +} + +// Returns the Lua interpreter instance for the /widget.event/ function of a widget /w/. +static inline +lua_State * +widget_event_lua_state(Widget *w) +{ + return w->sepstate_event ? sepstate.L : w->L; +} + +// Returns a pointer to the mutex guarding the Lua interpreter instance for the /widget.event/ +// function of a widget /w/. +static inline +pthread_mutex_t * +widget_event_L_mtx(Widget *w) +{ + return w->sepstate_event ? &sepstate.L_mtx : &w->L_mtx; +} + +// Returns the index of a widget /w/. +static inline +size_t +widget_index(Widget *w) +{ + return w - widgets; +} + +// Destroys a (possibly stillborn) widget /w/. +static +void +widget_destroy(Widget *w) +{ + if (!widget_is_stillborn(w)) { + w->plugin.iface.destroy(&w->data); + plugin_unload(&w->plugin); + lua_close(w->L); + PTH_ASSERT(pthread_mutex_destroy(&w->L_mtx)); + free(w->filename); + } +} + +// Initializes the /widgets/ and /nwidgets/ global variables from the given list of file names: +// sets /nwidgets/, allocates /widgets/, initialized all the widgets, and makes ones whose +// initialization failed stillborn. +static +void +widgets_init(char *const *filenames, size_t nfilenames) +{ + nwidgets = nfilenames; + widgets = LS_XNEW(Widget, nwidgets); + for (size_t i = 0; i < nwidgets; ++i) { + if (!widget_init(&widgets[i], filenames[i])) { + ERRF("cannot load widget '%s'", filenames[i]); + widget_init_stillborn(&widgets[i]); + } } } +// Destroys the widgets. +static +void +widgets_destroy(void) +{ + for (size_t i = 0; i < nwidgets; ++i) { + widget_destroy(&widgets[i]); + } + free(widgets); +} + +// Should be invoked whenever the barlib reports a fatal error. +static LS_ATTR_NORETURN +void +fatal_error_reported(void) +{ + fflush(NULL); + _exit(EXIT_FAILURE); +} + +// Invokes /barlib/'s /set_error()/ method on a widget with index /widget_idx/ and performs all the +// error-checking required. +// +// Does not do any locking/unlocking. +static +void +set_error_unlocked(size_t widget_idx) +{ + if (barlib.iface.set_error(&barlib.data, widget_idx) == LUASTATUS_ERR) { + FATALF("barlib's set_error() reported fatal error"); + fatal_error_reported(); + } +} + +static lua_State * plugin_call_begin(void *userdata) { - Widget *w = userdata; + TRACEF("plugin_call_begin(userdata=%p)", userdata); + Widget *w = userdata; LOCK_L(w); - assert(lua_gettop(w->L) == 0); // w->L: - - lua_rawgeti(w->L, LUA_REGISTRYINDEX, w->lua_ref_cb); // w->L: cb - return w->L; + lua_State *L = w->L; + assert(lua_gettop(L) == 0); // w->L: - + lua_rawgeti(L, LUA_REGISTRYINDEX, w->lref_cb); // w->L: cb + return L; } +static void plugin_call_end(void *userdata) { + TRACEF("plugin_call_end(userdata=%p)", userdata); + Widget *w = userdata; lua_State *L = w->L; - assert(lua_gettop(L) == 2); // L: cb data - + bool r = check_lua_call(L, lua_pcall(L, 1, 1, 0)); LOCK_B(); - - size_t widget_idx = WIDGET_INDEX(w); - if (!check_lua_call(L, lua_pcall(L, 1, 1, 0))) { - // L: - - set_error_unlocked(widget_idx); - } else { + size_t widget_idx = widget_index(w); + if (r) { // L: result switch (barlib.iface.set(&barlib.data, L, widget_idx)) { - case LUASTATUS_BARLIB_SET_RESULT_OK: + case LUASTATUS_OK: // L: result break; - case LUASTATUS_BARLIB_SET_RESULT_NONFATAL_ERR: + case LUASTATUS_NONFATAL_ERR: // L: ? set_error_unlocked(widget_idx); break; - case LUASTATUS_BARLIB_SET_RESULT_FATAL_ERR: + case LUASTATUS_ERR: // L: ? - internal_logf(LUASTATUS_FATAL, "barlib's set() reported fatal error"); + FATALF("barlib's set() reported fatal error"); fatal_error_reported(); break; } - lua_pop(L, lua_gettop(L)); // L: - + lua_settop(L, 0); // L: - + } else { + // L: - + set_error_unlocked(widget_idx); } - UNLOCK_B(); UNLOCK_L(w); } -void * -widget_thread(void *userdata) +static +void +plugin_call_cancel(void *userdata) { - Widget *w = userdata; - - w->plugin.iface.run(&w->data, plugin_call_begin, plugin_call_end); - internal_logf(LUASTATUS_WARN, "plugin's (%s) run() for widget '%s' has returned", - w->plugin.name, w->filename); - - LOCK_B(); - set_error_unlocked(WIDGET_INDEX(w)); - UNLOCK_B(); - - widget_unload(w); + TRACEF("plugin_call_cancel(userdata=%p)", userdata); - return NULL; + Widget *w = userdata; + lua_settop(w->L, 0); // w->L: - + UNLOCK_L(w); } +static lua_State * ew_call_begin(LS_ATTR_UNUSED_ARG void *userdata, size_t widget_idx) { - assert(widget_idx < nwidgets); + TRACEF("ew_call_begin(userdata=%p, widget_idx=%zu)", userdata, widget_idx); + assert(widget_idx < nwidgets); Widget *w = &widgets[widget_idx]; + LOCK_E(w); - LOCK_L(w); - - assert(lua_gettop(w->L) == 0); // w->L: - - lua_rawgeti(w->L, LUA_REGISTRYINDEX, w->lua_ref_event); // w->L: event - return w->L; + lua_State *L = widget_event_lua_state(w); + assert(lua_gettop(L) == 0); // L: - + lua_rawgeti(L, LUA_REGISTRYINDEX, w->lref_event); // L: event + return L; } +static void -ew_call_end(LS_ATTR_UNUSED_ARG void *userdata, size_t widget_idx) +ew_call_end(void *userdata, size_t widget_idx) { - assert(widget_idx < nwidgets); + TRACEF("ew_call_end(userdata=%p, widget_idx=%zu)", userdata, widget_idx); + assert(widget_idx < nwidgets); Widget *w = &widgets[widget_idx]; - lua_State *L = w->L; - + lua_State *L = widget_event_lua_state(w); assert(lua_gettop(L) == 2); // L: event arg - - if (w->lua_ref_event == LUA_REFNIL) { + if (w->lref_event == LUA_REFNIL) { lua_pop(L, 2); // L: - } else { if (!check_lua_call(L, lua_pcall(L, 1, 0, 0))) { @@ -176,99 +1018,106 @@ ew_call_end(LS_ATTR_UNUSED_ARG void *userdata, size_t widget_idx) LOCK_B(); set_error_unlocked(widget_idx); UNLOCK_B(); - } // else: L: - + } + // L: - } + UNLOCK_E(w); +} - UNLOCK_L(w); +static +void +ew_call_cancel(void *userdata, size_t widget_idx) +{ + TRACEF("ew_call_cancel(userdata=%p, widget_idx=%zu)", userdata, widget_idx); + + assert(widget_idx < nwidgets); + Widget *w = &widgets[widget_idx]; + lua_State *L = widget_event_lua_state(w); + lua_settop(L, 0); // L: - + UNLOCK_E(w); +} + +// Each thread spawned for a widget runs this function. /arg/ is a pointer to the widget. +static +void * +widget_thread(void *arg) +{ + Widget *w = arg; + DEBUGF("thread for widget '%s' is running", w->filename); + + w->plugin.iface.run(&w->data, (LuastatusPluginRunFuncs_v1) { + .call_begin = plugin_call_begin, + .call_end = plugin_call_end, + .call_cancel = plugin_call_cancel, + }); + WARNF("plugin's run() for widget '%s' has returned", w->filename); + + LOCK_B(); + set_error_unlocked(widget_index(w)); + UNLOCK_B(); + + return NULL; } +static bool prepare_stdio(void) { - // we rely on than stderr in unbuffered - if (setvbuf(stderr, NULL, _IONBF, 0) != 0) { - fprintf(stderr, "luastatus: (prepare) WARNING: setvbuf on stderr failed\n"); - fflush(stderr); // (!) - } - // we also rely a lot on *printf functions + // We rely on that /*printf/ functions produce numbers in C locale. if (setlocale(LC_NUMERIC, "C") == NULL) { - fprintf(stderr, "luastatus: (prepare) FATAL: setlocale failed\n"); + fprintf(stderr, "luastatus: FATAL: setlocale failed\n"); return false; } return true; } +static void ignore_signal(LS_ATTR_UNUSED_ARG int signo) { } +static bool prepare_signals(void) { struct sigaction sa = {.sa_flags = SA_RESTART}; if (sigemptyset(&sa.sa_mask) < 0) { LS_WITH_ERRSTR(s, errno, - fprintf(stderr, "luastatus: (prepare) WARNING: sigemptyset: %s", s); + fprintf(stderr, "luastatus: FATAL: sigemptyset: %s", s); ); return false; } - -#define CATCH(SigNo_) \ +#define HANDLE(SigNo_) \ do { \ if (sigaction(SigNo_, &sa, NULL) < 0) { \ LS_WITH_ERRSTR(s, errno, \ - fprintf(stderr, "luastatus: (prepare) WARNING: sigaction (%s): %s", #SigNo_, s); \ + fprintf(stderr, "luastatus: WARNING: sigaction: %s: %s", #SigNo_, s); \ ); \ } \ } while (0) + // We do not want to terminate on a write to a dead pipe. sa.sa_handler = ignore_signal; - CATCH(SIGPIPE); + HANDLE(SIGPIPE); + // We do this to ensure SA_RESTART is set for these. sa.sa_handler = SIG_DFL; - CATCH(SIGCHLD); - CATCH(SIGURG); + HANDLE(SIGCHLD); + HANDLE(SIGURG); -#undef CATCH +#undef HANDLE return true; } -bool -prepare(void) -{ - return prepare_stdio() && prepare_signals(); -} - +static void print_usage(void) { fprintf(stderr, "USAGE: luastatus -b barlib [-B barlib_option [-B ...]] [-l loglevel] [-e] " - "widget.lua [widget2.lua ...]\n"); - fprintf(stderr, " luastatus -v\n"); - fprintf(stderr, "See luastatus(1) for more information.\n"); -} - -void -print_version(void) -{ - fprintf(stderr, "This is luastatus %s.\n", LUASTATUS_VERSION); -} - -bool -init_or_make_dummy(Widget *w) -{ - assert(w->state == WIDGET_STATE_LOADED); - - if (widget_init(w, (LuastatusPluginData) {.userdata = w, .logf = external_logf})) { - lualibs_register_funcs(w, &barlib); - return true; - } else { - internal_logf(LUASTATUS_ERR, "can't init widget '%s'", w->filename); - widget_unload(w); - widget_load_dummy(w); - return false; - } + "widget.lua [widget2.lua ...]\n" + " luastatus -v\n" + "See luastatus(1) for more information.\n"); } int @@ -276,11 +1125,12 @@ main(int argc, char **argv) { int ret = EXIT_FAILURE; char *barlib_name = NULL; - LS_VECTOR_OF(char*) barlib_args = LS_VECTOR_NEW(); + LS_VECTOR_OF(const char *) barlib_args = LS_VECTOR_NEW(); bool no_hang = false; LS_VECTOR_OF(pthread_t) threads = LS_VECTOR_NEW(); - nwidgets = 0; - bool barlib_loaded = false; + bool barlib_inited = false; + + // Parse the arguments. for (int c; (c = getopt(argc, argv, "b:B:l:ev")) != -1;) { switch (c) { @@ -291,8 +1141,8 @@ main(int argc, char **argv) LS_VECTOR_PUSH(barlib_args, optarg); break; case 'l': - if ((global_loglevel = loglevel_fromstr(optarg)) == LUASTATUS_LOGLEVEL_LAST) { - fprintf(stderr, "Unknown loglevel '%s'.\n", optarg); + if ((loglevel = loglevel_fromstr(optarg)) == LUASTATUS_LOG_LAST) { + fprintf(stderr, "Unknown log level name '%s'.\n", optarg); print_usage(); goto cleanup; } @@ -301,7 +1151,7 @@ main(int argc, char **argv) no_hang = true; break; case 'v': - print_version(); + fprintf(stderr, "This is luastatus %s.\n", LUASTATUS_VERSION); goto cleanup; case '?': print_usage(); @@ -311,94 +1161,104 @@ main(int argc, char **argv) } } - if (!prepare()) { - goto cleanup; - } - if (!barlib_name) { - internal_logf(LUASTATUS_FATAL, "barlib not specified"); + fprintf(stderr, "Barlib was not specified."); print_usage(); goto cleanup; } - nwidgets = argc - optind; - widgets = LS_XNEW(Widget, nwidgets); - for (size_t i = 0; i < nwidgets; ++i) { - Widget *w = &widgets[i]; - const char *filename = argv[optind + i]; - if (!widget_load(w, filename)) { - internal_logf(LUASTATUS_ERR, "can't load widget '%s'", filename); - widget_load_dummy(w); - } - } + // Prepare. - if (!load_barlib_by_name(&barlib, barlib_name)) { - internal_logf(LUASTATUS_FATAL, "can't load barlib '%s'", barlib_name); + if (!prepare_stdio() || !prepare_signals()) { goto cleanup; } - barlib_loaded = true; - if (!check_taints(&barlib, widgets, nwidgets)) { - internal_logf(LUASTATUS_FATAL, "entities that share the same taint have been found"); - goto cleanup; + // Initialize the widgets (now, proper logging can be used). + + widgets_init(argv + optind, argc - optind); + + TRACEF("nwidgets = %zu, widgets = %p, sizeof(Widget) = %d", + nwidgets, (void *) widgets, (int) sizeof(Widget)); + + if (!nwidgets) { + WARNF("no widgets specified (see luastatus(1) for usage info)"); } + // Initialize the barlib. + LS_VECTOR_PUSH(barlib_args, NULL); - if (!barlib_init(&barlib, (LuastatusBarlibData) {.userdata = NULL, .logf = external_logf}, - (const char* const*) barlib_args.data, nwidgets)) - { - internal_logf(LUASTATUS_FATAL, "can't init the barlib"); + if (!barlib_init_by_name(barlib_name, barlib_args.data)) { + FATALF("cannot load barlib '%s'", barlib_name); goto cleanup; } + barlib_inited = true; + + // register barlib's function at the separate state, if we are going to use it. + if (sepstate.L) { + register_funcs(sepstate.L, NULL); + } + + // Spawn a thread for each successfully initialized widget, call /barlib/'s /set_error()/ method + // on each widget whose initialization has failed. LS_VECTOR_RESERVE(threads, nwidgets); for (size_t i = 0; i < nwidgets; ++i) { Widget *w = &widgets[i]; - if (w->state != WIDGET_STATE_DUMMY && init_or_make_dummy(w)) { - pthread_t t; - PTH_CHECK(pthread_create(&t, NULL, widget_thread, w)); - LS_VECTOR_PUSH(threads, t); - } else { + if (widget_is_stillborn(w)) { LOCK_B(); set_error_unlocked(i); UNLOCK_B(); + } else { + register_funcs(w->L, w); + pthread_t t; + PTH_ASSERT(pthread_create(&t, NULL, widget_thread, w)); + LS_VECTOR_PUSH(threads, t); } } + // Run /barlib/'s event watcher, if present. + if (barlib.iface.event_watcher) { - switch (barlib.iface.event_watcher(&barlib.data, ew_call_begin, ew_call_end)) { - case LUASTATUS_BARLIB_EW_RESULT_NO_MORE_EVENTS: - break; - case LUASTATUS_BARLIB_EW_RESULT_FATAL_ERR: - internal_logf(LUASTATUS_FATAL, "barlib's event_watcher() reported fatal error"); + if (barlib.iface.event_watcher(&barlib.data, (LuastatusBarlibEWFuncs_v1) { + .call_begin = ew_call_begin, + .call_end = ew_call_end, + .call_cancel = ew_call_cancel, + }) == LUASTATUS_ERR) + { + FATALF("barlib's event_watcher() reported fatal error"); fatal_error_reported(); - break; } } + // Join the widget threads. + + DEBUGF("joining all the widget threads"); for (size_t i = 0; i < threads.size; ++i) { - PTH_CHECK(pthread_join(threads.data[i], NULL)); + PTH_ASSERT(pthread_join(threads.data[i], NULL)); } - internal_logf(LUASTATUS_WARN, "all plugins' run() and barlib's event_watcher() have returned"); + // Either hang or exit. + + WARNF("all plugins' run() and barlib's event_watcher() have returned"); if (no_hang) { - internal_logf(LUASTATUS_INFO, "-e passed, exiting"); + INFOF("-e passed, exiting"); ret = EXIT_SUCCESS; } else { - internal_logf(LUASTATUS_INFO, "since -e not passed, will hang now"); + INFOF("since -e not passed, will hang now"); while (1) { pause(); } } cleanup: + // Let us please valgrind. LS_VECTOR_FREE(barlib_args); LS_VECTOR_FREE(threads); - for (size_t i = 0; i < nwidgets; ++i) { - widget_unload(&widgets[i]); - } - if (barlib_loaded) { - barlib_unload(&barlib); + widgets_destroy(); + if (barlib_inited) { + barlib_destroy(); } + sepstate_maybe_destroy(); + map_destroy(); return ret; } diff --git a/luastatus/plugin.c b/luastatus/plugin.c deleted file mode 100644 index 5afc7c6..0000000 --- a/luastatus/plugin.c +++ /dev/null @@ -1,56 +0,0 @@ -#include "plugin.h" - -#include -#include -#include -#include -#include "include/loglevel.h" -#include "libls/alloc_utils.h" -#include "sane_dlerror.h" -#include "log.h" - -bool -plugin_load(Plugin *p, const char *filename, const char *name) -{ - p->dlhandle = NULL; - - (void) dlerror(); // clear any last error - if (!(p->dlhandle = dlopen(filename, RTLD_NOW))) { - internal_logf(LUASTATUS_ERR, "dlopen: %s: %s", filename, sane_dlerror()); - goto error; - } - int *p_lua_ver = dlsym(p->dlhandle, "LUASTATUS_PLUGIN_LUA_VERSION_NUM"); - if (!p_lua_ver) { - internal_logf(LUASTATUS_ERR, "dlsym: LUASTATUS_PLUGIN_LUA_VERSION_NUM: %s", - sane_dlerror()); - goto error; - } - if (*p_lua_ver != LUA_VERSION_NUM) { - internal_logf(LUASTATUS_ERR, - "plugin '%s' was compiled with LUA_VERSION_NUM=%d and luastatus with %d", - filename, *p_lua_ver, LUA_VERSION_NUM); - goto error; - } - LuastatusPluginIface *p_iface = dlsym(p->dlhandle, "luastatus_plugin_iface"); - if (!p_iface) { - internal_logf(LUASTATUS_ERR, "dlsym: luastatus_plugin_iface: %s", sane_dlerror()); - goto error; - } - p->iface = *p_iface; - - p->name = ls_xstrdup(name); - return true; - -error: - if (p->dlhandle) { - dlclose(p->dlhandle); - } - return false; -} - -void -plugin_unload(Plugin *p) -{ - free(p->name); - dlclose(p->dlhandle); -} diff --git a/luastatus/plugin.h b/luastatus/plugin.h deleted file mode 100644 index 66846e4..0000000 --- a/luastatus/plugin.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef plugin_h_ -#define plugin_h_ - -#include -#include "include/plugin_data.h" - -typedef struct { - LuastatusPluginIface iface; - char *name; - void *dlhandle; -} Plugin; - -bool -plugin_load(Plugin *p, const char *filename, const char *name); - -void -plugin_unload(Plugin *p); - -#endif diff --git a/luastatus/pth_check.c b/luastatus/pth_check.c deleted file mode 100644 index d0ac5ce..0000000 --- a/luastatus/pth_check.c +++ /dev/null @@ -1,14 +0,0 @@ -#include "pth_check.h" - -#include -#include -#include "libls/errno_utils.h" - -void -pth_check__failed(int retval, const char *expr, const char *file, int line) -{ - LS_WITH_ERRSTR(s, retval, - fprintf(stderr, "PTH_CHECK(%s) failed at %s:%d.\nReason: %s\n", expr, file, line, s); - ); - abort(); -} diff --git a/luastatus/pth_check.h b/luastatus/pth_check.h deleted file mode 100644 index abd120d..0000000 --- a/luastatus/pth_check.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef pth_check_h_ -#define pth_check_h_ - -#include "libls/compdep.h" - -#define PTH_CHECK(Expr_) pth_check__impl(Expr_, #Expr_, __FILE__, __LINE__) - -void -pth_check__failed(int retval, const char *expr, const char *file, int line); - -LS_INHEADER -void -pth_check__impl(int retval, const char *expr, const char *file, int line) -{ - if (retval) { - pth_check__failed(retval, expr, file, line); - } -} - -#endif diff --git a/luastatus/sane_dlerror.h b/luastatus/sane_dlerror.h deleted file mode 100644 index ae842c6..0000000 --- a/luastatus/sane_dlerror.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef sane_dlsym_dlerror_h_ -#define sane_dlsym_dlerror_h_ - -#include -#include "libls/compdep.h" - -LS_INHEADER -const char * -sane_dlerror(void) -{ - const char *err = dlerror(); - return err ? err : "(no error, but the symbol is NULL)"; -} - -#endif diff --git a/luastatus/taints.c b/luastatus/taints.c deleted file mode 100644 index 47fde0a..0000000 --- a/luastatus/taints.c +++ /dev/null @@ -1,97 +0,0 @@ -#include "taints.h" - -#include -#include -#include -#include -#include "include/loglevel.h" -#include "libls/vector.h" -#include "libls/compdep.h" -#include "log.h" -#include "widget.h" -#include "barlib.h" - -static const size_t TAINT_OWNER_BARLIB = SIZE_MAX; - -typedef struct { - const char *id; - size_t owner; -} Taint; - -typedef LS_VECTOR_OF(Taint) TaintsVector; - -static inline -int -taint_cmp(const void *a, const void *b) -{ - return strcmp(((const Taint *) a)->id, ((const Taint *) b)->id); -} - -static -void -push_taints(TaintsVector *tv, const char *const *taints, size_t owner) -{ - if (!taints) { - return; - } - for (const char *const *s = taints; *s; ++s) { - LS_VECTOR_PUSH(*tv, ((Taint) {.id = *s, .owner = owner})); - } -} - -static -void -log_taint_block_begin(const char *id) -{ - internal_logf(LUASTATUS_INFO, "the following entities share taint '%s':", id); -} - -static -void -log_taint_owner_barlib(LS_ATTR_UNUSED_ARG Barlib *b) -{ - internal_logf(LUASTATUS_INFO, " * the barlib"); -} - -static -void -log_taint_owner_widget(Widget *w) -{ - internal_logf(LUASTATUS_INFO, " * plugin '%s' requested by widget '%s'", w->plugin.name, - w->filename); -} - -bool -check_taints(Barlib *barlib, Widget *widgets, size_t nwidgets) -{ - TaintsVector tv = LS_VECTOR_NEW(); - bool ret = true; - - push_taints(&tv, barlib->iface.taints, TAINT_OWNER_BARLIB); - for (size_t i = 0; i < nwidgets; ++i) { - push_taints(&tv, widgets[i].plugin.iface.taints, i); - } - - qsort(tv.data, tv.size, sizeof(Taint), taint_cmp); - - for (size_t i = 1; i < tv.size; ++i) { - if (taint_cmp(&tv.data[i - 1], &tv.data[i]) == 0) { - ret = false; - size_t from = i; - if (i == 1 || taint_cmp(&tv.data[i - 2], &tv.data[i - 1]) != 0) { - log_taint_block_begin(tv.data[i].id); - --from; - } - for (size_t j = from; j <= i; ++j) { - if (tv.data[j].owner == TAINT_OWNER_BARLIB) { - log_taint_owner_barlib(barlib); - } else { - log_taint_owner_widget(&widgets[tv.data[j].owner]); - } - } - } - } - - LS_VECTOR_FREE(tv); - return ret; -} diff --git a/luastatus/taints.h b/luastatus/taints.h deleted file mode 100644 index fe3a98f..0000000 --- a/luastatus/taints.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef taints_h_ -#define taints_h_ - -#include -#include -#include "barlib.h" -#include "widget.h" - -bool -check_taints(Barlib *barlib, Widget *widgets, size_t nwidgets); - -#endif diff --git a/luastatus/version.sh b/luastatus/version.sh new file mode 100755 index 0000000..74a173a --- /dev/null +++ b/luastatus/version.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +set -e +if [ $# -ne 1 ]; then + printf >&2 '%s\n' \ + "WARNING: this script should be executed by CMake, not by human." \ + "USAGE: $0 " + exit 2 +fi +cd -- "$1" || exit $? +git rev-parse --short HEAD || cat VERSION || echo UNKNOWN diff --git a/luastatus/version_h_generate.sh b/luastatus/version_h_generate.sh deleted file mode 100755 index 537df80..0000000 --- a/luastatus/version_h_generate.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh - -if [ $# -ne 2 ]; then - printf >&2 '%s\n' "\ -WARNING: this script should be executed by cmake, not by human. -USAGE: $0 " - exit 2 -fi - -cd -- "$1" || exit $? -version= -if [ -d .git ]; then - head=$(git rev-parse --short HEAD) && version="git-$head" -else - version=$(cat VERSION) -fi - -printf > "$2"/version.h '%s\n' "\ -#ifndef version_h_ -#define version_h_ - -#define LUASTATUS_VERSION \"${version:-UNKNOWN}\" - -#endif" diff --git a/luastatus/widget.c b/luastatus/widget.c deleted file mode 100644 index bb5cc08..0000000 --- a/luastatus/widget.c +++ /dev/null @@ -1,162 +0,0 @@ -#include "widget.h" - -#include -#include -#include -#include -#include -#include -#include "include/loglevel.h" -#include "libls/alloc_utils.h" -#include "libls/lua_utils.h" -#include "libls/compdep.h" -#include "pth_check.h" -#include "log.h" -#include "check_lua_call.h" -#include "lua_libs.h" -#include "load_by_name.h" -#include "plugin.h" - -bool -widget_load(Widget *w, const char *filename) -{ - lua_State *L = w->L = luaL_newstate(); - bool plugin_loaded = false; - - if (!L) { - ls_oom(); - } - luaL_openlibs(L); - // L: - - if (!check_lua_call(L, luaL_loadfile(L, filename))) { - goto error; - } - // L: chunk - lualibs_inject(L); // L: chunk - if (!check_lua_call(L, lua_pcall(L, 0, 0, 0))) { - goto error; - } - // L: - - ls_lua_pushglobaltable(L); // L: _G - ls_lua_rawgetf(L, "widget"); // L: _G widget - if (!lua_istable(L, -1)) { - internal_logf(LUASTATUS_ERR, "widget: expected table, found %s", luaL_typename(L, -1)); - goto error; - } - ls_lua_rawgetf(L, "plugin"); // L: _G widget plugin - if (!lua_isstring(L, -1)) { - internal_logf(LUASTATUS_ERR, "widget.plugin: expected string, found %s", - luaL_typename(L, -1)); - goto error; - } - if (!load_plugin_by_name(&w->plugin, lua_tostring(L, -1))) { - internal_logf(LUASTATUS_ERR, "can't load plugin '%s'", lua_tostring(L, -1)); - goto error; - } - plugin_loaded = true; - lua_pop(L, 1); // L: _G widget - - ls_lua_rawgetf(L, "cb"); // L: _G widget cb - if (!lua_isfunction(L, -1)) { - internal_logf(LUASTATUS_ERR, "widget.cb: expected function, found %s", - luaL_typename(L, -1)); - goto error; - } - w->lua_ref_cb = luaL_ref(L, LUA_REGISTRYINDEX); // L: _G widget - ls_lua_rawgetf(L, "event"); // L: _G widget event - if (!lua_isfunction(L, -1) && !lua_isnil(L, -1)) { - internal_logf(LUASTATUS_ERR, "widget.event: expected function or nil, found %s", - luaL_typename(L, -1)); - goto error; - } - w->lua_ref_event = luaL_ref(L, LUA_REGISTRYINDEX); // L: _G widget - lua_pop(L, 2); // L: - - - PTH_CHECK(pthread_mutex_init(&w->lua_mtx, NULL)); - w->filename = ls_xstrdup(filename); - w->state = WIDGET_STATE_LOADED; - return true; - -error: - lua_close(L); - if (plugin_loaded) { - plugin_unload(&w->plugin); - } - return false; -} - -void -widget_load_dummy(Widget *w) -{ - if (!(w->L = luaL_newstate())) { - ls_oom(); - } - w->state = WIDGET_STATE_DUMMY; - w->lua_ref_event = LUA_REFNIL; -} - -bool -widget_init(Widget *w, LuastatusPluginData data) -{ - assert(w->state == WIDGET_STATE_LOADED); - - w->data = data; - lua_State *L = w->L; - int init_top = lua_gettop(L); - // L: - - ls_lua_pushglobaltable(L); // L: _G - ls_lua_rawgetf(L, "widget"); // L: _G widget - assert(lua_istable(L, -1)); - ls_lua_rawgetf(L, "opts"); // L: _G widget opts - if (!lua_istable(L, -1)) { - if (lua_isnil(L, -1)) { - lua_pop(L, 1); // L: _G widget - lua_newtable(L); // L: _G widget table - } else { - internal_logf(LUASTATUS_ERR, "widget.opts: expected table or nil, found %s", - luaL_typename(L, -1)); - goto done; - } - } - switch (w->plugin.iface.init(&w->data, w->L)) { - case LUASTATUS_PLUGIN_INIT_RESULT_OK: - w->state = WIDGET_STATE_INITED; - goto done; - case LUASTATUS_PLUGIN_INIT_RESULT_ERR: - internal_logf(LUASTATUS_ERR, "plugin's (%s) init() failed", w->plugin.name); - goto done; - } - LS_UNREACHABLE(); - -done: - lua_settop(L, init_top); // L: - - return w->state == WIDGET_STATE_INITED; -} - -void -widget_uninit(Widget *w) -{ - assert(w->state == WIDGET_STATE_INITED); - - w->plugin.iface.destroy(&w->data); - w->state = WIDGET_STATE_LOADED; -} - -void -widget_unload(Widget *w) -{ - switch (w->state) { - case WIDGET_STATE_INITED: - widget_uninit(w); - /* fallthru */ - case WIDGET_STATE_LOADED: - plugin_unload(&w->plugin); - free(w->filename); - /* fallthru */ - case WIDGET_STATE_DUMMY: - lua_close(w->L); - PTH_CHECK(pthread_mutex_destroy(&w->lua_mtx)); - return; - } - LS_UNREACHABLE(); -} diff --git a/luastatus/widget.h b/luastatus/widget.h deleted file mode 100644 index 1ccb348..0000000 --- a/luastatus/widget.h +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef widget_h_ -#define widget_h_ - -#include -#include -#include -#include "include/plugin_data.h" -#include "plugin.h" - -typedef enum { - WIDGET_STATE_DUMMY, - WIDGET_STATE_LOADED, - WIDGET_STATE_INITED, -} WidgetState; - -typedef struct { - // DUMMY: undefined - // LOADED, INITED: a loaded Plugin - Plugin plugin; - - // DUMMY, LOADED: undefined - // INITED: plugin's data - LuastatusPluginData data; - - // DUMMY: an empty Lua state - // LOADED, INITED: a Lua state initialized from widget's file - lua_State *L; - - // DUMMY, LOADED, INITED: an initialized pthread_mutex_t - pthread_mutex_t lua_mtx; - - // DUMMY: undefined - // LOADED, INITED: a reference to the 'cb' function - int lua_ref_cb; - - // DUMMY: LUA_REFNIL - // LOADED, INITED: LUA_REFNIL or a reference to the 'event' function - int lua_ref_event; - - // DUMMY: undefined - // LOADED, INITED: path to widget's file - char *filename; - - WidgetState state; -} Widget; - -bool -widget_load(Widget *w, const char *filename); - -void -widget_load_dummy(Widget *w); - -bool -widget_init(Widget *w, LuastatusPluginData data); - -void -widget_uninit(Widget *w); - -void -widget_unload(Widget *w); - -#endif diff --git a/plugins/alsa/README.md b/plugins/alsa/README.md index 9999776..d56a24a 100644 --- a/plugins/alsa/README.md +++ b/plugins/alsa/README.md @@ -10,6 +10,14 @@ Options Channel name, defaults to `Master`. +* `in_db`: boolean + + Whether or not to report normalized volume (in dBs). + +* `capture`: boolean + + Whether or not this is a capture stream, as opposed to a playback one. Defaults to false. + `cb` argument === A table with the following entries: diff --git a/plugins/alsa/alsa.c b/plugins/alsa/alsa.c index f489258..e7f717b 100644 --- a/plugins/alsa/alsa.c +++ b/plugins/alsa/alsa.c @@ -5,32 +5,39 @@ #include #include -#include "include/plugin.h" -#include "include/plugin_logf_macros.h" +#include "include/plugin_v1.h" +#include "include/sayf_macros.h" #include "include/plugin_utils.h" #include "libls/alloc_utils.h" -#include "libls/sprintf_utils.h" typedef struct { char *card; char *channel; + bool capture; + bool in_db; } Priv; +static void -priv_destroy(Priv *p) +destroy(LuastatusPluginData *pd) { + Priv *p = pd->priv; free(p->card); free(p->channel); + free(p); } -LuastatusPluginInitResult +static +int init(LuastatusPluginData *pd, lua_State *L) { Priv *p = pd->priv = LS_XNEW(Priv, 1); *p = (Priv) { .card = NULL, .channel = NULL, + .capture = false, + .in_db = false, }; PU_MAYBE_VISIT_STR("card", s, @@ -47,14 +54,22 @@ init(LuastatusPluginData *pd, lua_State *L) p->channel = ls_xstrdup("Master"); } - return LUASTATUS_PLUGIN_INIT_RESULT_OK; + PU_MAYBE_VISIT_BOOL("capture", b, + p->capture = b; + ); + + PU_MAYBE_VISIT_BOOL("in_db", b, + p->in_db = b; + ); + + return LUASTATUS_OK; error: - priv_destroy(p); - free(p); - return LUASTATUS_PLUGIN_INIT_RESULT_ERR; + destroy(pd); + return LUASTATUS_ERR; } +static bool card_has_nicename(const char *realname, snd_ctl_card_info_t *info, const char *nicename) { @@ -68,6 +83,7 @@ card_has_nicename(const char *realname, snd_ctl_card_info_t *info, const char *n return r; } +static char * xalloc_card_realname(const char *nicename) { @@ -75,12 +91,12 @@ xalloc_card_realname(const char *nicename) if (snd_ctl_card_info_malloc(&info) < 0) { ls_oom(); } - static const size_t BUF_SZ = 16; + static const size_t BUF_SZ = 32; char *buf = LS_XNEW(char, BUF_SZ); int rcard = -1; while (snd_card_next(&rcard) >= 0 && rcard >= 0) { - ls_xsnprintf(buf, BUF_SZ, "hw:%d", rcard); + snprintf(buf, BUF_SZ, "hw:%d", rcard); if (card_has_nicename(buf, info, nicename)) { goto cleanup; } @@ -96,10 +112,7 @@ xalloc_card_realname(const char *nicename) static void -run( - LuastatusPluginData *pd, - LuastatusPluginCallBegin call_begin, - LuastatusPluginCallEnd call_end) +run(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs) { Priv *p = pd->priv; @@ -129,7 +142,7 @@ run( int r_; \ while ((r_ = Func_(__VA_ARGS__)) == -EINTR) {} \ if (r_ < 0) { \ - LUASTATUS_FATALF(pd, "%s: %s", #Func_, snd_strerror(r_)); \ + LS_FATALF(pd, "%s: %s", #Func_, snd_strerror(r_)); \ goto error; \ } \ } while (0) @@ -147,32 +160,49 @@ run( snd_mixer_selem_id_set_name(sid, p->channel); snd_mixer_elem_t *elem = snd_mixer_find_selem(mixer, sid); if (!elem) { - LUASTATUS_FATALF(pd, "can't find channel '%s'", p->channel); + LS_FATALF(pd, "can't find channel '%s'", p->channel); goto error; } + + int (*get_range)(snd_mixer_elem_t *, long *, long *) = + p->capture ? (p->in_db ? snd_mixer_selem_get_capture_dB_range + : snd_mixer_selem_get_capture_volume_range) + : (p->in_db ? snd_mixer_selem_get_playback_dB_range + : snd_mixer_selem_get_playback_volume_range); + + int (*get_cur)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *) = + p->capture ? (p->in_db ? snd_mixer_selem_get_capture_dB + : snd_mixer_selem_get_capture_volume) + : (p->in_db ? snd_mixer_selem_get_playback_dB + : snd_mixer_selem_get_playback_volume); + + int (*get_switch)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, int *) = + p->capture ? snd_mixer_selem_get_capture_switch + : snd_mixer_selem_get_playback_switch; + while (1) { - lua_State *L = call_begin(pd->userdata); + lua_State *L = funcs.call_begin(pd->userdata); lua_newtable(L); // L: table lua_newtable(L); // L: table table long pmin, pmax; - if (snd_mixer_selem_get_playback_volume_range(elem, &pmin, &pmax) >= 0) { + if (get_range(elem, &pmin, &pmax) >= 0) { lua_pushnumber(L, pmin); // L: table table pmin lua_setfield(L, -2, "min"); // L: table table lua_pushnumber(L, pmax); // L: table table pmax lua_setfield(L, -2, "max"); // L: table table } long pcur; - if (snd_mixer_selem_get_playback_volume(elem, 0, &pcur) >= 0) { + if (get_cur(elem, 0, &pcur) >= 0) { lua_pushnumber(L, pcur); // L: table table pcur lua_setfield(L, -2, "cur"); // L: table table } lua_setfield(L, -2, "vol"); // L: table - int pswitch; - if (snd_mixer_selem_get_playback_switch(elem, 0, &pswitch) >= 0) { - lua_pushboolean(L, !pswitch); // L: table !pswitch + int notmute; + if (get_switch(elem, 0, ¬mute) >= 0) { + lua_pushboolean(L, !notmute); // L: table !notmute lua_setfield(L, -2, "mute"); // L: table } - call_end(pd->userdata); + funcs.call_end(pd->userdata); ALSA_CALL(snd_mixer_wait, mixer, -1); ALSA_CALL(snd_mixer_handle_events, mixer); } @@ -187,14 +217,7 @@ run( free(realname); } -void -destroy(LuastatusPluginData *pd) -{ - priv_destroy(pd->priv); - free(pd->priv); -} - -LuastatusPluginIface luastatus_plugin_iface = { +LuastatusPluginIface luastatus_plugin_iface_v1 = { .init = init, .run = run, .destroy = destroy, diff --git a/plugins/fs/fs.c b/plugins/fs/fs.c index 516a747..cee9c2f 100644 --- a/plugins/fs/fs.c +++ b/plugins/fs/fs.c @@ -5,9 +5,11 @@ #include #include #include -#include "include/plugin.h" -#include "include/plugin_logf_macros.h" + +#include "include/plugin_v1.h" +#include "include/sayf_macros.h" #include "include/plugin_utils.h" + #include "libls/alloc_utils.h" #include "libls/lua_utils.h" #include "libls/vector.h" @@ -16,22 +18,26 @@ #include "libls/wakeup_fifo.h" typedef struct { - LS_VECTOR_OF(char*) paths; + LS_VECTOR_OF(char *) paths; struct timespec period; char *fifo; } Priv; +static void -priv_destroy(Priv *p) +destroy(LuastatusPluginData *pd) { + Priv *p = pd->priv; for (size_t i = 0; i < p->paths.size; ++i) { free(p->paths.data[i]); } LS_VECTOR_FREE(p->paths); free(p->fifo); + free(p); } -LuastatusPluginInitResult +static +int init(LuastatusPluginData *pd, lua_State *L) { Priv *p = pd->priv = LS_XNEW(Priv, 1); @@ -41,19 +47,19 @@ init(LuastatusPluginData *pd, lua_State *L) .fifo = NULL, }; - PU_MAYBE_TRAVERSE_TABLE("paths", - PU_CHECK_TYPE_AT(LS_LUA_TRAVERSE_KEY, "'paths' key", LUA_TNUMBER); - PU_VISIT_STR_AT(LS_LUA_TRAVERSE_VALUE, "'paths' element", s, + PU_TRAVERSE_TABLE("paths", + PU_CHECK_TYPE_AT(LS_LUA_KEY, "'paths' key", LUA_TNUMBER); + PU_VISIT_STR_AT(LS_LUA_VALUE, "'paths' element", s, LS_VECTOR_PUSH(p->paths, ls_xstrdup(s)); ); ); if (!p->paths.size) { - LUASTATUS_WARNF(pd, "paths not specified or empty"); + LS_WARNF(pd, "paths are empty"); } PU_MAYBE_VISIT_NUM("period", n, if (ls_timespec_is_invalid(p->period = ls_timespec_from_seconds(n))) { - LUASTATUS_FATALF(pd, "invalid 'period' value"); + LS_FATALF(pd, "invalid 'period' value"); goto error; } ); @@ -62,21 +68,21 @@ init(LuastatusPluginData *pd, lua_State *L) p->fifo = ls_xstrdup(s); ); - return LUASTATUS_PLUGIN_INIT_RESULT_OK; + return LUASTATUS_OK; error: - priv_destroy(p); - free(p); - return LUASTATUS_PLUGIN_INIT_RESULT_ERR; + destroy(pd); + return LUASTATUS_ERR; } +static bool push_for(LuastatusPluginData *pd, lua_State *L, const char *path) { struct statvfs st; if (statvfs(path, &st) < 0) { LS_WITH_ERRSTR(s, errno, - LUASTATUS_WARNF(pd, "statvfs: %s: %s", path, s); + LS_WARNF(pd, "statvfs: %s: %s", path, s); ); return false; } @@ -90,31 +96,23 @@ push_for(LuastatusPluginData *pd, lua_State *L, const char *path) return true; } +static void -run( - LuastatusPluginData *pd, - LuastatusPluginCallBegin call_begin, - LuastatusPluginCallEnd call_end) +run(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs) { Priv *p = pd->priv; - LSWakeupFifo w; - ls_wakeup_fifo_init(&w); - sigset_t allsigs; - if (sigfillset(&allsigs) < 0) { + if (ls_wakeup_fifo_init(&w, p->fifo, p->period, NULL) < 0) { LS_WITH_ERRSTR(s, errno, - LUASTATUS_FATALF(pd, "sigfillset: %s", s); + LS_FATALF(pd, "ls_wakeup_fifo_init: %s", s); ); goto error; } - w.fifo = p->fifo; - w.timeout = &p->period; - w.sigmask = &allsigs; while (1) { // make a call - lua_State *L = call_begin(pd->userdata); + lua_State *L = funcs.call_begin(pd->userdata); lua_newtable(L); for (size_t i = 0; i < p->paths.size; ++i) { const char *path = p->paths.data[i]; @@ -122,18 +120,18 @@ run( lua_setfield(L, -2, path); } } - call_end(pd->userdata); + funcs.call_end(pd->userdata); // wait if (ls_wakeup_fifo_open(&w) < 0) { LS_WITH_ERRSTR(s, errno, - LUASTATUS_WARNF(pd, "open: %s: %s", p->fifo, s); + LS_WARNF(pd, "ls_wakeup_fifo_open: %s: %s", p->fifo, s); ); } - if (ls_wakeup_fifo_pselect(&w) < 0) { + if (ls_wakeup_fifo_wait(&w) < 0) { LS_WITH_ERRSTR(s, errno, - LUASTATUS_FATALF(pd, "pselect: %s", s); - goto error; + LS_FATALF(pd, "ls_wakeup_fifo_wait: %s: %s", p->fifo, s); ); + goto error; } } @@ -141,14 +139,7 @@ run( ls_wakeup_fifo_destroy(&w); } -void -destroy(LuastatusPluginData *pd) -{ - priv_destroy(pd->priv); - free(pd->priv); -} - -LuastatusPluginIface luastatus_plugin_iface = { +LuastatusPluginIface luastatus_plugin_iface_v1 = { .init = init, .run = run, .destroy = destroy, diff --git a/plugins/inotify/CMakeLists.txt b/plugins/inotify/CMakeLists.txt new file mode 100644 index 0000000..c5687a7 --- /dev/null +++ b/plugins/inotify/CMakeLists.txt @@ -0,0 +1,10 @@ +file (GLOB sources "*.c") +luastatus_add_plugin (inotify $ ${sources}) + +include (CheckSymbolExists) +check_symbol_exists (inotify_init1 "sys/inotify.h" HAVE_INOTIFY_INIT1) +configure_file ("probes.in.h" "probes.generated.h") + +target_compile_definitions (inotify PUBLIC -D_POSIX_C_SOURCE=200809L) +luastatus_target_compile_with (inotify LUA) +target_include_directories (inotify PUBLIC "${PROJECT_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}") diff --git a/plugins/inotify/README.md b/plugins/inotify/README.md new file mode 100644 index 0000000..4912710 --- /dev/null +++ b/plugins/inotify/README.md @@ -0,0 +1,47 @@ +This plugin monitors inotify events. + +Options +=== +* `watch`: table + + A table. Keys are paths to watch, values are comma-separated event names (see the “events and flag names” section). + +* `greet`: boolean + + Whether or not a first call to `cb` with a `nil` argument should be made as soon as the widget starts. Defaults to false. + +`cb` argument +=== +If the `greet` option has been set to `true`, the first call is made with a `nil` argument. + +Otherwise, it is a table with the following entries: + + * `wd`: integer + + Watch descriptor (see the “Functions” section) of the event. + + * `mask`: table + + For each event name or event flag (see the “Events and flags names” section), this table contains an entry with key equal to its name and `true` value. + + * `cookie`: number + + Unique cookie associating related events (or, if there are no associated related events, is zero). + + * `name`: string (optional) + + Present only when an event is returned for a file inside a watched directory; it identifies the filename within to the watched directory. + +Functions +=== +* `wds = luastatus.plugin.get_initial_wds()` + +* `wd = luastatus.plugin.add_watch(path, events)` + +* `is_ok = luastatus.plugin.remove_watch(wd)` + +Events and flags names +=== +Each `IN_*` constant defined in `` corresponds to a string obtained from its name by dropping the initial `IN_` and making the rest lower-case, e.g. `IN_CLOSE_WRITE` corresponds to `"close_write"`. + +See http://man7.org/linux/man-pages/man7/inotify.7.html for details. diff --git a/plugins/inotify/inotify.c b/plugins/inotify/inotify.c new file mode 100644 index 0000000..877e997 --- /dev/null +++ b/plugins/inotify/inotify.c @@ -0,0 +1,338 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "include/plugin_v1.h" +#include "include/sayf_macros.h" +#include "include/plugin_utils.h" + +#include "libls/errno_utils.h" +#include "libls/vector.h" + +#include "inotify_compat.h" + +typedef struct { + int fd; + LS_VECTOR_OF(int) wds; + bool greet; +} Priv; + +static +void +destroy(LuastatusPluginData *pd) +{ + Priv *p = pd->priv; + ls_close(p->fd); + LS_VECTOR_FREE(p->wds); + free(p); +} + +typedef struct { + uint32_t mask; + bool in, out; + const char *name; +} EventType; + +static const EventType EVENT_TYPES[] = { + {IN_ACCESS, true, true, "access"}, + {IN_ATTRIB, true, true, "attrib"}, + {IN_CLOSE_WRITE, true, true, "close_write"}, + {IN_CLOSE_NOWRITE, true, true, "close_nowrite"}, + {IN_CREATE, true, true, "create"}, + {IN_DELETE, true, true, "delete"}, + {IN_DELETE_SELF, true, true, "delete_self"}, + {IN_MODIFY, true, true, "modify"}, + {IN_MOVE_SELF, true, true, "move_self"}, + {IN_MOVED_FROM, true, true, "moved_from"}, + {IN_MOVED_TO, true, true, "moved_to"}, + {IN_OPEN, true, true, "open"}, + + {IN_ALL_EVENTS, true, false, "all_events"}, + {IN_MOVE, true, false, "move"}, + {IN_CLOSE, true, false, "close"}, +#ifdef IN_DONT_FOLLOW + {IN_DONT_FOLLOW, true, false, "dont_follow"}, +#endif +#ifdef IN_EXCL_UNLINK + {IN_EXCL_UNLINK, true, false, "excl_unlink"}, +#endif + {IN_MASK_ADD, true, false, "mask_add"}, + {IN_ONESHOT, true, false, "oneshot"}, + {IN_ONLYDIR, true, false, "onlydir"}, + + {IN_IGNORED, false, true, "ignored"}, + {IN_ISDIR, false, true, "isdir"}, + {IN_Q_OVERFLOW, false, true, "q_overflow"}, + {IN_UNMOUNT, false, true, "unmount"}, + + {0}, +}; + +// returns 0 on failure +static inline +uint32_t +parse_input_event_name(const char *name) +{ + for (const EventType *et = EVENT_TYPES; et->name; ++et) { + if (et->in && strcmp(et->name, name) == 0) { + return et->mask; + } + } + return 0; +} + +static +int +init(LuastatusPluginData *pd, lua_State *L) +{ + Priv *p = pd->priv = LS_XNEW(Priv, 1); + *p = (Priv) { + .fd = -1, + .wds = LS_VECTOR_NEW(), + .greet = false, + }; + + if ((p->fd = compat_inotify_init(false, true)) < 0) { + LS_WITH_ERRSTR(s, errno, + LS_FATALF(pd, "inotify_init: %s", s); + ); + goto error; + } + + PU_MAYBE_VISIT_BOOL("greet", b, + p->greet = b; + ); + + PU_TRAVERSE_TABLE("watch", + uint32_t mask = 0; + const char *path; + + // Inspect the value + PU_TRAVERSE_TABLE_AT(LS_LUA_VALUE, "'watch' value", + PU_CHECK_TYPE_AT(LS_LUA_KEY, "key of 'watch' value", LUA_TNUMBER); + PU_VISIT_STR_AT(LS_LUA_VALUE, "element of 'watch' value", s, + uint32_t r = parse_input_event_name(s); + if (!r) { + LS_FATALF(pd, "unknown input event type name '%s'", s); + goto error; + } + mask |= r; + ); + ); + + // Inspect the key + PU_VISIT_LSTR_AT(LS_LUA_KEY, "'watch' key", s, ns, + if (strlen(s) != ns) { + LS_FATALF(pd, "'watch' key contains a NIL character"); + goto error; + } + path = s; + ); + + if (!mask) { + LS_WARNF(pd, "'watch' value for key '%s' an empty table", path); + } + int wd = inotify_add_watch(p->fd, path, mask); + if (wd < 0) { + LS_WITH_ERRSTR(s, errno, + LS_ERRF(pd, "inotify_add_watch: %s: %s", path, s); + ); + } else { + LS_VECTOR_PUSH(p->wds, wd); + } + ); + if (!p->wds.size) { + LS_WARNF(pd, "nothing to watch for"); + } + + return LUASTATUS_OK; + +error: + destroy(pd); + return LUASTATUS_ERR; +} + +static +int +l_add_watch(lua_State *L) +{ + const char *path = luaL_checkstring(L, 1); + luaL_checktype(L, 2, LUA_TTABLE); + uint32_t mask = 0; + LS_LUA_TRAVERSE(L, 2) { + // Inspect the value + if (!lua_isnumber(L, LS_LUA_KEY)) { + return luaL_error(L, "second argument is not an array (has a non-numeric key)"); + } + // Inspect the key + if (!lua_isstring(L, LS_LUA_VALUE)) { + return luaL_error(L, "array element: string expected, found %s", + luaL_typename(L, LS_LUA_VALUE)); + } + size_t nevent; + const char *event = lua_tolstring(L, LS_LUA_VALUE, &nevent); + if (nevent != strlen(event)) { + return luaL_error(L, "array element contains a NIL character"); + } + uint32_t r = parse_input_event_name(event); + if (!r) { + return luaL_error(L, "unknown input event type name '%s'", event); + } + mask |= r; + } + + LuastatusPluginData *pd = lua_touserdata(L, lua_upvalueindex(1)); + Priv *p = pd->priv; + + if (!mask) { + LS_WARNF(pd, "add_watch: empty table passed"); + } + int wd = inotify_add_watch(p->fd, path, mask); + if (wd < 0) { + LS_WITH_ERRSTR(s, errno, + LS_ERRF(pd, "inotify_add_watch: %s: %s", path, s); + ); + lua_pushnil(L); + } else { + lua_pushinteger(L, wd); + } + return 1; +} + +static +int +l_remove_watch(lua_State *L) +{ + int wd = luaL_checkinteger(L, 1); + + LuastatusPluginData *pd = lua_touserdata(L, lua_upvalueindex(1)); + Priv *p = pd->priv; + + if (inotify_rm_watch(p->fd, wd) < 0) { + LS_WITH_ERRSTR(s, errno, + LS_ERRF(pd, "inotify_rm_watch: %d: %s", wd, s); + ); + lua_pushboolean(L, false); + } else { + lua_pushboolean(L, true); + } + return 1; +} + +static +int +l_get_initial_wds(lua_State *L) +{ + LuastatusPluginData *pd = lua_touserdata(L, lua_upvalueindex(1)); + Priv *p = pd->priv; + + lua_createtable(L, p->wds.size, 0); // L: table + for (size_t i = 0; i < p->wds.size; ++i) { + lua_pushinteger(L, p->wds.data[i]); // L: table wd + lua_rawseti(L, -1, i + 1); // L: table + } + return 1; +} + +static +void +register_funcs(LuastatusPluginData *pd, lua_State *L) +{ +#define REG_CLOSURE(Func_, Name_) \ + /* L: table */ \ + lua_pushlightuserdata(L, pd); /* L: table bd */ \ + lua_pushcclosure(L, Func_, 1); /* L: table bd Func_ */ \ + ls_lua_rawsetf(L, Name_); /* L: table */ + + REG_CLOSURE(l_add_watch, "add_watch"); + REG_CLOSURE(l_remove_watch, "remove_watch"); + REG_CLOSURE(l_get_initial_wds, "get_initial_wds"); + +#undef REG_CLOSURE +} + +static +void +push_event(lua_State *L, const struct inotify_event *event) +{ + // L: - + lua_newtable(L); // L: table + + lua_pushinteger(L, event->wd); // L: table wd + lua_setfield(L, -2, "wd"); // L: table + + lua_newtable(L); // L: table table + for (const EventType *et = EVENT_TYPES; et->name; ++et) { + if (et->out && (event->mask & et->mask)) { + lua_pushboolean(L, 1); // L: table table true + lua_setfield(L, -2, et->name); // L: table table + } + } + lua_setfield(L, -2, "mask"); // L: table + + lua_pushnumber(L, event->cookie); // L: table cookie + lua_setfield(L, -2, "cookie"); // L: table + + if (event->len) { + lua_pushstring(L, event->name); // L: table name + lua_setfield(L, -2, "name"); // L: table + } +} + +static +void +run(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs) +{ + Priv *p = pd->priv; + + if (p->greet) { + lua_pushnil(funcs.call_begin(pd->userdata)); + funcs.call_end(pd->userdata); + } + +#if defined(NAME_MAX) && NAME_MAX < 4096 + char buf[sizeof(struct inotify_event) + NAME_MAX + 1] +#else + char buf[sizeof(struct inotify_event) + 4096] +#endif + __attribute__ ((aligned(__alignof__(struct inotify_event)))); + + const int fd = p->fd; + while (1) { + ssize_t r = read(fd, buf, sizeof(buf)); + if (r < 0) { + if (errno == EINTR) { + continue; + } + LS_WITH_ERRSTR(s, errno, + LS_FATALF(pd, "read: %s", s); + ); + return; + } else if (r == 0) { + LS_FATALF(pd, "read() from the inotify file descriptor returned 0"); + return; + } + const struct inotify_event *event; + for (char *ptr = buf; + ptr < buf + r; + ptr += sizeof(struct inotify_event) + event->len) + { + event = (const struct inotify_event *) ptr; + push_event(funcs.call_begin(pd->userdata), event); + funcs.call_end(pd->userdata); + } + } +} + +LuastatusPluginIface luastatus_plugin_iface_v1 = { + .init = init, + .register_funcs = register_funcs, + .run = run, + .destroy = destroy, +}; diff --git a/plugins/inotify/inotify_compat.h b/plugins/inotify/inotify_compat.h new file mode 100644 index 0000000..7a08526 --- /dev/null +++ b/plugins/inotify/inotify_compat.h @@ -0,0 +1,44 @@ +#ifndef inotify_compat_h_ +#define inotify_compat_h_ + +#include +#include +#include +#include + +#include "libls/io_utils.h" +#include "libls/osdep.h" +#include "libls/compdep.h" + +#include "probes.generated.h" + +LS_INHEADER +int +compat_inotify_init(bool nonblock, bool cloexec) +{ +#if HAVE_INOTIFY_INIT1 + return inotify_init1((nonblock ? IN_NONBLOCK : 0) | + (cloexec ? IN_CLOEXEC : 0)); +#else + int saved_errno; + int fd = inotify_init(); + if (fd < 0) { + goto error; + } + if (nonblock && ls_make_nonblock(fd) < 0) { + goto error; + } + if (cloexec && ls_make_cloexec(fd) < 0) { + goto error; + } + return fd; + +error: + saved_errno = errno; + ls_close(fd); + errno = saved_errno; + return -1; +#endif +} + +#endif diff --git a/plugins/inotify/probes.in.h b/plugins/inotify/probes.in.h new file mode 100644 index 0000000..1adab66 --- /dev/null +++ b/plugins/inotify/probes.in.h @@ -0,0 +1,6 @@ +#ifndef probes_h_ +#define probes_h_ + +#cmakedefine01 HAVE_INOTIFY_INIT1 + +#endif diff --git a/plugins/mpd/README.md b/plugins/mpd/README.md index acaa887..1f8bd8b 100644 --- a/plugins/mpd/README.md +++ b/plugins/mpd/README.md @@ -4,7 +4,7 @@ Options === * `hostname`: string - Hostname to connect to. Default is to connect to the local host. + Hostname to connect to. Default is to connect to the local host. An absolute path to a UNIX domain socket can also be specified (`port` is ignored then). * `port`: number diff --git a/plugins/mpd/connect.c b/plugins/mpd/connect.c new file mode 100644 index 0000000..666ec86 --- /dev/null +++ b/plugins/mpd/connect.c @@ -0,0 +1,96 @@ +#include "connect.h" + +#include +#include +#include +#include +#include +#include + +#include "include/plugin_data_v1.h" +#include "include/sayf_macros.h" + +#include "libls/errno_utils.h" +#include "libls/osdep.h" + +int +socket_open(LuastatusPluginData *pd, const char *path) +{ + struct sockaddr_un saun; + const size_t npath = strlen(path); + if (npath + 1 > sizeof(saun.sun_path)) { + LS_ERRF(pd, "socket path is too long: %s", path); + return -1; + } + saun.sun_family = AF_UNIX; + memcpy(saun.sun_path, path, npath + 1); + int fd = ls_cloexec_socket(PF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + LS_WITH_ERRSTR(s, errno, + LS_ERRF(pd, "socket: %s", s); + ); + return -1; + } + if (connect(fd, (const struct sockaddr *) &saun, sizeof(saun)) < 0) { + LS_WITH_ERRSTR(s, errno, + LS_ERRF(pd, "connect: %s: %s", path, s); + ); + ls_close(fd); + return -1; + } + return fd; +} + +int +tcp_open(LuastatusPluginData *pd, const char *hostname, const char *service) +{ + struct addrinfo *ai = NULL; + int fd = -1; + + struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_protocol = 0, + .ai_flags = AI_ADDRCONFIG, + }; + + int gai_r = getaddrinfo(hostname, service, &hints, &ai); + if (gai_r) { + if (gai_r == EAI_SYSTEM) { + LS_WITH_ERRSTR(s, errno, + LS_ERRF(pd, "getaddrinfo: %s", s); + ); + } else { + LS_ERRF(pd, "getaddrinfo: %s", gai_strerror(gai_r)); + } + ai = NULL; + goto cleanup; + } + + for (struct addrinfo *pai = ai; pai; pai = pai->ai_next) { + if ((fd = ls_cloexec_socket(pai->ai_family, pai->ai_socktype, pai->ai_protocol)) < 0) { + LS_WITH_ERRSTR(s, errno, + LS_WARNF(pd, "(candiate) socket: %s", s); + ); + continue; + } + if (connect(fd, pai->ai_addr, pai->ai_addrlen) < 0) { + LS_WITH_ERRSTR(s, errno, + LS_WARNF(pd, "(candiate) connect: %s", s); + ); + ls_close(fd); + fd = -1; + continue; + } + break; + } + if (fd < 0) { + LS_ERRF(pd, "can't connect to any of the candidates"); + } + +cleanup: + if (ai) { + freeaddrinfo(ai); + } + return fd; +} diff --git a/plugins/mpd/connect.h b/plugins/mpd/connect.h new file mode 100644 index 0000000..4caa867 --- /dev/null +++ b/plugins/mpd/connect.h @@ -0,0 +1,12 @@ +#ifndef connect_h_ +#define connect_h_ + +#include "include/plugin_data_v1.h" + +int +socket_open(LuastatusPluginData *pd, const char *path); + +int +tcp_open(LuastatusPluginData *pd, const char *hostname, const char *service); + +#endif diff --git a/plugins/mpd/mpd.c b/plugins/mpd/mpd.c index ab9b634..5e2fc7d 100644 --- a/plugins/mpd/mpd.c +++ b/plugins/mpd/mpd.c @@ -1,20 +1,15 @@ -#include "include/plugin.h" -#include "include/plugin_logf_macros.h" -#include "include/plugin_utils.h" - #include #include #include - #include -#include -#include - -#include #include - #include #include +#include + +#include "include/plugin_v1.h" +#include "include/sayf_macros.h" +#include "include/plugin_utils.h" #include "libls/alloc_utils.h" #include "libls/errno_utils.h" @@ -24,6 +19,9 @@ #include "libls/wakeup_fifo.h" #include "libls/strarr.h" +#include "connect.h" +#include "mpdproto_utils.h" + typedef struct { char *hostname; int port; @@ -31,17 +29,23 @@ typedef struct { struct timespec timeout; struct timespec retry_in; char *retry_fifo; + char *idle_str; } Priv; +static void -priv_destroy(Priv *p) +destroy(LuastatusPluginData *pd) { + Priv *p = pd->priv; free(p->hostname); free(p->password); free(p->retry_fifo); + free(p->idle_str); + free(p); } -LuastatusPluginInitResult +static +int init(LuastatusPluginData *pd, lua_State *L) { Priv *p = pd->priv = LS_XNEW(Priv, 1); @@ -52,7 +56,9 @@ init(LuastatusPluginData *pd, lua_State *L) .timeout = ls_timespec_invalid, .retry_in = (struct timespec) {10, 0}, .retry_fifo = NULL, + .idle_str = NULL, }; + LSString idle_str = ls_string_new_from_s("idle"); PU_MAYBE_VISIT_STR("hostname", s, p->hostname = ls_xstrdup(s); @@ -60,26 +66,30 @@ init(LuastatusPluginData *pd, lua_State *L) PU_MAYBE_VISIT_NUM("port", n, if (n < 0 || n > 65535) { - LUASTATUS_FATALF(pd, "port (%g) is not a valid port number", (double) n); + LS_FATALF(pd, "port (%g) is not a valid port number", (double) n); goto error; } p->port = n; ); PU_MAYBE_VISIT_STR("password", s, + if ((strchr(s, '\n'))) { + LS_FATALF(pd, "password contains a line break"); + goto error; + } p->password = ls_xstrdup(s); ); PU_MAYBE_VISIT_NUM("timeout", n, if (ls_timespec_is_invalid(p->timeout = ls_timespec_from_seconds(n)) && n >= 0) { - LUASTATUS_FATALF(pd, "'timeout' is invalid"); + LS_FATALF(pd, "'timeout' is invalid"); goto error; } ); PU_MAYBE_VISIT_NUM("retry_in", n, if (ls_timespec_is_invalid(p->retry_in = ls_timespec_from_seconds(n)) && n >= 0) { - LUASTATUS_FATALF(pd, "'retry_in' is invalid"); + LS_FATALF(pd, "'retry_in' is invalid"); goto error; } ); @@ -88,69 +98,30 @@ init(LuastatusPluginData *pd, lua_State *L) p->retry_fifo = ls_xstrdup(s); ); - return LUASTATUS_PLUGIN_INIT_RESULT_OK; - -error: - priv_destroy(p); - free(p); - return LUASTATUS_PLUGIN_INIT_RESULT_ERR; -} - -int -tcp_open(LuastatusPluginData *pd, const char *hostname, const char *service) -{ - struct addrinfo* ai = NULL; - int fd = -1; - - struct addrinfo hints = { - .ai_family = AF_UNSPEC, - .ai_socktype = SOCK_STREAM, - .ai_protocol = 0, - .ai_flags = AI_ADDRCONFIG, - }; - - int gai_r = getaddrinfo(hostname, service, &hints, &ai); - if (gai_r) { - if (gai_r == EAI_SYSTEM) { - LS_WITH_ERRSTR(s, errno, - LUASTATUS_ERRF(pd, "getaddrinfo: %s", s); - ); - } else { - LUASTATUS_ERRF(pd, "getaddrinfo: %s", gai_strerror(gai_r)); - } - ai = NULL; - goto cleanup; + bool has_events = false; + PU_MAYBE_TRAVERSE_TABLE("events", + has_events = true; + PU_CHECK_TYPE_AT(LS_LUA_KEY, "'events' key", LUA_TNUMBER); + PU_VISIT_STR_AT(LS_LUA_VALUE, "'events' element", s, + ls_string_append_c(&idle_str, ' '); + ls_string_append_s(&idle_str, s); + ); + ); + if (!has_events) { + ls_string_append_s(&idle_str, " mixer player"); } + ls_string_append_b(&idle_str, "\n", 2); // append '\n' and '\0' + p->idle_str = idle_str.data; - for (struct addrinfo *pai = ai; pai; pai = pai->ai_next) { - if ((fd = ls_cloexec_socket(pai->ai_family, pai->ai_socktype, pai->ai_protocol)) < 0) { - LS_WITH_ERRSTR(s, errno, - LUASTATUS_WARNF(pd, "(candiate) socket: %s", s); - ); - continue; - } - if (connect(fd, pai->ai_addr, pai->ai_addrlen) < 0) { - LS_WITH_ERRSTR(s, errno, - LUASTATUS_WARNF(pd, "(candiate) connect: %s", s); - ); - ls_close(fd); - fd = -1; - continue; - } - break; - } - if (fd < 0) { - LUASTATUS_ERRF(pd, "can't connect to any of the candidates"); - } + return LUASTATUS_OK; -cleanup: - if (ai) { - freeaddrinfo(ai); - } - return fd; +error: + LS_VECTOR_FREE(idle_str); + destroy(pd); + return LUASTATUS_ERR; } -// string length without trailing newlines +// Returns the length of /s/ without trailing newlines. static inline size_t dollar_strlen(const char *s) @@ -162,7 +133,8 @@ dollar_strlen(const char *s) return len; } -// If line is of form ": \n", appends and to sa. +// If /line/ is of form "key: value\n", appends /key/ and /value/ to /sa/. +static void kv_strarr_line_append(LSStringArray *sa, const char *line) { @@ -175,65 +147,47 @@ kv_strarr_line_append(LSStringArray *sa, const char *line) ls_strarr_append(sa, value_pos, dollar_strlen(value_pos)); } +static void kv_strarr_table_assign(LSStringArray sa, lua_State *L) { + // L: table const size_t n = ls_strarr_size(sa); for (size_t i = 0; i < n; i += 2) { size_t nkey; const char *key = ls_strarr_at(sa, i, &nkey); - lua_pushlstring(L, key, nkey); + lua_pushlstring(L, key, nkey); // L: table key size_t nvalue; const char *value = ls_strarr_at(sa, i + 1, &nvalue); - lua_pushlstring(L, value, nvalue); - } -} - -typedef enum { - RESP_TYPE_OK, - RESP_TYPE_ACK, - RESP_TYPE_OTHER, -} ResponseType; - -static inline -ResponseType -response_type(const char *buf) -{ - if (strcmp(buf, "OK\n") == 0) { - return RESP_TYPE_OK; + lua_pushlstring(L, value, nvalue); // L: table key value + lua_settable(L, -3); // L: table } - if (strncmp(buf, "ACK ", 4) == 0) { - return RESP_TYPE_ACK; - } - return RESP_TYPE_OTHER; } static inline void -report_status(LuastatusPluginData *pd, - LuastatusPluginCallBegin call_begin, LuastatusPluginCallEnd call_end, - const char *what) +report_status(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs, const char *what) { - lua_State *L = call_begin(pd->userdata); + lua_State *L = funcs.call_begin(pd->userdata); lua_newtable(L); // L: table lua_pushstring(L, what); // L: table what lua_setfield(L, -2, "what"); // L: table - call_end(pd->userdata); + funcs.call_end(pd->userdata); } // Code below is pretty ugly and spaghetti-like. Rewrite it if you can. +static void -interact(int fd, LuastatusPluginData *pd, - LuastatusPluginCallBegin call_begin, LuastatusPluginCallEnd call_end) +interact(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs, int fd) { Priv *p = pd->priv; FILE *f = NULL; int fd_to_close = fd; char *buf = NULL; - size_t nbuf = 0; - LSStringArray kv_song = LS_STRARR_INITIALIZER; - LSStringArray kv_status = LS_STRARR_INITIALIZER; + size_t nbuf = 1024; + LSStringArray kv_song = ls_strarr_new(); + LSStringArray kv_status = ls_strarr_new(); #define GETLINE() \ do { \ @@ -242,9 +196,11 @@ interact(int fd, LuastatusPluginData *pd, } \ } while (0) -#define WRITEF(...) \ +#define WRITE(What_) \ do { \ - if (fprintf(f, __VA_ARGS__) < 0 || fflush(f) < 0) { \ + fputs(What_, f); \ + fflush(f); \ + if (ferror(f)) { \ goto io_error; \ } \ } while (0) @@ -252,11 +208,11 @@ interact(int fd, LuastatusPluginData *pd, #define UNTIL_OK(...) \ do { \ GETLINE(); \ - const ResponseType rt_ = response_type(buf); \ - if (rt_ == RESP_TYPE_OK) { \ + const MPDProtoResponseType rt_ = mpdproto_response_type(buf); \ + if (rt_ == MPDPROTO_RESP_TYPE_OK) { \ break; \ - } else if (rt_ == RESP_TYPE_ACK) { \ - LUASTATUS_ERRF(pd, "server said: %.*s", (int) dollar_strlen(buf), buf); \ + } else if (rt_ == MPDPROTO_RESP_TYPE_ACK) { \ + LS_ERRF(pd, "server said: %.*s", (int) dollar_strlen(buf), buf); \ goto error; \ } else { \ __VA_ARGS__ \ @@ -265,7 +221,7 @@ interact(int fd, LuastatusPluginData *pd, if (!(f = fdopen(fd, "r+"))) { LS_WITH_ERRSTR(str, errno, - LUASTATUS_ERRF(pd, "fdopen: %s", str); + LS_ERRF(pd, "fdopen: %s", str); ); goto error; } @@ -274,16 +230,22 @@ interact(int fd, LuastatusPluginData *pd, // read and check the greeting GETLINE(); if (strncmp(buf, "OK MPD ", 7) != 0) { - LUASTATUS_ERRF(pd, "bad greeting: %.*s", (int) dollar_strlen(buf), buf); + LS_ERRF(pd, "bad greeting: %.*s", (int) dollar_strlen(buf), buf); goto error; } // send the password, if specified if (p->password) { - WRITEF("password %s\n", p->password); + fputs("password ", f); + mpdproto_write_quoted(f, p->password); + fputc('\n', f); + fflush(f); + if (ferror(f)) { + goto io_error; + } GETLINE(); - if (response_type(buf) != RESP_TYPE_OK) { - LUASTATUS_ERRF(pd, "(password) server said: %.*s", (int) dollar_strlen(buf), buf); + if (mpdproto_response_type(buf) != MPDPROTO_RESP_TYPE_OK) { + LS_ERRF(pd, "(password) server said: %.*s", (int) dollar_strlen(buf), buf); goto error; } } @@ -291,30 +253,30 @@ interact(int fd, LuastatusPluginData *pd, sigset_t allsigs; if (sigfillset(&allsigs) < 0) { LS_WITH_ERRSTR(str, errno, - LUASTATUS_ERRF(pd, "sigfillset: %s", str); + LS_ERRF(pd, "sigfillset: %s", str); ); goto error; } if (fd >= FD_SETSIZE && !ls_timespec_is_invalid(p->timeout)) { - LUASTATUS_WARNF(pd, "connection file descriptor is too large, will not report time outs"); + LS_WARNF(pd, "connection file descriptor is too large, will not report time outs"); } fd_set fds; FD_ZERO(&fds); while (1) { - WRITEF("currentsong\n"); + WRITE("currentsong\n"); UNTIL_OK( kv_strarr_line_append(&kv_song, buf); ); - WRITEF("status\n"); + WRITE("status\n"); UNTIL_OK( kv_strarr_line_append(&kv_status, buf); ); - lua_State *L = call_begin(pd->userdata); + lua_State *L = funcs.call_begin(pd->userdata); lua_newtable(L); // L: table lua_pushstring(L, "update"); // L: table "update" @@ -330,9 +292,9 @@ interact(int fd, LuastatusPluginData *pd, ls_strarr_clear(&kv_status); lua_setfield(L, -2, "status"); // L: table - call_end(pd->userdata); + funcs.call_end(pd->userdata); - WRITEF("idle player mixer\n"); + WRITE(p->idle_str); if (fd < FD_SETSIZE && !ls_timespec_is_invalid(p->timeout)) { while (1) { @@ -340,11 +302,11 @@ interact(int fd, LuastatusPluginData *pd, int r = pselect(fd + 1, &fds, NULL, NULL, &p->timeout, &allsigs); if (r < 0) { LS_WITH_ERRSTR(str, errno, - LUASTATUS_ERRF(pd, "pselect (on connection file descriptor): %s", str); + LS_ERRF(pd, "pselect (on connection file descriptor): %s", str); ); goto error; } else if (r == 0) { - report_status(pd, call_begin, call_end, "timeout"); + report_status(pd, funcs, "timeout"); } else { break; } @@ -355,14 +317,16 @@ interact(int fd, LuastatusPluginData *pd, // do nothing ); } +#undef GETLINE +#undef WRITE #undef UNTIL_OK io_error: if (feof(f)) { - LUASTATUS_ERRF(pd, "server closed the connection"); + LS_ERRF(pd, "server closed the connection"); } else { LS_WITH_ERRSTR(str, errno, - LUASTATUS_ERRF(pd, "I/O error: %s", str); + LS_ERRF(pd, "I/O error: %s", str); ); } @@ -376,51 +340,50 @@ interact(int fd, LuastatusPluginData *pd, ls_strarr_destroy(kv_status); } +static void -run(LuastatusPluginData *pd, LuastatusPluginCallBegin call_begin, LuastatusPluginCallEnd call_end) +run(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs) { Priv *p = pd->priv; - LSWakeupFifo w; - ls_wakeup_fifo_init(&w); - sigset_t allsigs; - if (sigfillset(&allsigs) < 0) { + if (ls_wakeup_fifo_init(&w, p->retry_fifo, p->retry_in, NULL) < 0) { LS_WITH_ERRSTR(s, errno, - LUASTATUS_FATALF(pd, "sigfillset: %s", s); + LS_FATALF(pd, "ls_wakeup_fifo_init: %s", s); ); goto error; } w.fifo = p->retry_fifo; - w.timeout = &p->retry_in; - w.sigmask = &allsigs; + w.timeout = p->retry_in; char portstr[8]; - ls_xsnprintf(portstr, sizeof(portstr), "%d", p->port); + snprintf(portstr, sizeof(portstr), "%d", p->port); while (1) { - report_status(pd, call_begin, call_end, "connecting"); + report_status(pd, funcs, "connecting"); - int fd = tcp_open(pd, p->hostname, portstr); + int fd = (p->hostname && p->hostname[0] == '/') + ? socket_open(pd, p->hostname) + : tcp_open(pd, p->hostname, portstr); if (fd >= 0) { - interact(fd, pd, call_begin, call_end); + interact(pd, funcs, fd); } if (ls_timespec_is_invalid(p->retry_in)) { - LUASTATUS_FATALF(pd, "an error occurred; not retrying as requested"); + LS_FATALF(pd, "an error occurred; not retrying as requested"); goto error; } - report_status(pd, call_begin, call_end, "error"); + report_status(pd, funcs, "error"); if (ls_wakeup_fifo_open(&w) < 0) { LS_WITH_ERRSTR(s, errno, - LUASTATUS_WARNF(pd, "open: %s: %s", p->retry_fifo, s); + LS_WARNF(pd, "ls_wakeup_fifo_open: %s: %s", p->retry_fifo, s); ); } - if (ls_wakeup_fifo_pselect(&w) < 0) { + if (ls_wakeup_fifo_wait(&w) < 0) { LS_WITH_ERRSTR(s, errno, - LUASTATUS_FATALF(pd, "pselect (on retry_fifo): %s", s); + LS_FATALF(pd, "ls_wakeup_fifo_wait: %s: %s", p->retry_fifo, s); ); goto error; } @@ -430,14 +393,7 @@ run(LuastatusPluginData *pd, LuastatusPluginCallBegin call_begin, LuastatusPlugi ls_wakeup_fifo_destroy(&w); } -void -destroy(LuastatusPluginData *pd) -{ - priv_destroy(pd->priv); - free(pd->priv); -} - -LuastatusPluginIface luastatus_plugin_iface = { +LuastatusPluginIface luastatus_plugin_iface_v1 = { .init = init, .run = run, .destroy = destroy, diff --git a/plugins/mpd/mpdproto_utils.h b/plugins/mpd/mpdproto_utils.h new file mode 100644 index 0000000..ef7c9d0 --- /dev/null +++ b/plugins/mpd/mpdproto_utils.h @@ -0,0 +1,42 @@ +#ifndef proto_utils_h_ +#define proto_utils_h_ + +#include +#include + +#include "libls/compdep.h" + +typedef enum { + MPDPROTO_RESP_TYPE_OK, + MPDPROTO_RESP_TYPE_ACK, + MPDPROTO_RESP_TYPE_OTHER, +} MPDProtoResponseType; + +LS_INHEADER +MPDProtoResponseType +mpdproto_response_type(const char *buf) +{ + if (strcmp(buf, "OK\n") == 0) { + return MPDPROTO_RESP_TYPE_OK; + } + if (strncmp(buf, "ACK ", 4) == 0) { + return MPDPROTO_RESP_TYPE_ACK; + } + return MPDPROTO_RESP_TYPE_OTHER; +} + +LS_INHEADER +void +mpdproto_write_quoted(FILE *f, const char *s) +{ + fputc('"', f); + for (const char *t; (t = strchr(s, '"'));) { + fwrite(s, 1, t - s, f); + fputs("\\\"", f); + s = t + 1; + } + fputs(s, f); + fputc('"', f); +} + +#endif diff --git a/plugins/pipe/CMakeLists.txt b/plugins/pipe/CMakeLists.txt deleted file mode 100644 index cadfe5c..0000000 --- a/plugins/pipe/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -file (GLOB sources "*.c") -luastatus_add_plugin (pipe $ ${sources}) - -target_compile_definitions (pipe PUBLIC -D_POSIX_C_SOURCE=200809L) -luastatus_target_compile_with (pipe LUA) -target_include_directories (pipe PUBLIC "${PROJECT_SOURCE_DIR}") diff --git a/plugins/pipe/README.md b/plugins/pipe/README.md deleted file mode 100644 index a13ffab..0000000 --- a/plugins/pipe/README.md +++ /dev/null @@ -1,15 +0,0 @@ -This plugin spaws a process and monitors its stdout. By default, it waits for the process to print a line, and calls `cb` with that line, but a custom delimiter can be specified. - -Options -=== -* `args`: array of strings - - Executable and arguments to spawn. Must be specified and must be non-empty. - -* `delimiter`: string of length 1 - - Delimiter. Defaults to `'\n'`. Note that Lua strings are not null-terminated strings, so `'\0'` is fine. - -`cb` argument -=== -A framgent of output between two delimiters or before a first delimiter. The delimiter itself is not included. diff --git a/plugins/pipe/pipe.c b/plugins/pipe/pipe.c deleted file mode 100644 index 1090d5b..0000000 --- a/plugins/pipe/pipe.c +++ /dev/null @@ -1,142 +0,0 @@ -#include "include/plugin.h" -#include "include/plugin_logf_macros.h" -#include "include/plugin_utils.h" - -#include -#include -#include -#include -#include -#include - -#include "libls/vector.h" -#include "libls/alloc_utils.h" -#include "libls/errno_utils.h" -#include "libls/io_utils.h" -#include "libls/osdep.h" -#include "libls/lua_utils.h" - -extern char **environ; - -typedef struct { - LS_VECTOR_OF(char*) args; - char delim; -} Priv; - -void -priv_destroy(Priv *p) -{ - for (size_t i = 0; i < p->args.size; ++i) { - free(p->args.data[i]); - } - LS_VECTOR_FREE(p->args); -} - -LuastatusPluginInitResult -init(LuastatusPluginData *pd, lua_State *L) -{ - Priv *p = pd->priv = LS_XNEW(Priv, 1); - *p = (Priv) { - .args = LS_VECTOR_NEW_RESERVE(char*, 8), - .delim = '\n', - }; - - PU_MAYBE_TRAVERSE_TABLE("args", - PU_CHECK_TYPE_AT(LS_LUA_TRAVERSE_KEY, "'args' key", LUA_TNUMBER); - PU_VISIT_STR_AT(LS_LUA_TRAVERSE_VALUE, "'args' element", s, - LS_VECTOR_PUSH(p->args, ls_xstrdup(s)); - ); - ); - if (!p->args.size) { - LUASTATUS_FATALF(pd, "args not specified or empty"); - goto error; - } - LS_VECTOR_PUSH(p->args, NULL); - - PU_MAYBE_VISIT_LSTR("delimiter", s, n, - if (n != 1) { - LUASTATUS_FATALF(pd, n ? "delimiter is longer than one symbol" : "delimiter is empty"); - goto error; - } - p->delim = s[0]; - ); - - return LUASTATUS_PLUGIN_INIT_RESULT_OK; - -error: - priv_destroy(p); - free(p); - return LUASTATUS_PLUGIN_INIT_RESULT_ERR; -} - -void -run( - LuastatusPluginData *pd, - LuastatusPluginCallBegin call_begin, - LuastatusPluginCallEnd call_end) -{ - Priv *p = pd->priv; - - pid_t pid = -1; - int fd = -1; - FILE *f = NULL; - size_t nbuf = 1024; - char *buf = NULL; - - if ((pid = ls_spawnp_pipe(p->args.data[0], &fd, p->args.data)) < 0) { - LS_WITH_ERRSTR(s, errno, - LUASTATUS_FATALF(pd, "spawn failed: %s", s); - ); - fd = -1; - goto error; - } - - if (!(f = fdopen(fd, "r"))) { - LS_WITH_ERRSTR(s, errno, - LUASTATUS_FATALF(pd, "fdopen: %s", s); - ); - goto error; - } - fd = -1; - - int delim = (unsigned char) p->delim; - while (1) { - ssize_t r = getdelim(&buf, &nbuf, delim, f); - if (r < 0) { - if (feof(f)) { - LUASTATUS_FATALF(pd, "child process closed its stdout"); - } else { - LS_WITH_ERRSTR(s, errno, - LUASTATUS_FATALF(pd, "read error: %s", s); - ); - } - goto error; - } else if (r > 0) { - lua_State *L = call_begin(pd->userdata); - lua_pushlstring(L, buf, r - 1); - call_end(pd->userdata); - } - } - -error: - waitpid(pid, NULL, 0); - if (f) { - fclose(f); - } - ls_close(fd); - free(buf); -} - -static -void -destroy(LuastatusPluginData *pd) -{ - priv_destroy(pd->priv); - free(pd->priv); -} - -LuastatusPluginIface luastatus_plugin_iface = { - .init = init, - .run = run, - .destroy = destroy, -}; diff --git a/plugins/timer/timer.c b/plugins/timer/timer.c index 6fbe38c..72f3b3e 100644 --- a/plugins/timer/timer.c +++ b/plugins/timer/timer.c @@ -8,9 +8,11 @@ #include #include #include -#include "include/plugin.h" -#include "include/plugin_logf_macros.h" + +#include "include/plugin_v1.h" +#include "include/sayf_macros.h" #include "include/plugin_utils.h" + #include "libls/alloc_utils.h" #include "libls/lua_utils.h" #include "libls/osdep.h" @@ -24,13 +26,17 @@ typedef struct { char *fifo; } Priv; +static void -priv_destroy(Priv *p) +destroy(LuastatusPluginData *pd) { + Priv *p = pd->priv; free(p->fifo); + free(p); } -LuastatusPluginInitResult +static +int init(LuastatusPluginData *pd, lua_State *L) { Priv *p = pd->priv = LS_XNEW(Priv, 1); @@ -41,7 +47,7 @@ init(LuastatusPluginData *pd, lua_State *L) PU_MAYBE_VISIT_NUM("period", n, if (ls_timespec_is_invalid(p->period = ls_timespec_from_seconds(n))) { - LUASTATUS_FATALF(pd, "invalid 'period' value"); + LS_FATALF(pd, "invalid 'period' value"); goto error; } ); @@ -50,53 +56,43 @@ init(LuastatusPluginData *pd, lua_State *L) p->fifo = ls_xstrdup(s); ); - return LUASTATUS_PLUGIN_INIT_RESULT_OK; + return LUASTATUS_OK; error: - priv_destroy(p); - free(p); - return LUASTATUS_PLUGIN_INIT_RESULT_ERR; + destroy(pd); + return LUASTATUS_ERR; } +static void -run( - LuastatusPluginData *pd, - LuastatusPluginCallBegin call_begin, - LuastatusPluginCallEnd call_end) +run(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs) { Priv *p = pd->priv; - LSWakeupFifo w; - ls_wakeup_fifo_init(&w); - - sigset_t allsigs; - if (sigfillset(&allsigs) < 0) { + if (ls_wakeup_fifo_init(&w, p->fifo, p->period, NULL) < 0) { LS_WITH_ERRSTR(s, errno, - LUASTATUS_FATALF(pd, "sigfillset: %s", s); + LS_WARNF(pd, "ls_wakeup_fifo_init: %s", s); ); goto error; } - w.fifo = p->fifo; - w.timeout = &p->period; - w.sigmask = &allsigs; const char *what = "hello"; while (1) { // make a call - lua_State *L = call_begin(pd->userdata); + lua_State *L = funcs.call_begin(pd->userdata); lua_pushstring(L, what); - call_end(pd->userdata); + funcs.call_end(pd->userdata); // wait if (ls_wakeup_fifo_open(&w) < 0) { LS_WITH_ERRSTR(s, errno, - LUASTATUS_WARNF(pd, "open: %s: %s", p->fifo, s); + LS_WARNF(pd, "ls_wakeup_fifo_open: %s: %s", p->fifo, s); ); } - int r = ls_wakeup_fifo_pselect(&w); + int r = ls_wakeup_fifo_wait(&w); if (r < 0) { LS_WITH_ERRSTR(s, errno, - LUASTATUS_FATALF(pd, "pselect: %s", s); + LS_FATALF(pd, "ls_wakeup_fifo_wait: %s", s); ); goto error; } else if (r == 0) { @@ -110,14 +106,7 @@ run( ls_wakeup_fifo_destroy(&w); } -void -destroy(LuastatusPluginData *pd) -{ - priv_destroy(pd->priv); - free(pd->priv); -} - -LuastatusPluginIface luastatus_plugin_iface = { +LuastatusPluginIface luastatus_plugin_iface_v1 = { .init = init, .run = run, .destroy = destroy, diff --git a/plugins/xkb/README.md b/plugins/xkb/README.md index daa7046..c4dd958 100644 --- a/plugins/xkb/README.md +++ b/plugins/xkb/README.md @@ -16,8 +16,8 @@ A table with the following entries: * `name`: string - Group name (if number of group names reported by libxkb-file is sufficient). + Group name (if number of group names reported is sufficient). * `id`: number (in Lua 5.3, integer) - Group ID (0, 1, 2, or 3). + Group ID (0, 1, 2, or 3). diff --git a/plugins/xkb/parse_groups.c b/plugins/xkb/parse_groups.c index 79f97f8..471a14a 100644 --- a/plugins/xkb/parse_groups.c +++ b/plugins/xkb/parse_groups.c @@ -2,6 +2,7 @@ #include #include + #include "libls/strarr.h" void diff --git a/plugins/xkb/parse_groups.h b/plugins/xkb/parse_groups.h index 791de1e..2f23480 100644 --- a/plugins/xkb/parse_groups.h +++ b/plugins/xkb/parse_groups.h @@ -4,6 +4,6 @@ #include "libls/strarr.h" void -parse_groups(LSStringArray *gb, const char *layout); +parse_groups(LSStringArray *groups, const char *layout); #endif diff --git a/plugins/xkb/rules_names.c b/plugins/xkb/rules_names.c index 85a9f91..2c379ec 100644 --- a/plugins/xkb/rules_names.c +++ b/plugins/xkb/rules_names.c @@ -16,60 +16,54 @@ rules_names_load(Display *dpy, RulesNames *out) out->model = NULL; out->layout = NULL; out->options = NULL; - out->_data = NULL; - - bool ret = false; + out->data_ = NULL; Atom rules_atom = XInternAtom(dpy, NAMES_PROP_ATOM, True); if (rules_atom == None) { - goto done; + goto error; } Atom actual_type; int fmt; unsigned long ndata, bytes_after; if (XGetWindowProperty(dpy, DefaultRootWindow(dpy), rules_atom, 0L, NAMES_PROP_MAXLEN, - False, XA_STRING, &actual_type, &fmt, &ndata, &bytes_after, &out->_data) + False, XA_STRING, &actual_type, &fmt, &ndata, &bytes_after, &out->data_) != Success) { - goto done; + goto error; } if (bytes_after || actual_type != XA_STRING || fmt != 8) { - goto done; + goto error; } - const char *ptr = (const char*) out->_data; - const char *end = (const char*) out->_data + ndata; + const char *ptr = (const char *) out->data_; + const char *end = ptr + ndata; - if (ptr != end) { - out->rules = ptr; - ptr += strlen(ptr) + 1; - } - if (ptr != end) { - out->model = ptr; - ptr += strlen(ptr) + 1; - } - if (ptr != end) { - out->layout = ptr; - ptr += strlen(ptr) + 1; - } - if (ptr != end) { - out->options = ptr; - ptr += strlen(ptr) + 1; - } +#define NEXTSTR(Dest_) \ + do { \ + if (ptr != end) { \ + Dest_ = ptr; \ + ptr += strlen(ptr) + 1; \ + } \ + } while (0) - ret = true; + NEXTSTR(out->rules); + NEXTSTR(out->model); + NEXTSTR(out->layout); + NEXTSTR(out->options); -done: - if (!ret) { - rules_names_destroy(out); - } - return ret; +#undef NEXTSTR + + return true; + +error: + rules_names_destroy(out); + return false; } void rules_names_destroy(const RulesNames *rn) { - if (rn->_data) { - XFree(rn->_data); + if (rn->data_) { + XFree(rn->data_); } } diff --git a/plugins/xkb/rules_names.h b/plugins/xkb/rules_names.h index 575cac4..fd88be5 100644 --- a/plugins/xkb/rules_names.h +++ b/plugins/xkb/rules_names.h @@ -9,7 +9,7 @@ typedef struct { const char *model; const char *layout; const char *options; - unsigned char *_data; + unsigned char *data_; } RulesNames; bool diff --git a/plugins/xkb/xkb.c b/plugins/xkb/xkb.c index 7875b50..f501d32 100644 --- a/plugins/xkb/xkb.c +++ b/plugins/xkb/xkb.c @@ -1,17 +1,15 @@ -#include "include/plugin.h" -#include "include/plugin_logf_macros.h" -#include "include/plugin_utils.h" - -#include - #include #include #include - #include #include #include #include +#include + +#include "include/plugin_v1.h" +#include "include/sayf_macros.h" +#include "include/plugin_utils.h" #include "libls/alloc_utils.h" #include "libls/compdep.h" @@ -27,13 +25,15 @@ typedef struct { static void -priv_destroy(Priv *p) +destroy(LuastatusPluginData *pd) { + Priv *p = pd->priv; free(p->dpyname); + free(p); } static -LuastatusPluginInitResult +int init(LuastatusPluginData *pd, lua_State *L) { Priv *p = pd->priv = LS_XNEW(Priv, 1); @@ -48,24 +48,33 @@ init(LuastatusPluginData *pd, lua_State *L) PU_MAYBE_VISIT_NUM("device_id", n, if (n < 0) { - LUASTATUS_FATALF(pd, "device_id < 0"); + LS_FATALF(pd, "device_id < 0"); goto error; } if (n > UINT_MAX) { - LUASTATUS_FATALF(pd, "device_id > UINT_MAX"); + LS_FATALF(pd, "device_id > UINT_MAX"); goto error; } p->deviceid = n; ); - return LUASTATUS_PLUGIN_INIT_RESULT_OK; + void **ptr = pd->map_get(pd->userdata, "flag:library_used:x11"); + if (!*ptr) { + if (!XInitThreads()) { + LS_FATALF(pd, "XInitThreads failed"); + goto error; + } + *ptr = p; + } + + return LUASTATUS_OK; error: - priv_destroy(p); - free(p); - return LUASTATUS_PLUGIN_INIT_RESULT_ERR; + destroy(pd); + return LUASTATUS_ERR; } +static Display * open_dpy(LuastatusPluginData *pd, char *dpyname) { @@ -98,10 +107,11 @@ open_dpy(LuastatusPluginData *pd, char *dpyname) msg = "unknown error"; break; } - LUASTATUS_FATALF(pd, "XkbOpenDisplay failed: %s", msg); + LS_FATALF(pd, "XkbOpenDisplay failed: %s", msg); return NULL; } +static bool query_groups(Display *dpy, LSStringArray *groups) { @@ -123,10 +133,11 @@ static jmp_buf global_jmpbuf; // > error occurs (for example, the connection to the server was lost). This is // > assumed to be a fatal condition, and the called routine should not return. // > If the I/O error handler does return, the client process exits. +static int x11_io_error_handler(LS_ATTR_UNUSED_ARG Display *dpy) { - LUASTATUS_FATALF(global_pd, "X11 I/O error occurred"); + LS_FATALF(global_pd, "X11 I/O error occurred"); longjmp(global_jmpbuf, 1); } @@ -134,24 +145,21 @@ x11_io_error_handler(LS_ATTR_UNUSED_ARG Display *dpy) // // > Because this condition is not assumed to be fatal, it is acceptable for // > your error handler to return; the returned value is ignored. +static int x11_error_handler(LS_ATTR_UNUSED_ARG Display *dpy, XErrorEvent *ev) { - LUASTATUS_ERRF(global_pd, - "X11 error: serial=%ld, error_code=%d, request_code=%d, minor_code=%d", - ev->serial, ev->error_code, ev->request_code, ev->minor_code); + LS_ERRF(global_pd, "X11 error: serial=%ld, error_code=%d, request_code=%d, minor_code=%d", + ev->serial, ev->error_code, ev->request_code, ev->minor_code); return 0; } static void -run( - LuastatusPluginData *pd, - LuastatusPluginCallBegin call_begin, - LuastatusPluginCallEnd call_end) +run(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs) { Priv *p = pd->priv; - LSStringArray groups = LS_STRARR_INITIALIZER; + LSStringArray groups = ls_strarr_new(); Display *dpy = NULL; global_pd = pd; @@ -170,34 +178,34 @@ run( } if (!query_groups(dpy, &groups)) { - LUASTATUS_FATALF(pd, "query_groups failed"); + LS_FATALF(pd, "query_groups failed"); goto error; } while (1) { // query current state XkbStateRec state; if (XkbGetState(dpy, p->deviceid, &state) != Success) { - LUASTATUS_FATALF(pd, "XkbGetState failed"); + LS_FATALF(pd, "XkbGetState failed"); goto error; } // check if group is valid and possibly requery int group = state.group; if (group < 0) { - LUASTATUS_WARNF(pd, "group ID is negative (%d)", group); + LS_WARNF(pd, "group ID is negative (%d)", group); } else if ((size_t) group >= ls_strarr_size(groups)) { - LUASTATUS_WARNF(pd, "group ID (%d) is too large, requerying", group); + LS_WARNF(pd, "group ID (%d) is too large, requerying", group); if (!query_groups(dpy, &groups)) { - LUASTATUS_FATALF(pd, "query_groups failed"); + LS_FATALF(pd, "query_groups failed"); goto error; } if ((size_t) group >= ls_strarr_size(groups)) { - LUASTATUS_WARNF(pd, "group ID is still too large"); + LS_WARNF(pd, "group ID is still too large"); } } // make a call - lua_State *L = call_begin(pd->userdata); // L: - + lua_State *L = funcs.call_begin(pd->userdata); // L: - lua_newtable(L); // L: table lua_pushinteger(L, group); // L: table n lua_setfield(L, -2, "id"); // L: table @@ -207,14 +215,14 @@ run( lua_pushlstring(L, buf, nbuf); // L: table group lua_setfield(L, -2, "name"); // L: table } - call_end(pd->userdata); + funcs.call_end(pd->userdata); // wait for next event if (XkbSelectEventDetails(dpy, p->deviceid, XkbStateNotify, XkbAllStateComponentsMask, XkbGroupStateMask) == False) { - LUASTATUS_FATALF(pd, "XkbSelectEventDetails failed"); + LS_FATALF(pd, "XkbSelectEventDetails failed"); goto error; } XEvent event; @@ -231,17 +239,8 @@ run( ls_strarr_destroy(groups); } -static -void -destroy(LuastatusPluginData *pd) -{ - priv_destroy(pd->priv); - free(pd->priv); -} - -LuastatusPluginIface luastatus_plugin_iface = { +LuastatusPluginIface luastatus_plugin_iface_v1 = { .init = init, .run = run, .destroy = destroy, - .taints = (const char *[]) {"libx11", NULL}, }; diff --git a/plugins/xtitle/CMakeLists.txt b/plugins/xtitle/CMakeLists.txt index 03ac01f..053836a 100644 --- a/plugins/xtitle/CMakeLists.txt +++ b/plugins/xtitle/CMakeLists.txt @@ -6,5 +6,5 @@ luastatus_target_compile_with (xtitle LUA) target_include_directories (xtitle PUBLIC "${PROJECT_SOURCE_DIR}") find_package (PkgConfig REQUIRED) -pkg_check_modules (XCBLIBS REQUIRED xcb xcb-ewmh xcb-icccm) +pkg_check_modules (XCBLIBS REQUIRED xcb xcb-ewmh xcb-icccm xcb-event) luastatus_target_build_with (xtitle XCBLIBS) diff --git a/plugins/xtitle/xtitle.c b/plugins/xtitle/xtitle.c index d017f26..c7fc335 100644 --- a/plugins/xtitle/xtitle.c +++ b/plugins/xtitle/xtitle.c @@ -1,22 +1,19 @@ -#include "include/plugin.h" -#include "include/plugin_logf_macros.h" -#include "include/plugin_utils.h" - -#include -#include - #include #include #include #include #include - +#include #include #include - #include #include #include +#include + +#include "include/plugin_v1.h" +#include "include/sayf_macros.h" +#include "include/plugin_utils.h" #include "libls/alloc_utils.h" #include "libls/errno_utils.h" @@ -31,13 +28,15 @@ typedef struct { static void -priv_destroy(Priv *p) +destroy(LuastatusPluginData *pd) { + Priv *p = pd->priv; free(p->dpyname); + free(p); } static -LuastatusPluginInitResult +int init(LuastatusPluginData *pd, lua_State *L) { Priv *p = pd->priv = LS_XNEW(Priv, 1); @@ -54,12 +53,11 @@ init(LuastatusPluginData *pd, lua_State *L) p->visible = b; ); - return LUASTATUS_PLUGIN_INIT_RESULT_OK; + return LUASTATUS_OK; error: - priv_destroy(p); - free(p); - return LUASTATUS_PLUGIN_INIT_RESULT_ERR; + destroy(pd); + return LUASTATUS_ERR; } typedef struct { @@ -89,6 +87,7 @@ get_active_window(Data *d, xcb_window_t *win) d->ewmh,xcb_ewmh_get_active_window(d->ewmh, d->screenp), win, NULL) == 1; } +static bool push_window_title(Data *d, lua_State *L, xcb_window_t win) { @@ -101,8 +100,8 @@ push_window_title(Data *d, lua_State *L, xcb_window_t win) icccm_txt_prop.name = NULL; if (d->visible && - xcb_ewmh_get_wm_visible_name_reply(d->ewmh, xcb_ewmh_get_wm_visible_name(d->ewmh, win), - &ewmh_txt_prop, NULL) == 1 && + xcb_ewmh_get_wm_visible_name_reply( + d->ewmh, xcb_ewmh_get_wm_visible_name(d->ewmh, win), &ewmh_txt_prop, NULL) == 1 && ewmh_txt_prop.strings) { lua_pushlstring(L, ewmh_txt_prop.strings, ewmh_txt_prop.strings_len); @@ -110,8 +109,8 @@ push_window_title(Data *d, lua_State *L, xcb_window_t win) return true; } - if (xcb_ewmh_get_wm_name_reply(d->ewmh, xcb_ewmh_get_wm_name(d->ewmh, win), &ewmh_txt_prop, - NULL) == 1 && + if (xcb_ewmh_get_wm_name_reply( + d->ewmh, xcb_ewmh_get_wm_name(d->ewmh, win), &ewmh_txt_prop, NULL) == 1 && ewmh_txt_prop.strings) { lua_pushlstring(L, ewmh_txt_prop.strings, ewmh_txt_prop.strings_len); @@ -119,8 +118,8 @@ push_window_title(Data *d, lua_State *L, xcb_window_t win) return true; } - if (xcb_icccm_get_wm_name_reply(d->conn, xcb_icccm_get_wm_name(d->conn, win), &icccm_txt_prop, - NULL) == 1 && + if (xcb_icccm_get_wm_name_reply( + d->conn, xcb_icccm_get_wm_name(d->conn, win), &icccm_txt_prop, NULL) == 1 && icccm_txt_prop.name) { lua_pushlstring(L, icccm_txt_prop.name, icccm_txt_prop.name_len); @@ -131,6 +130,7 @@ push_window_title(Data *d, lua_State *L, xcb_window_t win) return false; } +static void push_arg(Data *d, lua_State *L, xcb_window_t win) { @@ -140,13 +140,14 @@ push_arg(Data *d, lua_State *L, xcb_window_t win) } // updates *win and *last_win if the active window was changed +static bool title_changed(Data *d, xcb_generic_event_t *evt, xcb_window_t *win, xcb_window_t *last_win) { if (XCB_EVENT_RESPONSE_TYPE(evt) != XCB_PROPERTY_NOTIFY) { return false; } - xcb_property_notify_event_t *pne = (xcb_property_notify_event_t*) evt; + xcb_property_notify_event_t *pne = (xcb_property_notify_event_t *) evt; if (pne->atom == d->ewmh->_NET_ACTIVE_WINDOW) { watch(d, *last_win, false); @@ -172,10 +173,7 @@ title_changed(Data *d, xcb_generic_event_t *evt, xcb_window_t *win, xcb_window_t static void -run( - LuastatusPluginData *pd, - LuastatusPluginCallBegin call_begin, - LuastatusPluginCallEnd call_end) +run(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs) { Priv *p = pd->priv; @@ -191,7 +189,7 @@ run( do { \ int ErrCodeVarName_ = xcb_connection_has_error(d.conn); \ if (ErrCodeVarName_) { \ - LUASTATUS_FATALF(pd, __VA_ARGS__); \ + LS_FATALF(pd, __VA_ARGS__); \ goto error; \ } \ } while (0) @@ -206,15 +204,15 @@ run( d.root = iter.data->root; if (xcb_ewmh_init_atoms_replies(d.ewmh, xcb_ewmh_init_atoms(d.conn, d.ewmh), NULL) == 0) { - LUASTATUS_FATALF(pd, "xcb_ewmh_init_atoms_replies failed"); + LS_FATALF(pd, "xcb_ewmh_init_atoms_replies failed"); goto error; } ewmh_inited = true; xcb_window_t win = XCB_NONE; if (get_active_window(&d, &win)) { - push_arg(&d, call_begin(pd->userdata), win); - call_end(pd->userdata); + push_arg(&d, funcs.call_begin(pd->userdata), win); + funcs.call_end(pd->userdata); } watch(&d, d.root, true); watch(&d, win, true); @@ -223,7 +221,7 @@ run( sigset_t allsigs; if (sigfillset(&allsigs) < 0) { LS_WITH_ERRSTR(s, errno, - LUASTATUS_FATALF(pd, "sigfillset: %s", s); + LS_FATALF(pd, "sigfillset: %s", s); ); goto error; } @@ -238,15 +236,15 @@ run( int nfds = pselect(fd + 1, &fds, NULL, NULL, NULL, &allsigs); if (nfds < 0) { LS_WITH_ERRSTR(s, errno, - LUASTATUS_FATALF(pd, "pselect: %s", s); + LS_FATALF(pd, "pselect: %s", s); ); goto error; } else if (nfds > 0) { xcb_generic_event_t *evt; while ((evt = xcb_poll_for_event(d.conn))) { if (title_changed(&d, evt, &win, &last_win)) { - push_arg(&d, call_begin(pd->userdata), win); - call_end(pd->userdata); + push_arg(&d, funcs.call_begin(pd->userdata), win); + funcs.call_end(pd->userdata); } free(evt); } @@ -264,15 +262,7 @@ run( free(d.ewmh); } -static -void -destroy(LuastatusPluginData *pd) -{ - priv_destroy(pd->priv); - free(pd->priv); -} - -LuastatusPluginIface luastatus_plugin_iface = { +LuastatusPluginIface luastatus_plugin_iface_v1 = { .init = init, .run = run, .destroy = destroy, diff --git a/release.sh b/release.sh index 0346c00..5879257 100755 --- a/release.sh +++ b/release.sh @@ -62,7 +62,7 @@ else T ${EDITOR:-edit} RELEASE_NOTES ask 'Commit and push?' T git add VERSION RELEASE_NOTES - T git commit -m "Release $NEW_VERSION" + T git commit -m "$release_msg" fi T git tag --force v$NEW_VERSION diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..8f68f3b --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,9 @@ +luastatus_add_plugin_noinstall (mock_plugin $ "mock_plugin.c") +target_compile_definitions (mock_plugin PUBLIC -D_POSIX_C_SOURCE=200809L) +luastatus_target_compile_with (mock_plugin LUA) +target_include_directories (mock_plugin PUBLIC "${PROJECT_SOURCE_DIR}") + +luastatus_add_barlib_noinstall (mock_barlib $ "mock_barlib.c") +target_compile_definitions (mock_barlib PUBLIC -D_POSIX_C_SOURCE=200809L) +luastatus_target_compile_with (mock_barlib LUA) +target_include_directories (mock_barlib PUBLIC "${PROJECT_SOURCE_DIR}") diff --git a/tests/dlopen.supp b/tests/dlopen.supp new file mode 100644 index 0000000..7c3d061 --- /dev/null +++ b/tests/dlopen.supp @@ -0,0 +1,43 @@ +{ + Pattern 1 + Memcheck:Leak + match-leak-kinds: reachable + fun:calloc + ... + fun:_dlerror_run + fun:dlopen@@GLIBC_2.2.5 + ... +} +{ + Pattern 2 + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:dlclose + ... +} +{ + Pattern 3 + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:openaux + ... + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_error +} +{ + Pattern 4 + Memcheck:Leak + match-leak-kinds: reachable + fun:calloc + ... + fun:openaux + ... + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_error +} diff --git a/tests/misc.sh b/tests/misc.sh new file mode 100755 index 0000000..9988e07 --- /dev/null +++ b/tests/misc.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +set -e + +LUASTATUS=(../luastatus/luastatus ${DEBUG:+-l trace}) +HANG_TIMEOUT=${TIMEOUT:-2} + +fail() +{ + printf >&2 '%s\n' '=== FAILED ===' "$@" + exit 1 +} + +assert_exits_with_code() +{ + local code=$1 rc=0 + shift + "${LUASTATUS[@]}" "$@" || rc=$? + if (( rc != code )); then + fail "Command: $*" "Expected exit code $code, found $rc" + fi +} + +assert_succeeds() +{ + assert_exits_with_code 0 "$@" +} + +assert_fails() +{ + assert_exits_with_code 1 "$@" +} + +assert_hangs() +{ + local state pid + "${LUASTATUS[@]}" "$@" & pid=$! + sleep "$HANG_TIMEOUT" + state=$(awk '$1 == "State:" { print $2 }' /proc/"$pid"/status) \ + || fail "Command: $*" "Reading process state from “/proc/$pid/status” failed" + if [[ $state != S ]]; then + fail "Command: $*" "Expected state “S”, found “$state”" + fi + echo >&2 -n "assert_hangs $*: killing and waiting for termination... " + kill "$pid" || { echo >&2; fail "Command: $*" "“kill” failed"; } + wait "$pid" || true + echo >&2 "done" +} + +assert_works() +{ + assert_hangs "$@" + assert_succeeds -e "$@" +} + +assert_fails +assert_fails /dev/null +assert_fails /dev/nil +assert_fails -e +assert_fails -TheseFlagsDoNotExist +assert_fails -l +assert_fails -l -l +assert_fails -ы +assert_fails -v + +B='-b ./mock_barlib.so' + +assert_succeeds $B -eeeeeeee -e + +assert_works $B +assert_works $B . +assert_works $B . . . . . +assert_works $B /dev/null +assert_works $B /dev/null /dev/null /dev/null + +assert_works $B <(echo 'luastatus = nil') + +assert_works $B <(cat <&2 "=== PASSED ===" diff --git a/tests/mock_barlib.c b/tests/mock_barlib.c new file mode 100644 index 0000000..270ca94 --- /dev/null +++ b/tests/mock_barlib.c @@ -0,0 +1,103 @@ +#include +#include +#include + +#include "include/barlib_v1.h" +#include "include/sayf_macros.h" + +#include "libls/alloc_utils.h" +#include "libls/cstring_utils.h" +#include "libls/parse_int.h" + +typedef struct { + size_t nwidgets; + unsigned char *widgets; + int nevents; +} Priv; + +static +void +destroy(LuastatusBarlibData *bd) +{ + Priv *p = bd->priv; + free(p->widgets); + free(p); +} + +static +int +init(LuastatusBarlibData *bd, const char *const *opts, size_t nwidgets) +{ + Priv *p = bd->priv = LS_XNEW(Priv, 1); + *p = (Priv) { + .nwidgets = nwidgets, + .widgets = LS_XNEW0(unsigned char, nwidgets), + .nevents = 0, + }; + for (const char *const *s = opts; *s; ++s) { + const char *v; + if ((v = ls_strfollow(*s, "gen_events="))) { + if ((p->nevents = ls_full_parse_uint_s(v)) < 0) { + LS_FATALF(bd, "gen_events value is not a proper integer"); + goto error; + } + } else { + LS_FATALF(bd, "unknown option: '%s'", *s); + goto error; + } + } + + return LUASTATUS_OK; +error: + destroy(bd); + return LUASTATUS_ERR; +} + +static +int +set(LuastatusBarlibData *bd, lua_State *L, size_t widget_idx) +{ + Priv *p = bd->priv; + if (!lua_isnil(L, -1)) { + LS_ERRF(bd, "got non-nil data"); + return LUASTATUS_NONFATAL_ERR; + } + ++p->widgets[widget_idx]; + return LUASTATUS_OK; +} + +static +int +set_error(LuastatusBarlibData *bd, size_t widget_idx) +{ + Priv *p = bd->priv; + --p->widgets[widget_idx]; + return LUASTATUS_OK; +} + +static +int +event_watcher(LuastatusBarlibData *bd, LuastatusBarlibEWFuncs funcs) +{ + Priv *p = bd->priv; + if (p->nevents && !p->nwidgets) { + LS_FATALF(bd, "no widgets to generate events on"); + return LUASTATUS_ERR; + } + unsigned seed = time(NULL); + for (int i = 0; i < p->nevents; ++i) { + size_t widget_idx = rand_r(&seed) % p->nwidgets; + lua_State *L = funcs.call_begin(bd->userdata, widget_idx); + lua_pushnil(L); + funcs.call_end(bd->userdata, widget_idx); + } + return LUASTATUS_NONFATAL_ERR; +} + +LuastatusBarlibIface luastatus_barlib_iface_v1 = { + .init = init, + .set = set, + .set_error = set_error, + .event_watcher = event_watcher, + .destroy = destroy, +}; diff --git a/tests/mock_plugin.c b/tests/mock_plugin.c new file mode 100644 index 0000000..ea5ac6b --- /dev/null +++ b/tests/mock_plugin.c @@ -0,0 +1,56 @@ +#include + +#include "include/plugin_v1.h" +#include "include/sayf_macros.h" +#include "include/plugin_utils.h" + +#include "libls/alloc_utils.h" + +typedef struct { + int ncalls; +} Priv; + +static +void +destroy(LuastatusPluginData *pd) +{ + Priv *p = pd->priv; + free(p); +} + +static +int +init(LuastatusPluginData *pd, lua_State *L) +{ + Priv *p = pd->priv = LS_XNEW(Priv, 1); + *p = (Priv) { + .ncalls = 0, + }; + + PU_MAYBE_VISIT_NUM("make_calls", n, + p->ncalls = n; + ); + + return LUASTATUS_OK; +error: + destroy(pd); + return LUASTATUS_ERR; +} + +static +void +run(LuastatusPluginData *pd, LuastatusPluginRunFuncs funcs) +{ + Priv *p = pd->priv; + for (int i = 0; i < p->ncalls; ++i) { + lua_State *L = funcs.call_begin(pd->userdata); + lua_pushnil(L); + funcs.call_end(pd->userdata); + } +} + +LuastatusPluginIface luastatus_plugin_iface_v1 = { + .init = init, + .run = run, + .destroy = destroy, +}; diff --git a/tests/torture.sh b/tests/torture.sh new file mode 100755 index 0000000..d9863e7 --- /dev/null +++ b/tests/torture.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +set -e + +LUASTATUS=(../luastatus/luastatus ${DEBUG:+-l trace}) + +VALGRIND=(valgrind --error-exitcode=42) + +( + cd .. + cmake -DCMAKE_BUILD_TYPE=Debug . + make -C luastatus + make -C tests +) + +run1() { + local n=$1 m=$2 sep_st=$3 + if (( sep_st )); then + local event_beg='function()' event_end='end' + else + local event_beg='[[' event_end=']]' + fi + shift 3 + "${VALGRIND[@]}" "$@" "${LUASTATUS[@]}" -e -b ./mock_barlib.so -B gen_events="$m" <(cat <<__EOF__ +n = 0 +widget = { + plugin = './mock_plugin.so', + opts = { + make_calls = $n, + }, + cb = function() + n = n + 1 + if n % 10000 == 0 then + print('--- n = ' .. n .. ' ---') + end + end, + event = $event_beg + m = (m or 0) + 1 + if m % 10000 == 0 then + print('--- m = ' .. m .. ' ---') + end + $event_end, +} +__EOF__ +) +} + +run2() { + run1 "$1" "$2" 0 "${@:3}" || return $? + run1 "$1" "$2" 1 "${@:3}" || return $? +} + +run2 10000 10000 \ + --suppressions=dlopen.supp \ + --leak-check=full \ + --show-leak-kinds=all \ + --errors-for-leak-kinds=all \ + --track-fds=yes +# Interesting options: +# --fair-sched=yes + +run2 100000 100000 \ + --tool=helgrind + +echo >&2 "=== PASSED ==="