Run as a daemon or service: Difference between revisions
(added Perl 6) |
m (→{{header|Wren}}: Minor tidy and rerun) |
||
(22 intermediate revisions by 10 users not shown) | |||
Line 7: | Line 7: | ||
Note that in some language implementations it may not be possible to disconnect from the terminal, and instead the process needs to be started with stdout (and stdin) redirected to files before program start. If that is the case then a helper program to set up this redirection should be written in the language itself. A shell wrapper, as would be the usual solution on Unix systems, is not appropriate. |
Note that in some language implementations it may not be possible to disconnect from the terminal, and instead the process needs to be started with stdout (and stdin) redirected to files before program start. If that is the case then a helper program to set up this redirection should be written in the language itself. A shell wrapper, as would be the usual solution on Unix systems, is not appropriate. |
||
=={{header|BASIC}}== |
|||
==={{header|FreeBASIC}}=== |
|||
{{trans|QB64}} |
|||
<syntaxhighlight lang="freebasic">ScreenLock() |
|||
Open "c:\Users\Default\Documents\myDaemon.log" For Append As #1 |
|||
'Open "myDaemon.log" For Append As #1 'Ruta de archivo relativa para la salida // Relative file path for the output |
|||
For i As Byte = 1 To 5 'Entonces esto no se ejecuta ad infinitum // So this is not executed ad infinitum |
|||
Print #1, Time + " - Running" 'Imprime en el archivo previamente abierto la hora actual del sistema (hh:mm:ss) y una nota |
|||
'Prints in the previously opened file the current system time (hh: mm: ss) and a note |
|||
Sleep 1000 'Espera 1 segundo // Wait 1 second |
|||
Next i |
|||
Close #1 'Cierra el archivo previamente abierto para escritura // Close the file previously open for writing |
|||
ScreenUnlock() |
|||
End</syntaxhighlight> |
|||
==={{header|QB64}}=== |
|||
<syntaxhighlight lang="freebasic">'Metacommands and Statements for closing console or window |
|||
'' The following line is double commented ('') as some metacommands still execute even when singularly commented (') |
|||
''$Console 'Closes the console but does not close the main window, used for whole program run |
|||
'_Console Off 'Closes console but does not close main window, used for portion of a program run with _Console On |
|||
'_ScreenHide 'Closes main windowm used for a portion of a program run with _ScreenShow |
|||
$ScreenHide 'Closes main window, used for whole program run. (NB: this metacommand should not be commented in order to execute) |
|||
Open "/home/user/Documents/myDaemon.log" For Append As #1 'Fully qualified file path for output |
|||
'Open "./myDaemon.log" For Append As #1 'Relative file path for output |
|||
For i% = 1 To 5 'So this doesn't run ad infinitum |
|||
Print #1, Time$ + " - Running" 'Prints to the file previously opened the current system time (HH:MM:SS) and a note |
|||
Sleep 1 'Waits 1 second |
|||
Next i% |
|||
Close #1 'Closes the file previously opened for writing |
|||
System</syntaxhighlight> |
|||
{{out}} |
|||
<pre>09:02:23 - Running |
|||
09:02:24 - Running |
|||
09:02:25 - Running |
|||
09:02:26 - Running |
|||
09:02:27 - Running</pre> |
|||
=={{header|C}}== |
=={{header|C}}== |
||
Line 14: | Line 54: | ||
The task also wants to redirect stdout. This program does so with [http://netbsd.gw.com/cgi-bin/man-cgi?dup2+2+NetBSD-current dup2(2)]. Had we wanted to directly write to a file, we could open the file with <code>file = fopen(argv[1], "a")</code>, and write to ''file'' instead of ''stdout''. |
The task also wants to redirect stdout. This program does so with [http://netbsd.gw.com/cgi-bin/man-cgi?dup2+2+NetBSD-current dup2(2)]. Had we wanted to directly write to a file, we could open the file with <code>file = fopen(argv[1], "a")</code>, and write to ''file'' instead of ''stdout''. |
||
< |
<syntaxhighlight lang="c">#include <err.h> |
||
#include <errno.h> |
#include <errno.h> |
||
#include <fcntl.h> |
#include <fcntl.h> |
||
Line 65: | Line 105: | ||
sleep(1); /* Can wake early or drift late. */ |
sleep(1); /* Can wake early or drift late. */ |
||
} |
} |
||
}</ |
}</syntaxhighlight> |
||
<pre>$ make dumper |
<pre>$ make dumper |
||
Line 79: | Line 119: | ||
$ pkill -x dumper |
$ pkill -x dumper |
||
$ rm dump</pre> |
$ rm dump</pre> |
||
=={{header|Go}}== |
|||
{{libheader|go-daemon}} |
|||
{{works with|Ubuntu 16.04}} |
|||
<syntaxhighlight lang="go">package main |
|||
import ( |
|||
"fmt" |
|||
"github.com/sevlyar/go-daemon" |
|||
"log" |
|||
"os" |
|||
"time" |
|||
) |
|||
func work() { |
|||
f, err := os.Create("daemon_output.txt") |
|||
if err != nil { |
|||
log.Fatal(err) |
|||
} |
|||
defer f.Close() |
|||
ticker := time.NewTicker(time.Second) |
|||
go func() { |
|||
for t := range ticker.C { |
|||
fmt.Fprintln(f, t) // writes time to file every second |
|||
} |
|||
}() |
|||
time.Sleep(60 * time.Second) // stops after 60 seconds at most |
|||
ticker.Stop() |
|||
log.Print("ticker stopped") |
|||
} |
|||
func main() { |
|||
cntxt := &daemon.Context{ |
|||
PidFileName: "pid", |
|||
PidFilePerm: 0644, |
|||
LogFileName: "log", |
|||
LogFilePerm: 0640, |
|||
WorkDir: "./", |
|||
Umask: 027, |
|||
Args: []string{"[Rosetta Code daemon example]"}, |
|||
} |
|||
d, err := cntxt.Reborn() |
|||
if err != nil { |
|||
log.Fatal("Unable to run: ", err) |
|||
} |
|||
if d != nil { |
|||
return |
|||
} |
|||
defer cntxt.Release() |
|||
log.Print("- - - - - - - - - - - - - - -") |
|||
log.Print("daemon started") |
|||
work() |
|||
}</syntaxhighlight> |
|||
{{out}} |
|||
Although the daemon will only run for a maximum of 60 seconds, we kill it after a few seconds so the output is not too long. |
|||
<pre> |
|||
$ go build daemon.go |
|||
$ ./daemon |
|||
$ kill `cat pid` |
|||
$ cat log |
|||
2019/01/31 22:14:50 - - - - - - - - - - - - - - - |
|||
2019/01/31 22:14:50 daemon started |
|||
$ cat daemon_output.txt |
|||
2019-01-31 22:14:51.641498031 +0000 GMT m=+1.089305363 |
|||
2019-01-31 22:14:52.641672923 +0000 GMT m=+2.089480618 |
|||
2019-01-31 22:14:53.641724473 +0000 GMT m=+3.089531868 |
|||
2019-01-31 22:14:54.641557151 +0000 GMT m=+4.089364696 |
|||
2019-01-31 22:14:55.641543175 +0000 GMT m=+5.089350596 |
|||
</pre> |
|||
=={{header|Nim}}== |
|||
{{trans|Python}} |
|||
This is a direct translation of the Python Posix version. |
|||
<syntaxhighlight lang="nim">import os, posix, strutils |
|||
let pid = fork() |
|||
if pid != 0: |
|||
echo "Child process detached with pid $#".format(pid) |
|||
quit QuitSuccess |
|||
let |
|||
oldStdin = stdin |
|||
oldStdout = stdout |
|||
oldStderr = stderr |
|||
stdin = open("/dev/null") |
|||
stdout = open("/tmp/dmn.log", fmWrite) |
|||
stderr = stdout |
|||
oldStdin.close() |
|||
oldStdout.close() |
|||
oldStderr.close() |
|||
discard setsid() |
|||
import times |
|||
var t = now() |
|||
while now() < t + initDuration(seconds = 10): |
|||
echo "timer running, $# seconds".format((now() - t).inSeconds) |
|||
sleep(1000)</syntaxhighlight> |
|||
{{out}} |
|||
The file "/tmp/dmn.log" contains the following data: |
|||
<pre>timer running, 0 seconds |
|||
timer running, 1 seconds |
|||
timer running, 2 seconds |
|||
timer running, 3 seconds |
|||
timer running, 4 seconds |
|||
timer running, 5 seconds |
|||
timer running, 6 seconds |
|||
timer running, 7 seconds |
|||
timer running, 8 seconds |
|||
timer running, 9 seconds</pre> |
|||
=={{header|PARI/GP}}== |
=={{header|PARI/GP}}== |
||
GP scripts cannot run in this fashion directly, but can be compiled into PARI code with <code>gp2c</code>. PARI code, whether from <code>gp2c</code> or not, can be run as a daemon just as [[#C|C]] would be. |
GP scripts cannot run in this fashion directly, but can be compiled into PARI code with <code>gp2c</code>. PARI code, whether from <code>gp2c</code> or not, can be run as a daemon just as [[#C|C]] would be. |
||
=={{header|Perl |
=={{header|Perl}}== |
||
First off, start with a standalone script and let's call it "count.pl" |
|||
<lang perl6>#!/usr/bin/env perl6 |
|||
<syntaxhighlight lang="perl"># 20211217 Perl programming solution |
|||
use strict; |
|||
use warnings; |
|||
use IO::Handle; |
|||
use File::Temp qw/ tempfile tempdir /; |
|||
my ($fh, $tempfile) = tempfile(); |
|||
my $count = 0; |
|||
open $fh, '>', $tempfile or die; |
|||
print "\n\nOutput goes to $tempfile\n"; |
|||
while (1) { |
|||
sleep 1; |
|||
print $fh "Sheep number ",$count++," just leaped over the fence ..\n"; |
|||
$fh->flush(); |
|||
}</syntaxhighlight> |
|||
Make sure it works and then we can control/run it in daemon mode with another script |
|||
<syntaxhighlight lang="perl">use warnings; |
|||
use strict; |
|||
use Daemon::Control; |
|||
exit Daemon::Control->new( |
|||
name => 'Counter daemon', |
|||
program => '/home/hkdtam/count.pl', |
|||
pid_file => '/tmp/counter.pid', |
|||
)->run;</syntaxhighlight> |
|||
{{out}} |
|||
<pre> |
|||
./daemon.pl status |
|||
Counter daemon [Not Running] |
|||
./daemon.pl start |
|||
Counter daemon [Started] |
|||
Output goes to /tmp/5wHmHKu4fN |
|||
./daemon.pl status |
|||
Counter daemon [Running] |
|||
tail -3 /tmp/5wHmHKu4fN |
|||
Sheep number 76 just leaped over the fence .. |
|||
Sheep number 77 just leaped over the fence .. |
|||
Sheep number 78 just leaped over the fence .. |
|||
cat /tmp/counter.pid |
|||
32178 |
|||
./daemon.pl restart |
|||
Counter daemon [Stopped] |
|||
Counter daemon [Started] |
|||
Output goes to /tmp/ttTTS4oGY_ |
|||
tail -3 /tmp/ttTTS4oGY_ |
|||
Sheep number 32 just leaped over the fence .. |
|||
Sheep number 33 just leaped over the fence .. |
|||
Sheep number 34 just leaped over the fence .. |
|||
cat /tmp/counter.pid |
|||
32188 |
|||
./daemon.pl stop |
|||
Counter daemon [Stopped] |
|||
./daemon.pl status |
|||
Counter daemon [Not Running] |
|||
cat /tmp/counter.pid |
|||
cat: /tmp/counter.pid: No such file or directory |
|||
</pre> |
|||
=={{header|Phix}}== |
|||
Assuming this task is simply "a program that can write to stdout(/stderr) or a file".<br> |
|||
In some cases free_console() may help, should an attached console have been incorrectly created in the first place.<br> |
|||
Be warned however that some methods of invocation and (obviously) console capture may die horribly when that is invoked inappropriately.<br>(Specifically, you can spend an awful long time waiting for a message or signal, from an agent that has just been surrepticiously murdered.)<br> |
|||
Phix does not have an implicit stdout, instead (eg) printf(1,fmt,args) writes |
|||
to stdout, whereas printf(fn,fmt,args) writes to some previously opened fn.<br> |
|||
Of course there is nothing at all to stop fn from being set to 1 for stdout, or 2 for stderr.<br> |
|||
It is also strongly against the core philosophy of Phix to have or permit anything that would somehow make printf(1,x) do something it does not look like it should be doing, especially given printf(fn,x) has no such ambiguity. Not having to change your code may sound dandy, not being able to tell what your code is doing by looking at it, isn't. |
|||
<!--<syntaxhighlight lang="phix">(notonline)--> |
|||
<span style="color: #008080;">without</span> <span style="color: #008080;">javascript_semantics</span> |
|||
<span style="color: #008080;">include</span> <span style="color: #004080;">timedate</span><span style="color: #0000FF;">.</span><span style="color: #000000;">e</span> |
|||
<span style="color: #004080;">integer</span> <span style="color: #000000;">fn</span> <span style="color: #0000FF;">=</span> <span style="color: #008080;">iff</span><span style="color: #0000FF;">(</span><span style="color: #7060A8;">rand</span><span style="color: #0000FF;">(</span><span style="color: #000000;">2</span><span style="color: #0000FF;">)==</span><span style="color: #000000;">2</span><span style="color: #0000FF;">?</span><span style="color: #7060A8;">open</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"log.txt"</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"w"</span><span style="color: #0000FF;">):</span><span style="color: #000000;">1</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #008080;">for</span> <span style="color: #000000;">i</span><span style="color: #0000FF;">=</span><span style="color: #000000;">1</span> <span style="color: #008080;">to</span> <span style="color: #000000;">5</span> <span style="color: #008080;">do</span> |
|||
<span style="color: #7060A8;">printf</span><span style="color: #0000FF;">(</span><span style="color: #000000;">fn</span><span style="color: #0000FF;">,</span><span style="color: #7060A8;">format_timedate</span><span style="color: #0000FF;">(</span><span style="color: #7060A8;">date</span><span style="color: #0000FF;">(),</span><span style="color: #008000;">"'The time is' h:mm:sspm\n"</span><span style="color: #0000FF;">))</span> |
|||
<span style="color: #7060A8;">sleep</span><span style="color: #0000FF;">(</span><span style="color: #000000;">1</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #008080;">end</span> <span style="color: #008080;">for</span> |
|||
<span style="color: #7060A8;">close</span><span style="color: #0000FF;">(</span><span style="color: #000000;">fn</span><span style="color: #0000FF;">)</span> <span style="color: #000080;font-style:italic;">-- [close(1) quietly does nothing]</span> |
|||
<!--</syntaxhighlight>--> |
|||
{{out}} |
|||
<small>(When fn is 1, or the content of log.txt when it is not.)</small> |
|||
<pre> |
|||
The time is 3:18:01pm |
|||
The time is 3:18:02pm |
|||
The time is 3:18:03pm |
|||
The time is 3:18:04pm |
|||
The time is 3:18:05pm |
|||
</pre> |
|||
=={{header|PicoLisp}}== |
|||
<syntaxhighlight lang="picolisp">(unless (fork) |
|||
(out "file.log" |
|||
(println *Pid) # First write the daemon's PID to the file |
|||
(for N 3600 # Write count for about one hour (if not killed) |
|||
(wait 1000) |
|||
(println N) |
|||
(flush) ) ) |
|||
(bye) ) # Child terminates after one hour |
|||
(bye) # Parent terminates immediately</syntaxhighlight> |
|||
=={{header|Pike}}== |
|||
<code>__FILE__</code> is a preprocessor definition that contains the current filename. |
|||
if the first argument is "daemon" the program will be restarted with stdout redirected to "foo". |
|||
<syntaxhighlight lang="pike">int main(int argc, array argv) |
|||
{ |
|||
if (sizeof(argv)>1 && argv[1] == "daemon") |
|||
{ |
|||
Stdio.File newout = Stdio.File("foo", "wc"); |
|||
Process.spawn_pike(({ __FILE__ }), ([ "stdout":newout ])); |
|||
return 1; |
|||
} |
|||
int i = 100; |
|||
while(i--) |
|||
{ |
|||
write(i+"\n"); |
|||
sleep(0.1); |
|||
} |
|||
}</syntaxhighlight> |
|||
=={{header|Python}}== |
|||
<syntaxhighlight lang="python"> |
|||
#!/usr/bin/python3 |
|||
import posix |
|||
import os |
|||
import sys |
|||
pid = posix.fork() |
|||
if pid != 0: |
|||
print("Child process detached with pid %s" % pid) |
|||
sys.exit(0) |
|||
old_stdin = sys.stdin |
|||
old_stdout = sys.stdout |
|||
old_stderr = sys.stderr |
|||
sys.stdin = open('/dev/null', 'rt') |
|||
sys.stdout = open('/tmp/dmn.log', 'wt') |
|||
sys.stderr = sys.stdout |
|||
old_stdin.close() |
|||
old_stdout.close() |
|||
old_stderr.close() |
|||
posix.setsid() |
|||
import time |
|||
t = time.time() |
|||
while time.time() < t + 10: |
|||
print("timer running, %s seconds" % str(time.time() - t)) |
|||
time.sleep(1) |
|||
</syntaxhighlight> |
|||
=={{header|Racket}}== |
|||
<syntaxhighlight lang="racket"> |
|||
#lang racket |
|||
(require ffi/unsafe) |
|||
((get-ffi-obj 'daemon #f (_fun _int _int -> _int)) 0 0) |
|||
(with-output-to-file "/tmp/foo" |
|||
(λ() (for ([i 10]) (displayln (random 1000)) (flush-output) (sleep 1)))) |
|||
</syntaxhighlight> |
|||
=={{header|Raku}}== |
|||
# Reference: |
|||
(formerly Perl 6) |
|||
<syntaxhighlight lang="raku" line># Reference: |
|||
# https://github.com/hipek8/p6-UNIX-Daemonize/ |
# https://github.com/hipek8/p6-UNIX-Daemonize/ |
||
Line 102: | Line 431: | ||
sleep(1); |
sleep(1); |
||
spurt $output, DateTime.now.Str~"\n", :append; |
spurt $output, DateTime.now.Str~"\n", :append; |
||
}</ |
}</syntaxhighlight> |
||
{{out}}<pre>root@ubuntu:~# su - david |
{{out}}<pre>root@ubuntu:~# su - david |
||
david@ubuntu:~$ ./dumper.p6 |
david@ubuntu:~$ ./dumper.p6 |
||
Line 152: | Line 481: | ||
1 |
1 |
||
</pre> |
</pre> |
||
=={{header|PicoLisp}}== |
|||
<lang PicoLisp>(unless (fork) |
|||
(out "file.log" |
|||
(println *Pid) # First write the daemon's PID to the file |
|||
(for N 3600 # Write count for about one hour (if not killed) |
|||
(wait 1000) |
|||
(println N) |
|||
(flush) ) ) |
|||
(bye) ) # Child terminates after one hour |
|||
(bye) # Parent terminates immediately</lang> |
|||
=={{header|Pike}}== |
|||
<code>__FILE__</code> is a preprocessor definition that contains the current filename. |
|||
if the first argument is "daemon" the program will be restarted with stdout redirected to "foo". |
|||
<lang Pike>int main(int argc, array argv) |
|||
{ |
|||
if (sizeof(argv)>1 && argv[1] == "daemon") |
|||
{ |
|||
Stdio.File newout = Stdio.File("foo", "wc"); |
|||
Process.spawn_pike(({ __FILE__ }), ([ "stdout":newout ])); |
|||
return 1; |
|||
} |
|||
int i = 100; |
|||
while(i--) |
|||
{ |
|||
write(i+"\n"); |
|||
sleep(0.1); |
|||
} |
|||
}</lang> |
|||
=={{header|Racket}}== |
|||
<lang racket> |
|||
#lang racket |
|||
(require ffi/unsafe) |
|||
((get-ffi-obj 'daemon #f (_fun _int _int -> _int)) 0 0) |
|||
(with-output-to-file "/tmp/foo" |
|||
(λ() (for ([i 10]) (displayln (random 1000)) (flush-output) (sleep 1)))) |
|||
</lang> |
|||
=={{header|Sidef}}== |
=={{header|Sidef}}== |
||
When the "daemon" argument is specified, a fork of the program is created with its STDOUT redirected into the file "foo.txt", and the main process is exited. |
When the "daemon" argument is specified, a fork of the program is created with its STDOUT redirected into the file "foo.txt", and the main process is exited. |
||
< |
<syntaxhighlight lang="ruby">var block = { |
||
for n in (1..100) { |
for n in (1..100) { |
||
STDOUT.say(n) |
STDOUT.say(n) |
||
Line 214: | Line 501: | ||
STDERR.say("Normal mode") |
STDERR.say("Normal mode") |
||
block.run</ |
block.run</syntaxhighlight> |
||
=={{header|Smalltalk}}== |
|||
{{works with |Smalltalk/X}} |
|||
<syntaxhighlight lang="smalltalk">#! /usr/bin/env stx --script |
|||
(pid := OperatingSystem fork) == 0 ifTrue:[ |
|||
Stdin close. |
|||
Stdout := '/tmp/daemon.log' asFilename writeStream. |
|||
Stdout buffered:false. "so we can watch it growing" |
|||
Transcript := Stderr := Stdout. |
|||
Stdout showCR: e'here is the child'. |
|||
1 to:5 do:[:n | |
|||
Delay waitForSeconds:5. |
|||
Transcript showCR: e'another hello on Transcript {n}'. |
|||
]. |
|||
] ifFalse:[ |
|||
Stdout showCR: e'forked new process pid={pid}; now exiting'. |
|||
OperatingSystem exit:0 |
|||
]</syntaxhighlight> |
|||
{{out}} |
|||
<pre>bash-3.2$ ./daemon.st |
|||
forked new process pid=77897; now exiting |
|||
bash-3.2$ |
|||
... after a few seconds: |
|||
bash-3.2$ cat /tmp/daemon.log |
|||
here is the child |
|||
another hello on Transcript 1 |
|||
another hello on Transcript 2 |
|||
another hello on Transcript 3 |
|||
another hello on Transcript 4 |
|||
another hello on Transcript 5</pre> |
|||
=={{header|Tcl}}== |
=={{header|Tcl}}== |
||
Tcl doesn't come with tools for converting the process into a daemon, but can build them easily enough. Here's the BSD daemon function mapped into a Tcl command in a package. |
Tcl doesn't come with tools for converting the process into a daemon, but can build them easily enough. Here's the BSD daemon function mapped into a Tcl command in a package. |
||
{{libheader|Critcl}} |
{{libheader|Critcl}} |
||
< |
<syntaxhighlight lang="tcl">package provide daemon 1 |
||
package require critcl |
package require critcl |
||
Line 232: | Line 550: | ||
} |
} |
||
return TCL_OK; |
return TCL_OK; |
||
}</ |
}</syntaxhighlight> |
||
These tools can then be used to solve this task: |
These tools can then be used to solve this task: |
||
< |
<syntaxhighlight lang="tcl">### Command line argument parsing |
||
if {$argc < 1} { |
if {$argc < 1} { |
||
puts "usage: $argv0 file ?message...?" |
puts "usage: $argv0 file ?message...?" |
||
Line 253: | Line 571: | ||
proc every {ms body} {eval $body; after $ms [info level 0]} |
proc every {ms body} {eval $body; after $ms [info level 0]} |
||
every 1000 {puts "[clock format [clock seconds]]: $message"} |
every 1000 {puts "[clock format [clock seconds]]: $message"} |
||
vwait forever</ |
vwait forever</syntaxhighlight> |
||
On Windows, there is a commercial extension to Tcl which allows a script to be installed as a service. Such a script would be much like the one above, but without the daemonization section as that has become a property of the runtime. |
On Windows, there is a commercial extension to Tcl which allows a script to be installed as a service. Such a script would be much like the one above, but without the daemonization section as that has become a property of the runtime. |
||
=={{header|Wren}}== |
|||
{{libheader|Wren-date}} |
|||
An embedded program but the actual daemon is of course the C host even though the process is orchestrated by the Wren code which handles any errors and prints the time every second to the file. |
|||
The following script is based on the C example though modified a little to run on Ubuntu 22.04. |
|||
<syntaxhighlight lang="wren">/* Run_as_a_daemon_or_service.wren */ |
|||
import "./date" for Date |
|||
var O_WRONLY = 1 |
|||
var O_APPEND = 1024 |
|||
var O_CREAT = 64 |
|||
var STDOUT_FILENO = 1 |
|||
class C { |
|||
foreign static fileName |
|||
foreign static open(pathName, flags, mode) |
|||
foreign static daemon(nochdir, noclose) |
|||
foreign static redirectStdout(oldfd, newfd) |
|||
foreign static close(fd) |
|||
foreign static time |
|||
foreign static sleep(seconds) |
|||
} |
|||
// gets a Date object from a Unix time in seconds |
|||
var UT2Date = Fn.new { |ut| Date.unixEpoch.addSeconds(ut) } |
|||
Date.default = "ddd| |mmm| |dd| |hh|:|MM|:|ss| |yyyy" // default format for printing |
|||
// open file before becoming a daemon |
|||
var fd = C.open(C.fileName, O_WRONLY | O_APPEND | O_CREAT, 438) |
|||
if (fd < 0) { |
|||
System.print("Error opening %(C.fileName)") |
|||
return |
|||
} |
|||
// become a daemon |
|||
if (C.daemon(0, 0) < 0) { |
|||
System.print("Error creating daemon.") |
|||
return |
|||
} |
|||
// redirect stdout |
|||
if (C.redirectStdout(fd, STDOUT_FILENO) < 0) { |
|||
System.print("Error redirecting stdout.") |
|||
return |
|||
} |
|||
// close file |
|||
if (C.close(fd) < 0) { |
|||
System.print("Error closing %(C.fileName)") |
|||
return |
|||
} |
|||
// dump time every second |
|||
while (true) { |
|||
System.print(UT2Date.call(C.time)) |
|||
C.sleep(1) |
|||
}</syntaxhighlight> |
|||
<br> |
|||
We now embed this in the following C program, compile and run it. |
|||
<syntaxhighlight lang="c">/* gcc Run_as_a_daemon_or_service.c -o dumper -lwren -lm */ |
|||
#include <fcntl.h> |
|||
#include <stdlib.h> |
|||
#include <stdio.h> |
|||
#include <string.h> |
|||
#include <time.h> |
|||
#include <unistd.h> |
|||
#include "wren.h" |
|||
char *fileName; |
|||
void C_fileName(WrenVM* vm) { |
|||
wrenSetSlotString(vm, 0, fileName); |
|||
} |
|||
void C_open(WrenVM* vm) { |
|||
const char *pathName = wrenGetSlotString(vm, 1); |
|||
int flags = (int)wrenGetSlotDouble(vm, 2); |
|||
mode_t mode = (mode_t)wrenGetSlotDouble(vm, 3); |
|||
int fd = open(pathName, flags, mode); |
|||
wrenSetSlotDouble(vm, 0, (double)fd); |
|||
} |
|||
void C_daemon(WrenVM* vm) { |
|||
int nochdir = (int)wrenGetSlotDouble(vm, 1); |
|||
int noclose = (int)wrenGetSlotDouble(vm, 2); |
|||
int d = daemon(nochdir, noclose); |
|||
wrenSetSlotDouble(vm, 0, (double)d); |
|||
} |
|||
void C_redirectStdout(WrenVM* vm) { |
|||
int oldfd = (int)wrenGetSlotDouble(vm, 1); |
|||
int newfd = (int)wrenGetSlotDouble(vm, 2); |
|||
newfd = dup2(oldfd, newfd); |
|||
wrenSetSlotDouble(vm, 0, (double)newfd); |
|||
} |
|||
void C_close(WrenVM* vm) { |
|||
int fd = (int)wrenGetSlotDouble(vm, 1); |
|||
int ret = close(fd); |
|||
wrenSetSlotDouble(vm, 0, (double)ret); |
|||
} |
|||
void C_time(WrenVM* vm) { |
|||
time_t t = time(NULL); |
|||
wrenSetSlotDouble(vm, 0, (double)t); |
|||
} |
|||
void C_sleep(WrenVM* vm) { |
|||
unsigned int seconds = (unsigned int) wrenGetSlotDouble(vm, 1); |
|||
unsigned int ret = sleep(seconds); |
|||
wrenSetSlotDouble(vm, 0, (double)ret); |
|||
} |
|||
WrenForeignMethodFn bindForeignMethod( |
|||
WrenVM* vm, |
|||
const char* module, |
|||
const char* className, |
|||
bool isStatic, |
|||
const char* signature) { |
|||
if (strcmp(module, "main") == 0) { |
|||
if (strcmp(className, "C") == 0) { |
|||
if (isStatic && strcmp(signature, "fileName") == 0) return C_fileName; |
|||
if (isStatic && strcmp(signature, "open(_,_,_)") == 0) return C_open; |
|||
if (isStatic && strcmp(signature, "daemon(_,_)") == 0) return C_daemon; |
|||
if (isStatic && strcmp(signature, "redirectStdout(_,_)") == 0) return C_redirectStdout; |
|||
if (isStatic && strcmp(signature, "close(_)") == 0) return C_close; |
|||
if (isStatic && strcmp(signature, "time") == 0) return C_time; |
|||
if (isStatic && strcmp(signature, "sleep(_)") == 0) return C_sleep; |
|||
} |
|||
} |
|||
return NULL; |
|||
} |
|||
static void writeFn(WrenVM* vm, const char* text) { |
|||
printf("%s", text); |
|||
fflush(stdout); // as we're redirecting stdout to a file |
|||
} |
|||
void errorFn(WrenVM* vm, WrenErrorType errorType, const char* module, const int line, const char* msg) { |
|||
switch (errorType) { |
|||
case WREN_ERROR_COMPILE: |
|||
printf("[%s line %d] [Error] %s\n", module, line, msg); |
|||
break; |
|||
case WREN_ERROR_STACK_TRACE: |
|||
printf("[%s line %d] in %s\n", module, line, msg); |
|||
break; |
|||
case WREN_ERROR_RUNTIME: |
|||
printf("[Runtime Error] %s\n", msg); |
|||
break; |
|||
} |
|||
} |
|||
char *readFile(const char *fileName) { |
|||
FILE *f = fopen(fileName, "r"); |
|||
fseek(f, 0, SEEK_END); |
|||
long fsize = ftell(f); |
|||
rewind(f); |
|||
char *script = malloc(fsize + 1); |
|||
size_t ret = fread(script, 1, fsize, f); |
|||
if (ret != fsize) printf("Error reading %s\n", fileName); |
|||
fclose(f); |
|||
script[fsize] = 0; |
|||
return script; |
|||
} |
|||
static void loadModuleComplete(WrenVM* vm, const char* module, WrenLoadModuleResult result) { |
|||
if( result.source) free((void*)result.source); |
|||
} |
|||
WrenLoadModuleResult loadModule(WrenVM* vm, const char* name) { |
|||
WrenLoadModuleResult result = {0}; |
|||
if (strcmp(name, "random") != 0 && strcmp(name, "meta") != 0) { |
|||
result.onComplete = loadModuleComplete; |
|||
char fullName[strlen(name) + 6]; |
|||
strcpy(fullName, name); |
|||
strcat(fullName, ".wren"); |
|||
result.source = readFile(fullName); |
|||
} |
|||
return result; |
|||
} |
|||
int main(int argc, char **argv) { |
|||
fileName = argv[1]; |
|||
WrenConfiguration config; |
|||
wrenInitConfiguration(&config); |
|||
config.writeFn = &writeFn; |
|||
config.errorFn = &errorFn; |
|||
config.bindForeignMethodFn = &bindForeignMethod; |
|||
config.loadModuleFn = &loadModule; |
|||
WrenVM* vm = wrenNewVM(&config); |
|||
const char* module = "main"; |
|||
const char* fileName = "Run_as_a_daemon_or_service.wren"; |
|||
char *script = readFile(fileName); |
|||
WrenInterpretResult result = wrenInterpret(vm, module, script); |
|||
switch (result) { |
|||
case WREN_RESULT_COMPILE_ERROR: |
|||
printf("Compile Error!\n"); |
|||
break; |
|||
case WREN_RESULT_RUNTIME_ERROR: |
|||
printf("Runtime Error!\n"); |
|||
break; |
|||
case WREN_RESULT_SUCCESS: |
|||
break; |
|||
} |
|||
wrenFreeVM(vm); |
|||
free(script); |
|||
return 0; |
|||
}</syntaxhighlight> |
|||
{{out}} |
|||
Sample output: |
|||
<pre> |
|||
$ ./dumper dump |
|||
$ tail -f dump |
|||
Sat Feb 03 20:02:20 2024 |
|||
Sat Feb 03 20:02:21 2024 |
|||
Sat Feb 03 20:02:22 2024 |
|||
Sat Feb 03 20:02:23 2024 |
|||
Sat Feb 03 20:02:24 2024 |
|||
Sat Feb 03 20:02:25 2024 |
|||
Sat Feb 03 20:02:26 2024 |
|||
Sat Feb 03 20:02:27 2024 |
|||
Sat Feb 03 20:02:28 2024 |
|||
Sat Feb 03 20:02:29 2024 |
|||
^C |
|||
$ pkill -x dumper |
|||
$ rm dump |
|||
</pre> |
|||
{{omit from|Brlcad}} |
{{omit from|Brlcad}} |
Latest revision as of 20:06, 3 February 2024
A daemon is a service that runs in the background independent of a users login session.
Demonstrate how a program disconnects from the terminal to run as a daemon in the background.
Write a small program that writes a message roughly once a second to its stdout which should be redirected to a file.
Note that in some language implementations it may not be possible to disconnect from the terminal, and instead the process needs to be started with stdout (and stdin) redirected to files before program start. If that is the case then a helper program to set up this redirection should be written in the language itself. A shell wrapper, as would be the usual solution on Unix systems, is not appropriate.
BASIC
FreeBASIC
ScreenLock()
Open "c:\Users\Default\Documents\myDaemon.log" For Append As #1
'Open "myDaemon.log" For Append As #1 'Ruta de archivo relativa para la salida // Relative file path for the output
For i As Byte = 1 To 5 'Entonces esto no se ejecuta ad infinitum // So this is not executed ad infinitum
Print #1, Time + " - Running" 'Imprime en el archivo previamente abierto la hora actual del sistema (hh:mm:ss) y una nota
'Prints in the previously opened file the current system time (hh: mm: ss) and a note
Sleep 1000 'Espera 1 segundo // Wait 1 second
Next i
Close #1 'Cierra el archivo previamente abierto para escritura // Close the file previously open for writing
ScreenUnlock()
End
QB64
'Metacommands and Statements for closing console or window
'' The following line is double commented ('') as some metacommands still execute even when singularly commented (')
''$Console 'Closes the console but does not close the main window, used for whole program run
'_Console Off 'Closes console but does not close main window, used for portion of a program run with _Console On
'_ScreenHide 'Closes main windowm used for a portion of a program run with _ScreenShow
$ScreenHide 'Closes main window, used for whole program run. (NB: this metacommand should not be commented in order to execute)
Open "/home/user/Documents/myDaemon.log" For Append As #1 'Fully qualified file path for output
'Open "./myDaemon.log" For Append As #1 'Relative file path for output
For i% = 1 To 5 'So this doesn't run ad infinitum
Print #1, Time$ + " - Running" 'Prints to the file previously opened the current system time (HH:MM:SS) and a note
Sleep 1 'Waits 1 second
Next i%
Close #1 'Closes the file previously opened for writing
System
- Output:
09:02:23 - Running 09:02:24 - Running 09:02:25 - Running 09:02:26 - Running 09:02:27 - Running
C
BSD provides a convenient daemon(3) function. GNU libc also provides daemon(3), but POSIX omits it, so it is not portable. Other BSDisms in this program are __progname and <err.h>.
The task also wants to redirect stdout. This program does so with dup2(2). Had we wanted to directly write to a file, we could open the file with file = fopen(argv[1], "a")
, and write to file instead of stdout.
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>
int
main(int argc, char **argv)
{
extern char *__progname;
time_t clock;
int fd;
if (argc != 2) {
fprintf(stderr, "usage: %s file\n", __progname);
exit(1);
}
/* Open the file before becoming a daemon. */
fd = open(argv[1], O_WRONLY | O_APPEND | O_CREAT, 0666);
if (fd < 0)
err(1, argv[1]);
/*
* Become a daemon. Lose terminal, current working directory,
* stdin, stdout, stderr.
*/
if (daemon(0, 0) < 0)
err(1, "daemon");
/* Redirect stdout. */
if (dup2(fd, STDOUT_FILENO) < 0) {
syslog(LOG_ERR, "dup2: %s", strerror(errno));
exit(1);
}
close(fd);
/* Dump clock. */
for (;;) {
time(&clock);
fputs(ctime(&clock), stdout);
if (fflush(stdout) == EOF) {
syslog(LOG_ERR, "%s: %s", argv[1], strerror(errno));
exit(1);
}
sleep(1); /* Can wake early or drift late. */
}
}
$ make dumper cc -O2 -pipe -o dumper dumper.c $ ./dumper dump $ tail -f dump Fri Nov 18 13:50:41 2011 Fri Nov 18 13:50:42 2011 Fri Nov 18 13:50:43 2011 Fri Nov 18 13:50:44 2011 Fri Nov 18 13:50:45 2011 ^C $ pkill -x dumper $ rm dump
Go
package main
import (
"fmt"
"github.com/sevlyar/go-daemon"
"log"
"os"
"time"
)
func work() {
f, err := os.Create("daemon_output.txt")
if err != nil {
log.Fatal(err)
}
defer f.Close()
ticker := time.NewTicker(time.Second)
go func() {
for t := range ticker.C {
fmt.Fprintln(f, t) // writes time to file every second
}
}()
time.Sleep(60 * time.Second) // stops after 60 seconds at most
ticker.Stop()
log.Print("ticker stopped")
}
func main() {
cntxt := &daemon.Context{
PidFileName: "pid",
PidFilePerm: 0644,
LogFileName: "log",
LogFilePerm: 0640,
WorkDir: "./",
Umask: 027,
Args: []string{"[Rosetta Code daemon example]"},
}
d, err := cntxt.Reborn()
if err != nil {
log.Fatal("Unable to run: ", err)
}
if d != nil {
return
}
defer cntxt.Release()
log.Print("- - - - - - - - - - - - - - -")
log.Print("daemon started")
work()
}
- Output:
Although the daemon will only run for a maximum of 60 seconds, we kill it after a few seconds so the output is not too long.
$ go build daemon.go $ ./daemon $ kill `cat pid` $ cat log 2019/01/31 22:14:50 - - - - - - - - - - - - - - - 2019/01/31 22:14:50 daemon started $ cat daemon_output.txt 2019-01-31 22:14:51.641498031 +0000 GMT m=+1.089305363 2019-01-31 22:14:52.641672923 +0000 GMT m=+2.089480618 2019-01-31 22:14:53.641724473 +0000 GMT m=+3.089531868 2019-01-31 22:14:54.641557151 +0000 GMT m=+4.089364696 2019-01-31 22:14:55.641543175 +0000 GMT m=+5.089350596
Nim
This is a direct translation of the Python Posix version.
import os, posix, strutils
let pid = fork()
if pid != 0:
echo "Child process detached with pid $#".format(pid)
quit QuitSuccess
let
oldStdin = stdin
oldStdout = stdout
oldStderr = stderr
stdin = open("/dev/null")
stdout = open("/tmp/dmn.log", fmWrite)
stderr = stdout
oldStdin.close()
oldStdout.close()
oldStderr.close()
discard setsid()
import times
var t = now()
while now() < t + initDuration(seconds = 10):
echo "timer running, $# seconds".format((now() - t).inSeconds)
sleep(1000)
- Output:
The file "/tmp/dmn.log" contains the following data:
timer running, 0 seconds timer running, 1 seconds timer running, 2 seconds timer running, 3 seconds timer running, 4 seconds timer running, 5 seconds timer running, 6 seconds timer running, 7 seconds timer running, 8 seconds timer running, 9 seconds
PARI/GP
GP scripts cannot run in this fashion directly, but can be compiled into PARI code with gp2c
. PARI code, whether from gp2c
or not, can be run as a daemon just as C would be.
Perl
First off, start with a standalone script and let's call it "count.pl"
# 20211217 Perl programming solution
use strict;
use warnings;
use IO::Handle;
use File::Temp qw/ tempfile tempdir /;
my ($fh, $tempfile) = tempfile();
my $count = 0;
open $fh, '>', $tempfile or die;
print "\n\nOutput goes to $tempfile\n";
while (1) {
sleep 1;
print $fh "Sheep number ",$count++," just leaped over the fence ..\n";
$fh->flush();
}
Make sure it works and then we can control/run it in daemon mode with another script
use warnings;
use strict;
use Daemon::Control;
exit Daemon::Control->new(
name => 'Counter daemon',
program => '/home/hkdtam/count.pl',
pid_file => '/tmp/counter.pid',
)->run;
- Output:
./daemon.pl status Counter daemon [Not Running] ./daemon.pl start Counter daemon [Started] Output goes to /tmp/5wHmHKu4fN ./daemon.pl status Counter daemon [Running] tail -3 /tmp/5wHmHKu4fN Sheep number 76 just leaped over the fence .. Sheep number 77 just leaped over the fence .. Sheep number 78 just leaped over the fence .. cat /tmp/counter.pid 32178 ./daemon.pl restart Counter daemon [Stopped] Counter daemon [Started] Output goes to /tmp/ttTTS4oGY_ tail -3 /tmp/ttTTS4oGY_ Sheep number 32 just leaped over the fence .. Sheep number 33 just leaped over the fence .. Sheep number 34 just leaped over the fence .. cat /tmp/counter.pid 32188 ./daemon.pl stop Counter daemon [Stopped] ./daemon.pl status Counter daemon [Not Running] cat /tmp/counter.pid cat: /tmp/counter.pid: No such file or directory
Phix
Assuming this task is simply "a program that can write to stdout(/stderr) or a file".
In some cases free_console() may help, should an attached console have been incorrectly created in the first place.
Be warned however that some methods of invocation and (obviously) console capture may die horribly when that is invoked inappropriately.
(Specifically, you can spend an awful long time waiting for a message or signal, from an agent that has just been surrepticiously murdered.)
Phix does not have an implicit stdout, instead (eg) printf(1,fmt,args) writes
to stdout, whereas printf(fn,fmt,args) writes to some previously opened fn.
Of course there is nothing at all to stop fn from being set to 1 for stdout, or 2 for stderr.
It is also strongly against the core philosophy of Phix to have or permit anything that would somehow make printf(1,x) do something it does not look like it should be doing, especially given printf(fn,x) has no such ambiguity. Not having to change your code may sound dandy, not being able to tell what your code is doing by looking at it, isn't.
without javascript_semantics include timedate.e integer fn = iff(rand(2)==2?open("log.txt","w"):1) for i=1 to 5 do printf(fn,format_timedate(date(),"'The time is' h:mm:sspm\n")) sleep(1) end for close(fn) -- [close(1) quietly does nothing]
- Output:
(When fn is 1, or the content of log.txt when it is not.)
The time is 3:18:01pm The time is 3:18:02pm The time is 3:18:03pm The time is 3:18:04pm The time is 3:18:05pm
PicoLisp
(unless (fork)
(out "file.log"
(println *Pid) # First write the daemon's PID to the file
(for N 3600 # Write count for about one hour (if not killed)
(wait 1000)
(println N)
(flush) ) )
(bye) ) # Child terminates after one hour
(bye) # Parent terminates immediately
Pike
__FILE__
is a preprocessor definition that contains the current filename.
if the first argument is "daemon" the program will be restarted with stdout redirected to "foo".
int main(int argc, array argv)
{
if (sizeof(argv)>1 && argv[1] == "daemon")
{
Stdio.File newout = Stdio.File("foo", "wc");
Process.spawn_pike(({ __FILE__ }), ([ "stdout":newout ]));
return 1;
}
int i = 100;
while(i--)
{
write(i+"\n");
sleep(0.1);
}
}
Python
#!/usr/bin/python3
import posix
import os
import sys
pid = posix.fork()
if pid != 0:
print("Child process detached with pid %s" % pid)
sys.exit(0)
old_stdin = sys.stdin
old_stdout = sys.stdout
old_stderr = sys.stderr
sys.stdin = open('/dev/null', 'rt')
sys.stdout = open('/tmp/dmn.log', 'wt')
sys.stderr = sys.stdout
old_stdin.close()
old_stdout.close()
old_stderr.close()
posix.setsid()
import time
t = time.time()
while time.time() < t + 10:
print("timer running, %s seconds" % str(time.time() - t))
time.sleep(1)
Racket
#lang racket
(require ffi/unsafe)
((get-ffi-obj 'daemon #f (_fun _int _int -> _int)) 0 0)
(with-output-to-file "/tmp/foo"
(λ() (for ([i 10]) (displayln (random 1000)) (flush-output) (sleep 1))))
Raku
(formerly Perl 6)
# Reference:
# https://github.com/hipek8/p6-UNIX-Daemonize/
use v6;
use UNIX::Daemonize;
use File::Temp;
my ($output, $filehandle) = tempfile(:tempdir("/tmp"),:!unlink) or die;
say "Output now goes to ",$output;
daemonize();
loop {
sleep(1);
spurt $output, DateTime.now.Str~"\n", :append;
}
- Output:
root@ubuntu:~# su - daviddavid@ubuntu:~$ ./dumper.p6 Output now goes to /tmp/x2ovx9JG8b david@ubuntu:~$ tail -f /tmp/x2ovx9JG8b 2018-12-11T20:20:01.510484+08:00 2018-12-11T20:20:02.513732+08:00 2018-12-11T20:20:03.517063+08:00 2018-12-11T20:20:04.520394+08:00 2018-12-11T20:20:05.524871+08:00 2018-12-11T20:20:06.528244+08:00 2018-12-11T20:20:07.531985+08:00 2018-12-11T20:20:08.537776+08:00 2018-12-11T20:20:09.541606+08:00 2018-12-11T20:20:10.545796+08:00 2018-12-11T20:20:11.549047+08:00 2018-12-11T20:20:12.552704+08:00 ^C david@ubuntu:~$ exit logout root@ubuntu:~# tail -f /tmp/x2ovx9JG8b 2018-12-11T20:20:28.623690+08:00 2018-12-11T20:20:29.626978+08:00 2018-12-11T20:20:30.634309+08:00 2018-12-11T20:20:31.637481+08:00 2018-12-11T20:20:32.640794+08:00 2018-12-11T20:20:33.643947+08:00 2018-12-11T20:20:34.647146+08:00 2018-12-11T20:20:35.651008+08:00 ^C root@ubuntu:~# su - david david@ubuntu:~$ tail -f /tmp/x2ovx9JG8b 2018-12-11T20:20:51.711357+08:00 2018-12-11T20:20:52.715044+08:00 2018-12-11T20:20:53.718921+08:00 2018-12-11T20:20:54.722134+08:00 2018-12-11T20:20:55.725970+08:00 2018-12-11T20:20:56.729160+08:00 2018-12-11T20:20:57.732376+08:00 2018-12-11T20:20:58.735409+08:00 2018-12-11T20:20:59.738886+08:00 2018-12-11T20:21:00.743045+08:00 2018-12-11T20:21:01.748113+08:00 2018-12-11T20:21:02.753204+08:00 2018-12-11T20:21:03.756665+08:00 2018-12-11T20:21:04.759902+08:00 ^C david@ubuntu:~$ pkill -c moar 1
Sidef
When the "daemon" argument is specified, a fork of the program is created with its STDOUT redirected into the file "foo.txt", and the main process is exited.
var block = {
for n in (1..100) {
STDOUT.say(n)
Sys.sleep(0.5)
}
}
if (ARGV[0] == 'daemon') {
STDERR.say("Daemon mode")
STDOUT{:fh} = %f'foo.txt'.open_w(){:fh}
STDOUT.autoflush(true)
block.fork
STDERR.say("Exiting")
Sys.exit(0)
}
STDERR.say("Normal mode")
block.run
Smalltalk
#! /usr/bin/env stx --script
(pid := OperatingSystem fork) == 0 ifTrue:[
Stdin close.
Stdout := '/tmp/daemon.log' asFilename writeStream.
Stdout buffered:false. "so we can watch it growing"
Transcript := Stderr := Stdout.
Stdout showCR: e'here is the child'.
1 to:5 do:[:n |
Delay waitForSeconds:5.
Transcript showCR: e'another hello on Transcript {n}'.
].
] ifFalse:[
Stdout showCR: e'forked new process pid={pid}; now exiting'.
OperatingSystem exit:0
]
- Output:
bash-3.2$ ./daemon.st forked new process pid=77897; now exiting bash-3.2$ ... after a few seconds: bash-3.2$ cat /tmp/daemon.log here is the child another hello on Transcript 1 another hello on Transcript 2 another hello on Transcript 3 another hello on Transcript 4 another hello on Transcript 5
Tcl
Tcl doesn't come with tools for converting the process into a daemon, but can build them easily enough. Here's the BSD daemon function mapped into a Tcl command in a package.
package provide daemon 1
package require critcl
critcl::ccode {
#include <stdlib.h>
}
critcl::cproc daemon {Tcl_Interp* interp} ok {
if (daemon(0, 0) < 0) {
Tcl_AppendResult(interp, "cannot switch to daemon operation: ",
Tcl_PosixError(interp), NULL);
return TCL_ERROR;
}
return TCL_OK;
}
These tools can then be used to solve this task:
### Command line argument parsing
if {$argc < 1} {
puts "usage: $argv0 file ?message...?"
exit 1
} elseif {$argc == 1} {
set filename [lindex $argv 0]
set message "Hi there!"
} else {
set message [join [lassign $argv filename]]
}
### Daemonize
package require daemon
daemon
close stdout; open $filename ;# Redirects stdout!
### Print the message to the file every second until killed
proc every {ms body} {eval $body; after $ms [info level 0]}
every 1000 {puts "[clock format [clock seconds]]: $message"}
vwait forever
On Windows, there is a commercial extension to Tcl which allows a script to be installed as a service. Such a script would be much like the one above, but without the daemonization section as that has become a property of the runtime.
Wren
An embedded program but the actual daemon is of course the C host even though the process is orchestrated by the Wren code which handles any errors and prints the time every second to the file.
The following script is based on the C example though modified a little to run on Ubuntu 22.04.
/* Run_as_a_daemon_or_service.wren */
import "./date" for Date
var O_WRONLY = 1
var O_APPEND = 1024
var O_CREAT = 64
var STDOUT_FILENO = 1
class C {
foreign static fileName
foreign static open(pathName, flags, mode)
foreign static daemon(nochdir, noclose)
foreign static redirectStdout(oldfd, newfd)
foreign static close(fd)
foreign static time
foreign static sleep(seconds)
}
// gets a Date object from a Unix time in seconds
var UT2Date = Fn.new { |ut| Date.unixEpoch.addSeconds(ut) }
Date.default = "ddd| |mmm| |dd| |hh|:|MM|:|ss| |yyyy" // default format for printing
// open file before becoming a daemon
var fd = C.open(C.fileName, O_WRONLY | O_APPEND | O_CREAT, 438)
if (fd < 0) {
System.print("Error opening %(C.fileName)")
return
}
// become a daemon
if (C.daemon(0, 0) < 0) {
System.print("Error creating daemon.")
return
}
// redirect stdout
if (C.redirectStdout(fd, STDOUT_FILENO) < 0) {
System.print("Error redirecting stdout.")
return
}
// close file
if (C.close(fd) < 0) {
System.print("Error closing %(C.fileName)")
return
}
// dump time every second
while (true) {
System.print(UT2Date.call(C.time))
C.sleep(1)
}
We now embed this in the following C program, compile and run it.
/* gcc Run_as_a_daemon_or_service.c -o dumper -lwren -lm */
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "wren.h"
char *fileName;
void C_fileName(WrenVM* vm) {
wrenSetSlotString(vm, 0, fileName);
}
void C_open(WrenVM* vm) {
const char *pathName = wrenGetSlotString(vm, 1);
int flags = (int)wrenGetSlotDouble(vm, 2);
mode_t mode = (mode_t)wrenGetSlotDouble(vm, 3);
int fd = open(pathName, flags, mode);
wrenSetSlotDouble(vm, 0, (double)fd);
}
void C_daemon(WrenVM* vm) {
int nochdir = (int)wrenGetSlotDouble(vm, 1);
int noclose = (int)wrenGetSlotDouble(vm, 2);
int d = daemon(nochdir, noclose);
wrenSetSlotDouble(vm, 0, (double)d);
}
void C_redirectStdout(WrenVM* vm) {
int oldfd = (int)wrenGetSlotDouble(vm, 1);
int newfd = (int)wrenGetSlotDouble(vm, 2);
newfd = dup2(oldfd, newfd);
wrenSetSlotDouble(vm, 0, (double)newfd);
}
void C_close(WrenVM* vm) {
int fd = (int)wrenGetSlotDouble(vm, 1);
int ret = close(fd);
wrenSetSlotDouble(vm, 0, (double)ret);
}
void C_time(WrenVM* vm) {
time_t t = time(NULL);
wrenSetSlotDouble(vm, 0, (double)t);
}
void C_sleep(WrenVM* vm) {
unsigned int seconds = (unsigned int) wrenGetSlotDouble(vm, 1);
unsigned int ret = sleep(seconds);
wrenSetSlotDouble(vm, 0, (double)ret);
}
WrenForeignMethodFn bindForeignMethod(
WrenVM* vm,
const char* module,
const char* className,
bool isStatic,
const char* signature) {
if (strcmp(module, "main") == 0) {
if (strcmp(className, "C") == 0) {
if (isStatic && strcmp(signature, "fileName") == 0) return C_fileName;
if (isStatic && strcmp(signature, "open(_,_,_)") == 0) return C_open;
if (isStatic && strcmp(signature, "daemon(_,_)") == 0) return C_daemon;
if (isStatic && strcmp(signature, "redirectStdout(_,_)") == 0) return C_redirectStdout;
if (isStatic && strcmp(signature, "close(_)") == 0) return C_close;
if (isStatic && strcmp(signature, "time") == 0) return C_time;
if (isStatic && strcmp(signature, "sleep(_)") == 0) return C_sleep;
}
}
return NULL;
}
static void writeFn(WrenVM* vm, const char* text) {
printf("%s", text);
fflush(stdout); // as we're redirecting stdout to a file
}
void errorFn(WrenVM* vm, WrenErrorType errorType, const char* module, const int line, const char* msg) {
switch (errorType) {
case WREN_ERROR_COMPILE:
printf("[%s line %d] [Error] %s\n", module, line, msg);
break;
case WREN_ERROR_STACK_TRACE:
printf("[%s line %d] in %s\n", module, line, msg);
break;
case WREN_ERROR_RUNTIME:
printf("[Runtime Error] %s\n", msg);
break;
}
}
char *readFile(const char *fileName) {
FILE *f = fopen(fileName, "r");
fseek(f, 0, SEEK_END);
long fsize = ftell(f);
rewind(f);
char *script = malloc(fsize + 1);
size_t ret = fread(script, 1, fsize, f);
if (ret != fsize) printf("Error reading %s\n", fileName);
fclose(f);
script[fsize] = 0;
return script;
}
static void loadModuleComplete(WrenVM* vm, const char* module, WrenLoadModuleResult result) {
if( result.source) free((void*)result.source);
}
WrenLoadModuleResult loadModule(WrenVM* vm, const char* name) {
WrenLoadModuleResult result = {0};
if (strcmp(name, "random") != 0 && strcmp(name, "meta") != 0) {
result.onComplete = loadModuleComplete;
char fullName[strlen(name) + 6];
strcpy(fullName, name);
strcat(fullName, ".wren");
result.source = readFile(fullName);
}
return result;
}
int main(int argc, char **argv) {
fileName = argv[1];
WrenConfiguration config;
wrenInitConfiguration(&config);
config.writeFn = &writeFn;
config.errorFn = &errorFn;
config.bindForeignMethodFn = &bindForeignMethod;
config.loadModuleFn = &loadModule;
WrenVM* vm = wrenNewVM(&config);
const char* module = "main";
const char* fileName = "Run_as_a_daemon_or_service.wren";
char *script = readFile(fileName);
WrenInterpretResult result = wrenInterpret(vm, module, script);
switch (result) {
case WREN_RESULT_COMPILE_ERROR:
printf("Compile Error!\n");
break;
case WREN_RESULT_RUNTIME_ERROR:
printf("Runtime Error!\n");
break;
case WREN_RESULT_SUCCESS:
break;
}
wrenFreeVM(vm);
free(script);
return 0;
}
- Output:
Sample output:
$ ./dumper dump $ tail -f dump Sat Feb 03 20:02:20 2024 Sat Feb 03 20:02:21 2024 Sat Feb 03 20:02:22 2024 Sat Feb 03 20:02:23 2024 Sat Feb 03 20:02:24 2024 Sat Feb 03 20:02:25 2024 Sat Feb 03 20:02:26 2024 Sat Feb 03 20:02:27 2024 Sat Feb 03 20:02:28 2024 Sat Feb 03 20:02:29 2024 ^C $ pkill -x dumper $ rm dump