Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Added `filename:dirname/1`, `filename:basename/1,2`, `filename:extension/1`, `filename:rootname/1,2` and `filename:join/2`
- Added `init:get_arguments/0`
- Added `atomvm:posix_kill/2` to send a signal to a process, typically one started with `atomvm:subprocess/4`
- Added Erlang distribution over serial (uart)
- Added WASM32 JIT backend for Emscripten platform
- Added `network:wifi_scan/0,1` to ESP32 network driver to scan available APs when in sta or sta+ap mode.
Expand Down
26 changes: 25 additions & 1 deletion libs/eavmlib/src/atomvm.erl
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@
posix_closedir/1,
posix_readdir/1,
get_creation/0,
subprocess/4
subprocess/4,
posix_kill/2
]).

-export_type([
Expand Down Expand Up @@ -518,3 +519,26 @@ get_creation() ->
{ok, non_neg_integer(), posix_fd()} | {error, posix_error()}.
subprocess(_Path, _Args, _Env, _Options) ->
erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param OsPid operating system process id, as returned by `subprocess/4'
%% @param Signal signal number to send, e.g. 15 for SIGTERM
%% @returns `ok' or an error tuple
%% @doc Send a signal to a process using kill(2). Typically used to
%% terminate a process started with `subprocess/4'.
%%
%% A return value of `ok' means kill(2) accepted the request; it does
%% not guarantee that the target terminated, as the signal may be
%% caught, blocked, or ignored. Signal `0' sends no signal and merely
%% performs a POSIX existence/permission check.
%%
%% `OsPid' follows kill(2) semantics: a positive value targets a single
%% process, `0' targets the caller's process group, `-1' targets every
%% process the caller may signal, and a value less than `-1' targets the
%% process group whose id is the absolute value.
%% @end
%%-----------------------------------------------------------------------------
-spec posix_kill(OsPid :: integer(), Signal :: non_neg_integer()) ->
ok | {error, posix_error()}.
posix_kill(_OsPid, _Signal) ->
erlang:nif_error(undefined).
1 change: 1 addition & 0 deletions src/libAtomVM/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ define_if_function_exists(libAtomVM fstat "sys/stat.h" PRIVATE HAVE_FSTAT)
define_if_function_exists(libAtomVM unlink "unistd.h" PRIVATE HAVE_UNLINK)
define_if_function_exists(libAtomVM rmdir "unistd.h" PRIVATE HAVE_RMDIR)
define_if_function_exists(libAtomVM execve "unistd.h" PRIVATE HAVE_EXECVE)
define_if_function_exists(libAtomVM kill "signal.h" PRIVATE HAVE_KILL)
define_if_function_exists(libAtomVM tcgetattr "termios.h" PRIVATE HAVE_TCGETATTR)
define_if_function_exists(libAtomVM closefrom "unistd.h" PRIVATE HAVE_CLOSEFROM)
define_if_function_exists(libAtomVM getcwd "unistd.h" PUBLIC HAVE_GETCWD)
Expand Down
5 changes: 5 additions & 0 deletions src/libAtomVM/nifs.c
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,11 @@ DEFINE_MATH_NIF(tanh)
#define IF_HAVE_OPEN_CLOSE(expr) NULL
#define IF_HAVE_EXECVE(expr) NULL
#endif
#if HAVE_KILL
#define IF_HAVE_KILL(expr) (expr)
#else
#define IF_HAVE_KILL(expr) NULL
#endif
#if HAVE_OPEN && HAVE_CLOSE && HAVE_LSEEK
#define IF_HAVE_LSEEK(expr) (expr)
#else
Expand Down
1 change: 1 addition & 0 deletions src/libAtomVM/nifs.gperf
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ atomvm:posix_select_read/3, IF_HAVE_OPEN_CLOSE(&atomvm_posix_select_read_nif)
atomvm:posix_select_write/3, IF_HAVE_OPEN_CLOSE(&atomvm_posix_select_write_nif)
atomvm:posix_select_stop/1, IF_HAVE_OPEN_CLOSE(&atomvm_posix_select_stop_nif)
atomvm:subprocess/4, IF_HAVE_EXECVE(&atomvm_subprocess_nif)
atomvm:posix_kill/2, IF_HAVE_KILL(&atomvm_posix_kill_nif)
atomvm:posix_seek/3, IF_HAVE_LSEEK(&atomvm_posix_seek_nif)
atomvm:posix_pread/3, IF_HAVE_PREAD(&atomvm_posix_pread_nif)
atomvm:posix_pwrite/3, IF_HAVE_PWRITE(&atomvm_posix_pwrite_nif)
Expand Down
50 changes: 49 additions & 1 deletion src/libAtomVM/posix_nifs.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@
#include <spawn.h>
#endif

#if HAVE_KILL
#include <limits.h>
#include <signal.h>
#endif

#include "defaultatoms.h"
#include "erl_nif_priv.h"
#include "globalcontext.h"
Expand All @@ -69,7 +74,7 @@

term posix_errno_to_term(int err, GlobalContext *glb)
{
#if HAVE_OPEN && HAVE_CLOSE || defined(HAVE_CLOCK_SETTIME) || defined(HAVE_SETTIMEOFDAY)
#if HAVE_OPEN && HAVE_CLOSE || defined(HAVE_CLOCK_SETTIME) || defined(HAVE_SETTIMEOFDAY) || HAVE_KILL
// These are defined in SUSv1
term result;
switch (err) {
Expand Down Expand Up @@ -198,7 +203,7 @@
return result;
}

static term errno_to_error_tuple_maybe_gc(Context *ctx)

Check warning on line 206 in src/libAtomVM/posix_nifs.c

View workflow job for this annotation

GitHub Actions / stm32 (stm32u585ait6q)

'errno_to_error_tuple_maybe_gc' defined but not used [-Wunused-function]

Check warning on line 206 in src/libAtomVM/posix_nifs.c

View workflow job for this annotation

GitHub Actions / stm32 (stm32l476rgt6)

'errno_to_error_tuple_maybe_gc' defined but not used [-Wunused-function]

Check warning on line 206 in src/libAtomVM/posix_nifs.c

View workflow job for this annotation

GitHub Actions / stm32 (stm32g474ret6)

'errno_to_error_tuple_maybe_gc' defined but not used [-Wunused-function]

Check warning on line 206 in src/libAtomVM/posix_nifs.c

View workflow job for this annotation

GitHub Actions / stm32 (stm32wb55rg)

'errno_to_error_tuple_maybe_gc' defined but not used [-Wunused-function]

Check warning on line 206 in src/libAtomVM/posix_nifs.c

View workflow job for this annotation

GitHub Actions / stm32 (stm32f411ceu6)

'errno_to_error_tuple_maybe_gc' defined but not used [-Wunused-function]

Check warning on line 206 in src/libAtomVM/posix_nifs.c

View workflow job for this annotation

GitHub Actions / stm32 (stm32u375rgt6)

'errno_to_error_tuple_maybe_gc' defined but not used [-Wunused-function]

Check warning on line 206 in src/libAtomVM/posix_nifs.c

View workflow job for this annotation

GitHub Actions / stm32 (stm32g0b1ret6)

'errno_to_error_tuple_maybe_gc' defined but not used [-Wunused-function]

Check warning on line 206 in src/libAtomVM/posix_nifs.c

View workflow job for this annotation

GitHub Actions / stm32 (stm32f207zgt6)

'errno_to_error_tuple_maybe_gc' defined but not used [-Wunused-function]

Check warning on line 206 in src/libAtomVM/posix_nifs.c

View workflow job for this annotation

GitHub Actions / stm32 (stm32f429zit6)

'errno_to_error_tuple_maybe_gc' defined but not used [-Wunused-function]

Check warning on line 206 in src/libAtomVM/posix_nifs.c

View workflow job for this annotation

GitHub Actions / stm32 (stm32h743zit6)

'errno_to_error_tuple_maybe_gc' defined but not used [-Wunused-function]

Check warning on line 206 in src/libAtomVM/posix_nifs.c

View workflow job for this annotation

GitHub Actions / stm32 (stm32h562rgt6)

'errno_to_error_tuple_maybe_gc' defined but not used [-Wunused-function]

Check warning on line 206 in src/libAtomVM/posix_nifs.c

View workflow job for this annotation

GitHub Actions / stm32 (stm32f407vgt6)

'errno_to_error_tuple_maybe_gc' defined but not used [-Wunused-function]

Check warning on line 206 in src/libAtomVM/posix_nifs.c

View workflow job for this annotation

GitHub Actions / stm32 (stm32l562qei6)

'errno_to_error_tuple_maybe_gc' defined but not used [-Wunused-function]

Check warning on line 206 in src/libAtomVM/posix_nifs.c

View workflow job for this annotation

GitHub Actions / stm32 (stm32h743vit6)

'errno_to_error_tuple_maybe_gc' defined but not used [-Wunused-function]

Check warning on line 206 in src/libAtomVM/posix_nifs.c

View workflow job for this annotation

GitHub Actions / stm32 (stm32f746zgt6)

'errno_to_error_tuple_maybe_gc' defined but not used [-Wunused-function]
{
return error_tuple_maybe_gc(errno, ctx);
}
Expand Down Expand Up @@ -954,6 +959,43 @@
#endif
#endif

#if HAVE_KILL
static bool term_is_valid_pid(term t)
{
if (!term_is_int64(t)) {
return false;
}
// The value must round-trip through pid_t: a larger integer would be
// silently truncated and target a different (possibly own) process.
int64_t value = term_to_int64(t);
return ((int64_t) (pid_t) value) == value;
}

static bool term_is_signal_number(term t)
{
if (!term_is_int64(t)) {
return false;
}
int64_t value = term_to_int64(t);
return value >= 0 && value <= INT_MAX;
}

static term nif_atomvm_posix_kill(Context *ctx, int argc, term argv[])
{
UNUSED(argc);
VALIDATE_VALUE(argv[0], term_is_valid_pid);
VALIDATE_VALUE(argv[1], term_is_signal_number);

pid_t pid = (pid_t) term_to_int64(argv[0]);
int signo = (int) term_to_int64(argv[1]);

if (UNLIKELY(kill(pid, signo) != 0)) {
return errno_to_error_tuple_maybe_gc(ctx);
}
return OK_ATOM;
}
#endif

#if HAVE_MKFIFO
static term nif_atomvm_posix_mkfifo(Context *ctx, int argc, term argv[])
{
Expand Down Expand Up @@ -1836,6 +1878,12 @@
};
#endif
#endif
#if HAVE_KILL
const struct Nif atomvm_posix_kill_nif = {
.base.type = NIFFunctionType,
.nif_ptr = nif_atomvm_posix_kill
};
#endif
#if HAVE_OPEN && HAVE_CLOSE && HAVE_LSEEK
const struct Nif atomvm_posix_seek_nif = {
.base.type = NIFFunctionType,
Expand Down
3 changes: 3 additions & 0 deletions src/libAtomVM/posix_nifs.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ extern const struct Nif atomvm_posix_select_stop_nif;
extern const struct Nif atomvm_subprocess_nif;
#endif
#endif
#if HAVE_KILL
extern const struct Nif atomvm_posix_kill_nif;
#endif
#if HAVE_OPEN && HAVE_CLOSE && HAVE_LSEEK
extern const struct Nif atomvm_posix_seek_nif;
#endif
Expand Down
2 changes: 2 additions & 0 deletions src/platforms/esp32/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ set(CMAKE_TRY_COMPILE_TARGET_TYPE "STATIC_LIBRARY")
set(HAVE_MKFIFO "" CACHE INTERNAL "Have symbol mkfifo" FORCE)
# Likewise with EXECVE
set(HAVE_EXECVE "" CACHE INTERNAL "Have symbol execve" FORCE)
# kill links to a newlib stub but there are no OS processes to signal
set(HAVE_KILL "" CACHE INTERNAL "Have symbol kill" FORCE)
# Force HAVE_SOCKET
# Automatically detecting it requires to put too many components include dirs
# in CMAKE_REQUIRED_INCLUDES as lwip includes freetos and many esp system components
Expand Down
2 changes: 2 additions & 0 deletions src/platforms/esp32/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ set(HAVE_MKFIFO NO)
set(HAVE_MKFIFO "" CACHE INTERNAL "Have symbol mkfifo" FORCE)
set(HAVE_EXECVE NO)
set(HAVE_EXECVE "" CACHE INTERNAL "Have symbol execve" FORCE)
set(HAVE_KILL NO)
set(HAVE_KILL "" CACHE INTERNAL "Have symbol kill" FORCE)
# Force HAVE_SOCKET
# Automatically detecting it requires to put too many components include dirs
# in CMAKE_REQUIRED_INCLUDES as lwip includes freetos and many esp system components
Expand Down
2 changes: 2 additions & 0 deletions src/platforms/rp2/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ set(HAVE_MKFIFO "" CACHE INTERNAL "Have symbol mkfifo" FORCE)
set(HAVE_UNLINK "" CACHE INTERNAL "Have symbol unlink" FORCE)
# Likewise with EXECVE
set(HAVE_EXECVE "" CACHE INTERNAL "Have symbol execve" FORCE)
# kill is declared in newlib's signal.h but not linkable on bare-metal
set(HAVE_KILL "" CACHE INTERNAL "Have symbol kill" FORCE)
# getcwd is defined in newlib header but not implemented
set(HAVE_GETCWD "" CACHE INTERNAL "Have symbol getcwd" FORCE)
# pread, pwrite, fsync, ftruncate, mkdir and rmdir are
Expand Down
1 change: 1 addition & 0 deletions src/platforms/stm32/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ endif ()
set(HAVE_MKFIFO "" CACHE INTERNAL "Have symbol mkfifo" FORCE)
set(HAVE_UNLINK "" CACHE INTERNAL "Have symbol unlink" FORCE)
set(HAVE_EXECVE "" CACHE INTERNAL "Have symbol execve" FORCE)
set(HAVE_KILL "" CACHE INTERNAL "Have symbol kill" FORCE)
set(HAVE_OPEN "" CACHE INTERNAL "Have symbol open" FORCE)
set(HAVE_CLOSE "" CACHE INTERNAL "Have symbol close" FORCE)
set(HAVE_OPENDIR "" CACHE INTERNAL "Have symbol opendir" FORCE)
Expand Down
1 change: 1 addition & 0 deletions tests/libs/eavmlib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ set(ERLANG_MODULES
test_http_server
test_mdns
test_port
test_subprocess
test_timer_manager
)

Expand Down
96 changes: 96 additions & 0 deletions tests/libs/eavmlib/test_subprocess.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
%
% This file is part of AtomVM.
%
% Copyright 2026 Paul Guyot <pguyot@kallisys.net>
%
% Licensed under the Apache License, Version 2.0 (the "License");
% you may not use this file except in compliance with the License.
% You may obtain a copy of the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS,
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
% See the License for the specific language governing permissions and
% limitations under the License.
%
% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
%

-module(test_subprocess).

-export([test/0]).

test() ->
case erlang:system_info(machine) of
"ATOM" ->
test_atomvm();
_ ->
%% atomvm:subprocess/posix_kill are only available on AtomVM
ok
end.

test_atomvm() ->
case atomvm:platform() of
generic_unix ->
ok = test_posix_kill(),
ok = test_posix_kill_badarg(),
ok = test_posix_kill_esrch(),
ok;
_ ->
%% atomvm:subprocess/posix_kill are only available on generic_unix
ok
end.

%% A subprocess that would outlive the test unless killed: posix_kill
%% terminates it, observed as EOF on its stdout pipe (the write end is
%% closed when the process dies).
test_posix_kill() ->
%% exec so the subprocess pid is sleep itself, not the wrapping shell:
%% killing the shell would not reliably reach sleep, which inherits the
%% stdout pipe and would keep read_until_eof/1 blocked.
{ok, OsPid, Fd} = atomvm:subprocess(
"/bin/sh", ["sh", "-c", "exec sleep 30"], undefined, [stdout]
),
true = is_integer(OsPid),
%% SIGTERM
ok = atomvm:posix_kill(OsPid, 15),
eof = read_until_eof(Fd),
ok = atomvm:posix_close(Fd),
ok.

test_posix_kill_badarg() ->
ok = expect_badarg(fun() -> atomvm:posix_kill(not_a_pid, 15) end),
ok = expect_badarg(fun() -> atomvm:posix_kill(1, not_a_signal) end),
%% Oversized signal must be rejected rather than truncated to a valid one.
ok = expect_badarg(fun() -> atomvm:posix_kill(1, 1 bsl 40) end),
ok.

expect_badarg(Fun) ->
try Fun() of
_ -> fail
catch
error:badarg -> ok
end.

%% Signalling a pid that cannot exist fails with esrch.
test_posix_kill_esrch() ->
{error, esrch} = atomvm:posix_kill(536870911, 0),
ok.

read_until_eof(Fd) ->
case atomvm:posix_read(Fd, 64) of
eof ->
eof;
{ok, _Data} ->
read_until_eof(Fd);
{error, eagain} ->
ok = atomvm:posix_select_read(Fd, self(), undefined),
receive
{select, _FdRes, undefined, ready_input} -> ok
after 5000 ->
exit(posix_kill_eof_timeout)
end,
read_until_eof(Fd)
end.
1 change: 1 addition & 0 deletions tests/libs/eavmlib/tests.erl
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ start() ->
test_http_server,
test_mdns,
test_port,
test_subprocess,
test_timer_manager,
test_ahttp_client
]).
14 changes: 8 additions & 6 deletions tests/libs/estdlib/test_serial_dist_socat.erl
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ test_multi_port_ping(OsPid1, SocatFd1, PtyA1, PtyB, OsPid2, SocatFd2, PtyA2, Pty
start_socat() ->
{ok, OsPid, Fd} = atomvm:subprocess(
"/bin/sh",
["sh", "-c", "socat -d -d pty,raw,echo=0 pty,raw,echo=0 2>&1"],
["sh", "-c", "exec socat -d -d pty,raw,echo=0 pty,raw,echo=0 2>&1"],
undefined,
[stdout]
),
Expand All @@ -235,11 +235,13 @@ start_socat() ->
{OsPid, Fd, PtyA, PtyB}.

stop_socat(OsPid, Fd) ->
atomvm:posix_close(Fd),
{ok, _, KillFd} = atomvm:subprocess(
"/bin/kill", ["kill", integer_to_list(OsPid)], undefined, [stdout]
),
atomvm:posix_close(KillFd).
%% SIGTERM; exec in start_socat makes OsPid socat itself. Tolerate esrch so
%% an already-exited socat does not mask the real test failure.
case atomvm:posix_kill(OsPid, 15) of
ok -> ok;
{error, esrch} -> ok
end,
atomvm:posix_close(Fd).

start_peer(PtyPath, TestName) ->
AvmBin = find_atomvm_binary(),
Expand Down
Loading
Loading