This is an automated email from the git hooks/post-receive script.
teigland pushed a commit to branch master
in repository sanlock.
commit 49dae73a1244859a3f16c64741128e5c1e406276
Author: Claudio Fontana <cfontana(a)suse.de>
AuthorDate: Thu Mar 12 22:34:22 2026 +0100
sanlock: add sanlock client spawn action
"sanlock client spawn" overcomes some limitations of the similar
"sanlock client command" by allowing multiple commands to be run
in succession, all under the same lease.
The sanlock client spawn process:
- registers as the lease holder in sanlock
- acquires the lease
- uses fork() and exec() to run each -c command in succession
- stops running -c commands if one fails (non-zero child exit)
- releases the lease after any command fails or after all
commands succeed
Signed-off-by: Claudio Fontana <cfontana(a)suse.de>
---
src/main.c | 196 ++++++++++++++++++++++++++++++++++++++++++++++---
src/sanlock.8 | 15 ++++
src/sanlock_internal.h | 8 ++
3 files changed, 208 insertions(+), 11 deletions(-)
diff --git a/src/main.c b/src/main.c
index cb5a0de..d6f6fba 100644
--- a/src/main.c
+++ b/src/main.c
@@ -2325,6 +2325,7 @@ static void print_usage(void)
printf("sanlock client inq_lockspace -s LOCKSPACE\n");
printf("sanlock client rem_lockspace -s LOCKSPACE\n");
printf("sanlock client command -r RESOURCE [-h 0|1] -c <path> <args>\n");
+ printf("sanlock client spawn -r RESOURCE [-h 0|1] [-O 0|1] [-P 0|1] -c COUNT CMD [ARG...] [-c COUNT CMD [ARG...]]\n");
printf("sanlock client acquire -r RESOURCE -p|-C <id>\n");
printf("sanlock client convert -r RESOURCE -p|-C <id>\n");
printf("sanlock client release -r RESOURCE -p|-C <id>\n");
@@ -2381,6 +2382,52 @@ static void print_usage(void)
printf("\n");
}
+/*
+ * read_spawn_command - parse a single "-c COUNT CMD [ARG...]" option group
+ *
+ * Called with I pointing at the COUNT argument (the option argument
+ * that follows -c). Parses COUNT and the subsequent CMD [ARG...] words,
+ * appends a new entry to com.spawn_args, and returns the updated index
+ * pointing at the next option. The caller can then check argv[i] to decide
+ * whether another -c group follows.
+ *
+ * Returns the updated index (>= 0) on success, or a negative error code.
+ */
+static int read_spawn_command(int i, int argc, char *argv[])
+{
+ int count, k;
+
+ if (i >= argc) {
+ log_tool("spawn -c requires COUNT and CMD");
+ return -EINVAL;
+ }
+ count = strtol(argv[i], NULL, 0);
+ if (i + count >= argc) {
+ log_tool("spawn -c COUNT past the end of arguments");
+ return -EINVAL;
+ }
+
+ com.spawn_args = realloc(com.spawn_args,
+ (com.spawn_count + 1) * sizeof(struct spawn_cmd));
+ if (!com.spawn_args)
+ return -ENOMEM;
+
+ com.spawn_args[com.spawn_count].argc = count;
+ com.spawn_args[com.spawn_count].argv = malloc((count + 1) * sizeof(char *));
+ if (!com.spawn_args[com.spawn_count].argv)
+ return -ENOMEM;
+
+ for (k = 0; k < count; k++) {
+ i += 1;
+ com.spawn_args[com.spawn_count].argv[k] = strdup(argv[i]);
+ if (!com.spawn_args[com.spawn_count].argv[k])
+ return -ENOMEM;
+ }
+ com.spawn_args[com.spawn_count].argv[count] = NULL;
+ com.spawn_count += 1;
+ return i + 1;
+}
+
static int read_command_line(int argc, char *argv[])
{
char optchar;
@@ -2459,6 +2506,8 @@ static int read_command_line(int argc, char *argv[])
com.action = ACT_REM_LOCKSPACE;
else if (!strcmp(act, "command"))
com.action = ACT_COMMAND;
+ else if (!strcmp(act, "spawn"))
+ com.action = ACT_SPAWN;
else if (!strcmp(act, "acquire"))
com.action = ACT_ACQUIRE;
else if (!strcmp(act, "convert"))
@@ -2634,7 +2683,8 @@ static int read_command_line(int argc, char *argv[])
com.wait = atoi(optionarg);
break;
case 'h':
- if (com.action == ACT_GETS || com.action == ACT_CLIENT_READ || com.action == ACT_COMMAND)
+ if (com.action == ACT_GETS || com.action == ACT_CLIENT_READ ||
+ com.action == ACT_COMMAND || com.action == ACT_SPAWN)
com.get_hosts = atoi(optionarg);
else
com.high_priority = atoi(optionarg);
@@ -2737,7 +2787,22 @@ static int read_command_line(int argc, char *argv[])
break;
case 'c':
- begin_command = 1;
+ if (com.action == ACT_SPAWN) {
+ /*
+ * repeated -c COUNT CMD [ARG...] where COUNT includes the
+ * CMD itself. This lets the parser consume arguments with no
+ * ambiguity regardless of their content.
+ */
+ do {
+ i = read_spawn_command(i, argc, argv);
+ if (i < 0)
+ return i;
+ } while (i < argc && !strcmp(argv[i], "-c") && ++i);
+ /* i already points at the next option, so continue the loop */
+ continue;
+ } else {
+ begin_command = 1;
+ }
break;
case 'A':
@@ -3582,6 +3647,116 @@ static void do_client_version(void)
(proto & 0x0000FFFF));
}
+/*
+ * log_owner - for ACT_COMMAND and ACT_SPAWN, log the owner when failing to acquire
+ *
+ * Only logs when option "-h 1" is used
+ */
+static void log_owner(struct sanlk_host *owner, char *owner_name)
+{
+ if (com.get_hosts && (owner->host_id || owner_name)) {
+ log_tool("owner: host_id %llu generation %llu timestamp %llu state %s name %s",
+ (unsigned long long)owner->host_id,
+ (unsigned long long)owner->generation,
+ (unsigned long long)owner->timestamp,
+ host_state_str(owner->flags),
+ owner_name ?: "none");
+ }
+}
+
+/*
+ * do_spawn - implementation of "sanlock client spawn"
+ *
+ * Acquires the resource lease, runs a sequence of commands specified via
+ * repeated "-c COUNT CMD [ARG...]" options, then releases the lease explicitly
+ * and synchronously before returning.
+ */
+static int do_spawn(void)
+{
+ struct sanlk_host owner = { 0 };
+ char *owner_name = NULL;
+ uint32_t flags = 0;
+ pid_t pid;
+ int status, exit_code;
+ int i, fd, rv;
+
+ if (!com.spawn_count) {
+ log_tool("spawn requires at least one -c command");
+ return -EINVAL;
+ }
+
+ log_tool("register");
+ fd = sanlock_register();
+ log_tool("register done %d", fd);
+ if (fd < 0)
+ return fd;
+ /*
+ * Do not allow the daemon to use SIGTERM or SIGKILL on this process
+ * for recovery. If the lockspace lease cannot be renewed, the daemon
+ * must fence the node via the watchdog instead. This prevents the
+ * unsafe scenario of a killed supervisor with a still-running writer.
+ */
+ sanlock_restrict(fd, SANLK_RESTRICT_SIGKILL | SANLK_RESTRICT_SIGTERM);
+ flags |= com.orphan ? SANLK_ACQUIRE_ORPHAN : 0;
+
+ log_tool("acquire fd %d", fd);
+ if (com.get_hosts)
+ rv = sanlock_acquire2(fd, -1, flags, com.res_args[0], NULL, &owner, &owner_name);
+ else
+ rv = sanlock_acquire(fd, -1, flags, com.res_count, com.res_args, NULL);
+ log_tool("acquire done %d", rv);
+
+ if (rv < 0) {
+ log_owner(&owner, owner_name);
+ if (owner_name)
+ free(owner_name);
+ close(fd);
+ return rv;
+ }
+ /*
+ * Run each command sequentially under the held lease, stop on failure.
+ */
+ exit_code = 0;
+ for (i = 0; i < com.spawn_count; i++) {
+ log_tool("spawn: %s", com.spawn_args[i].argv[0]);
+ pid = fork();
+ if (pid < 0) {
+ log_tool("spawn fork failed: %s", strerror(errno));
+ exit_code = -errno;
+ break;
+ } else if (pid == 0) {
+ /* child: close the lease fd so it is not inherited */
+ close(fd);
+ execv(com.spawn_args[i].argv[0], com.spawn_args[i].argv);
+ perror("spawn execv failed");
+ exit(1);
+ }
+ /* parent: wait for child to complete before continuing */
+ if (waitpid(pid, &status, 0) < 0) {
+ log_tool("spawn waitpid failed: %s", strerror(errno));
+ exit_code = -errno;
+ break;
+ }
+ exit_code = WIFEXITED(status) ? WEXITSTATUS(status) : 1;
+ log_tool("spawn done %d", exit_code);
+ if (exit_code != 0)
+ break;
+ }
+ /*
+ * Explicit synchronous release. When sanlock_release() returns the
+ * daemon has already processed the release, unlike ACT_COMMAND which
+ * relies on POLLHUP as an asynchronous method to release the lease.
+ */
+ log_tool("release");
+ rv = sanlock_release(fd, -1, SANLK_REL_ALL, 0, NULL);
+ log_tool("release done %d", rv);
+ close(fd);
+ if (rv < 0) {
+ return rv;
+ }
+ return exit_code;
+}
+
static int do_client(void)
{
struct sanlk_host_event he;
@@ -3599,7 +3774,7 @@ static int do_client(void)
int i, fd;
int rv = 0;
- if (com.action == ACT_COMMAND || com.action == ACT_ACQUIRE) {
+ if (com.action == ACT_COMMAND || com.action == ACT_SPAWN || com.action == ACT_ACQUIRE) {
for (i = 0; i < com.res_count; i++) {
res = com.res_args[i];
@@ -3641,6 +3816,10 @@ static int do_client(void)
log_tool("shutdown done %d", rv);
break;
+ case ACT_SPAWN:
+ rv = do_spawn();
+ break;
+
case ACT_COMMAND:
log_tool("register");
fd = sanlock_register();
@@ -3660,14 +3839,9 @@ static int do_client(void)
log_tool("acquire done %d", rv);
if (rv < 0) {
- if (com.get_hosts && (owner.host_id || owner_name)) {
- log_tool("owner: host_id %llu generation %llu timestamp %llu state %s name %s",
- (unsigned long long)owner.host_id, (unsigned long long)owner.generation,
- (unsigned long long)owner.timestamp, host_state_str(owner.flags),
- owner_name ?: "none");
- if (owner_name)
- free(owner_name);
- }
+ log_owner(&owner, owner_name);
+ if (owner_name)
+ free(owner_name);
goto out;
}
diff --git a/src/sanlock.8 b/src/sanlock.8
index 983f5a9..367f040 100644
--- a/src/sanlock.8
+++ b/src/sanlock.8
@@ -194,6 +194,21 @@ Register with the sanlock daemon, acquire the specified resource lease,
and exec the command at path with args. When the command exits, the
sanlock daemon will release the lease. -c must be the final option.
+.BR "sanlock client spawn -r" " RESOURCE " \
+\fB-c\fP " " \fICOUNT\fP " " \fICMD\fP " [" \fIARG\fP "...] " \
+\fR[\fP\fB-c\fP " " \fICOUNT\fP " " \fICMD\fP " [" \fIARG\fP "...]\fR]\fP..."
+
+Register with the sanlock daemon, acquire the specified resource lease,
+fork and exec each command specified by
+.B -c
+sequentially as separate processes, checking the exit status of each
+process, and only moving to the next process on success.
+After all processes are successfully executed, or at the first failure,
+the lease is released explicitly before exiting. Use -P 1 for persistent
+locks that will not be dropped if the spawn process dies while a child
+process is running. Use -h 1 to report a conflicting lock owner. Use -O 1
+to acquire an orphan lock.
+
.BR "sanlock client acquire -r" " RESOURCE " \
\fB-p\fP " " \fIpid\fP
.br
diff --git a/src/sanlock_internal.h b/src/sanlock_internal.h
index 614ce9e..5ec3346 100644
--- a/src/sanlock_internal.h
+++ b/src/sanlock_internal.h
@@ -409,6 +409,11 @@ EXTERN struct client *client;
#define DEFAULT_MAX_SECTORS_KB_ALIGN 0 /* set it to align size */
#define DEFAULT_MAX_SECTORS_KB_NUM 1024 /* set it to num KB for all lockspaces */
+struct spawn_cmd {
+ int argc;
+ char **argv; /* argv[argc] is NULL */
+};
+
struct command_line {
int type; /* COM_ */
int action; /* ACT_ */
@@ -487,6 +492,8 @@ struct command_line {
struct sanlk_rindex rindex; /* -x RINDEX */
struct sanlk_lockspace lockspace; /* -s LOCKSPACE */
struct sanlk_resource *res_args[SANLK_MAX_RESOURCES]; /* -r RESOURCE */
+ struct spawn_cmd *spawn_args; /* -c COUNT CMD [ARG...], for ACT_SPAWN */
+ int spawn_count;
};
EXTERN struct command_line com;
@@ -548,6 +555,7 @@ enum {
ACT_CLIENT_INIT_HOST,
ACT_READ,
ACT_SET_HOST,
+ ACT_SPAWN,
};
EXTERN int external_shutdown;
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.