2 Commits

Author SHA1 Message Date
ddc38ef27f fix: stupid logic bug
https://github.com/javalsai/lidm/issues/77#issuecomment-3225642519
2025-08-26 22:33:13 +02:00
grialion
b3c8d3cbea feat: exec parser for desktop entries (#78)
Feature-rich Exec= parser implementation for .desktop files
* backslash stripping (e.g '\ ' -> ' ')
* percentage stripping (e.g '%u' -> '', '%%' -> '%')
* quote string handling (e.g 'arg1 "arg 2"' -> "arg1", "arg 2")
The current implementation strips all "percentage codes", instead
of handling them.
Argument count is limited at 100.

---------

Co-authored-by: javalsai <jvssxxi@gmail.com>
2025-08-26 21:19:25 +02:00
4 changed files with 216 additions and 6 deletions

View File

@@ -16,10 +16,10 @@ ALLFLAGS=$(CFLAGS) $(CPPFLAGS) -I$(IDIR)
LIBS=-lpam LIBS=-lpam
_DEPS = version.h log.h util.h ui.h ui_state.h config.h desktop.h auth.h ofield.h efield.h keys.h users.h sessions.h chvt.h macros.h launch_state.h _DEPS = version.h log.h util.h ui.h ui_state.h config.h desktop.h desktop_exec.h auth.h ofield.h efield.h keys.h users.h sessions.h chvt.h macros.h launch_state.h
DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS)) DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS))
_OBJ = main.o log.o util.o ui.o ui_state.o config.o desktop.o auth.o ofield.o efield.o users.o sessions.o chvt.o launch_state.o _OBJ = main.o log.o util.o ui.o ui_state.o config.o desktop.o desktop_exec.o auth.o ofield.o efield.o users.o sessions.o chvt.o launch_state.o
OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ)) OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ))
INFO_GIT_REV?=$$(git describe --long --tags --always || echo '?') INFO_GIT_REV?=$$(git describe --long --tags --always || echo '?')

7
include/desktop_exec.h Normal file
View File

@@ -0,0 +1,7 @@
#ifndef DESKTOP_EXEC_H_
#define DESKTOP_EXEC_H_
int parse_exec_string(const char* exec_s, int* arg_count, char*** args);
void free_parsed_args(int arg_count, char** args);
#endif

View File

@@ -10,6 +10,8 @@
#include "auth.h" #include "auth.h"
#include "config.h" #include "config.h"
#include "desktop_exec.h"
#include "log.h"
#include "sessions.h" #include "sessions.h"
#include "ui.h" #include "ui.h"
#include "unistd.h" #include "unistd.h"
@@ -166,6 +168,22 @@ void moarEnv(char* user, struct session session, struct passwd* pw,
// NOLINTBEGIN(readability-function-cognitive-complexity) // NOLINTBEGIN(readability-function-cognitive-complexity)
bool launch(char* user, char* passwd, struct session session, void (*cb)(void), bool launch(char* user, char* passwd, struct session session, void (*cb)(void),
struct config* config) { struct config* config) {
char** desktop_exec;
int desktop_count;
if (session.type != SHELL) {
desktop_exec = NULL;
int parse_status =
parse_exec_string(session.exec, &desktop_count, &desktop_exec);
if (parse_status != 0 || desktop_count == 0 || !desktop_exec[0]) {
print_err("failure parsing exec string");
log_printf("failure parsing exec string '%s': %d\n",
session.exec ? session.exec : "NULL", parse_status);
free_parsed_args(desktop_count, desktop_exec);
return false;
}
}
struct passwd* pw = getpwnam(user); struct passwd* pw = getpwnam(user);
if (pw == NULL) { if (pw == NULL) {
print_err("could not get user info"); print_err("could not get user info");
@@ -237,8 +255,7 @@ bool launch(char* user, char* passwd, struct session session, void (*cb)(void),
*reach_session = true; *reach_session = true;
// TODO: these will be different due to TryExec // TODO: test existence of executable with TryExec
// and, Exec/TryExec might contain spaces as args
printf("\x1b[0m"); printf("\x1b[0m");
// NOLINTNEXTLINE(bugprone-branch-clone) // NOLINTNEXTLINE(bugprone-branch-clone)
if (session.type == SHELL) { if (session.type == SHELL) {
@@ -248,9 +265,12 @@ bool launch(char* user, char* passwd, struct session session, void (*cb)(void),
} else if (session.type == XORG || session.type == WAYLAND) { } else if (session.type == XORG || session.type == WAYLAND) {
clear_screen(); clear_screen();
(void)fflush(stdout); (void)fflush(stdout);
execlp(session.exec, session.exec, NULL); // NOLINTNEXTLINE
execvp(desktop_exec[0], desktop_exec);
// NOLINTNEXTLINE
free_parsed_args(desktop_count, desktop_exec);
} }
perror("execl error"); perror("exec error");
(void)fputs("failure calling session\n", stderr); (void)fputs("failure calling session\n", stderr);
} else { } else {
pid_t child_pid = (pid_t)pid; pid_t child_pid = (pid_t)pid;

183
src/desktop_exec.c Normal file
View File

@@ -0,0 +1,183 @@
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "desktop_exec.h"
// constants for exec string parsing
#define MAX_ARGS 100
// ARG_LENGTH is the initial length of a parsed argument
#define ARG_LENGTH 64
// parse Exec=/bin/prog arg1 arg2\ with\ spaces
void free_parsed_args(int arg_count, char** args) {
if (!args) return;
for (int i = 0; i < arg_count; i++) {
free(args[i]);
}
free((void*)args);
}
/* small closure-like struct to pass state to helper functions */
struct ctx {
char** pcur;
size_t* pcur_len;
size_t* pcur_cap;
char*** pargv;
int* pargc;
};
/* append_char(state, ch) -> 0 on error, 1 on success */
int append_char(struct ctx* st, char ch) {
char** pcur = st->pcur;
size_t* plen = st->pcur_len;
size_t* pcap = st->pcur_cap;
if (*plen + 1 >= *pcap) {
size_t newcap = *pcap ? (*pcap) * 2 : ARG_LENGTH;
char* cur = (char*)realloc(*pcur, newcap);
if (!cur) return 0;
*pcur = cur;
*pcap = newcap;
}
(*pcur)[(*plen)++] = ch;
return 1;
}
/* push_arg(state) -> 0 on error, 1 on success */
int push_arg(struct ctx* st) {
char** pcur = st->pcur;
size_t* plen = st->pcur_len;
size_t* pcap = st->pcur_cap;
char*** pargv = st->pargv;
int* pargc = st->pargc;
if (*pargc > MAX_ARGS) {
return 1;
}
if (!*pcur) {
char* empty = strdup("");
if (!empty) return 0;
char** na = (char**)realloc((void*)*pargv, sizeof(char*) * ((*pargc) + 1));
if (!na) {
free(empty);
return 0;
}
*pargv = na;
(*pargv)[(*pargc)++] = empty;
return 1;
}
if (!append_char(st, '\0')) return 0;
char* final = (char*)realloc(*pcur, *plen);
if (!final) final = *pcur;
*pcur = NULL;
*plen = 0;
*pcap = 0;
char** na = (char**)realloc((void*)*pargv, sizeof(char*) * ((*pargc) + 1));
if (!na) {
free(final);
return 0;
}
*pargv = na;
(*pargv)[(*pargc)++] = final;
return 1;
}
/* Return codes:
0 = success
1 = bad args
2 = memory
3 = syntax
Important: call free_parsed_args afterwards to free the passed ***args
*/
// NOLINTBEGIN(readability-function-cognitive-complexity)
int parse_exec_string(const char* exec_s, int* arg_count, char*** args) {
if (!exec_s || !args || !arg_count) return 1;
*args = NULL;
*arg_count = 0;
size_t len = strlen(exec_s);
size_t idx = 0;
char* cur = NULL;
size_t cur_len = 0;
size_t cur_cap = 0;
char** argv = NULL;
int argc = 0;
int in_quote = 0;
struct ctx ctx;
ctx.pcur = &cur;
ctx.pcur_len = &cur_len;
ctx.pcur_cap = &cur_cap;
ctx.pargv = &argv;
ctx.pargc = &argc;
while (idx < len) {
char cur_c = exec_s[idx];
if (!in_quote && (cur_c == ' ' || cur_c == '\t' || cur_c == '\n')) {
if (cur_cap) {
if (!push_arg(&ctx)) goto nomem;
}
idx++;
continue;
}
if (!in_quote && cur_c == '"') {
in_quote = 1;
idx++;
continue;
}
if (in_quote && cur_c == '"') {
in_quote = 0;
idx++;
continue;
}
if (cur_c == '\\') {
if (idx + 1 >= len) goto syntax_err;
if (!append_char(&ctx, exec_s[idx + 1])) goto nomem;
idx += 2;
continue;
}
if (cur_c == '%') {
if (idx + 1 >= len) goto syntax_err;
if (exec_s[idx + 1] == '%') {
if (!append_char(&ctx, '%')) goto nomem;
idx += 2;
continue;
}
/* drop any %X */
idx += 2;
continue;
}
if (!append_char(&ctx, cur_c)) goto nomem;
idx++;
}
if (in_quote) goto syntax_err;
if (cur_cap) {
if (!push_arg(&ctx)) goto nomem;
}
char** na = (char**)realloc((void*)argv, sizeof(char*) * (argc + 1));
if (!na) goto nomem;
argv = na;
argv[argc] = NULL;
*args = argv;
*arg_count = argc;
return 0;
nomem:
if (cur) free(cur);
free_parsed_args(argc, argv);
*args = NULL;
*arg_count = 0;
return 2;
syntax_err:
if (cur) free(cur);
free_parsed_args(argc, argv);
*args = NULL;
*arg_count = 0;
return 3;
}
// NOLINTEND(readability-function-cognitive-complexity)