lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


G'day,

[This message should be threaded, but I read the list in
Digest mode, so apologies for any thread breakage.]

A query came up in the last couple of days regarding
executing commands but protecting parameters against
unwanted modification by special character substitution
by the shell.

I'm on GNU/Linux; not sure if the following suggestion
works on Windows(R).

I use the Lua Rock "luaposix", which contains a slew of
functions to work with the Posix interface of an OS.
Subprocesses can be set up by:

    Facilitating stdin/stdout/stderr connections:
	 posix.pipe()
	 posix.dup2()
	 posix.fileno()

    Having a single-word command (Cmd), plus all
    arguments in a single table (CmdlineArgs); each of
    these are interpreted as ASCII/UTF-8 C strings -- the
    first NUL ends the string.

    Using execp() in the child process to search the
    environment path for the command (e.g. "cp"), but
    otherwise making no changes to CmdlineArgs; and

    Parent:  Close the child end of the interconnecting
    pipes, and return the child's process ID (pid), along
    with the parent-end stdin/stdout/stderr file
    descriptors; and

    Child: Close the parent end of the interconnecting
    pipes, then use posix.dup2 to redirect the child's
    stdin/stdout/stderr to each go to its respective
    parent pipe; and

    Child: Use posix.execp to execute the command,
    together with its command-line arguments.

The code for this is part of a module, and is reproduced
below:

--- Obtain pipes for stdin/stdout/stderr, fork the child process from
-- the parent, and:
--
-- (a) **Child**: Set up its end of the pipes, and execute the specified
-- command with the given arguments; or
--
-- (b) **Parent**: Close down the child end of the pipes, and hand details
-- of the child's operation, e.g. pid, fd0, fd1 and fd2, to the caller.
function M.raw_exec(Command, CmdlineArgs)
        assert(Command, "missing command name")
        CmdlineArgs = CmdlineArgs or {}

        local Pipes = GetNonstdPipes(3)
        local pid = posix.fork()
        if pid < 0 then
                return nil, "raw_exec: Unable to fork(): "
                        .. posix.errno(pid)
        end
        if pid ~= 0 then
                -- Parent: Close child end of pipes
                assert(posix.close(Pipes[1].Rd))
                assert(posix.close(Pipes[2].Wr))
                assert(posix.close(Pipes[3].Wr))

                -- Report PID and piped FDs to caller
                return pid, Pipes[1].Wr, Pipes[2].Rd, Pipes[3].Rd
        end

        -- Child: Close parent end of pipes
        assert(posix.close(Pipes[1].Wr))
        assert(posix.close(Pipes[2].Rd))
        assert(posix.close(Pipes[3].Rd))

        -- Change child's stdout/stdin/stderr to use its end of the
        -- pipes.
        assert(posix.dup2(Pipes[1].Rd, posix.fileno(io.stdin)))
        assert(posix.dup2(Pipes[2].Wr, posix.fileno(io.stdout)))
        assert(posix.dup2(Pipes[3].Wr, posix.fileno(io.stderr)))

        -- Finally, execute the requested command in the child
        -- environment.
        local _, Errstr
        _, Errstr = posix.execp(Command, CmdlineArgs)

        -- Execp should overwrite the Lua interpreter and replace it
        -- with the command, so, if Lua is still functioning here,
        -- something went terribly wrong.  Try to retrieve an error
        -- string (presumably from the command shell), and write it to
        -- the parent's stderr pipe.
        PosixUnistd.write(Pipes[3].Wr, Errstr)
        io.flush()
        os.exit(1)
end

After that, the parent uses posix.poll to handle events
from the child, including gathering output, writing input
and handling the child-process-exit (HUP signal) case.  This
is called with a timeout, so it does not consume large
portions of CPU time polling file descriptors.

At the end, the results, especially including any child
output text, stderr text, and the exit status of the
child's program, are reported to the caller.

------------------------------------

The above is lifted almost literally from a demonstration
program in the luaposix package.  I've gathered the bits
into a module I've called "PosixExec", which makes life
easier for the user:

     local posix = require("posix")  -- optional

     local PE = require("PosixExec"  -- PosixExec.lua a "pseudo-rock"

     local Result
     local CmdlineArgs
     -- Dest comes from elsewhere

     CmdlineArgs {"-p", "--", Source"}
     if Dest == "" then
          Dest = "unknown-destination"
     end
     CmdlineArgs[#CmdlineArgs + 1] = Dest

     Result = PE.exec("cp", CmdlineArgs)
     PE.ValidateRun(Result)

     print("Child stdout: ", Result.stdout)
     print("Child stderr: ", Result.stderr)

There is an ancient version of PosixExec.lua floating around
the Internet already; if this list is strongly interested,
I'll post the current (MIT Licensed) version to this list;
which 375 lines, 15077 bytes.

(The latest uses std.normalize and std.strict, to provide
some standardization in the face of different Lua versions.)

----

cheers,

sur-behoffski (Brenton Hoff)
programmer, rouse Software