デーモン化

任意のコマンドをデーモンプロセスとして実行するようなコマンドが欲しかったので実装してみた。

/***
 * ファイル名: daemonize.c
 * コンパイル: gcc -o daemonize daemonize
 * 
 * 概要:
 * 引数で渡したコマンドをデーモンプロセスとして実行する。
 *
 * 使い方:
 * $ daemonize [-p PID] [--nochdir] [--noclose] cmd arg*
 *    -p PID: cmdのプロセスIDをファイルPIDに書き込む   ※ PIDには絶対パスを指定する必要がある
 *    --nochdir: 指定されていない場合は、'/'にカレントディレクトリを移して、cmdを実行する
 *    --noclose: 指定されていない場合は、標準入力/標準出力/標準エラー出力、を/dev/nullにリダイレクトして、cmdを実行する
 */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>

int main(int argc, char** argv) {
  char*  pid=NULL;
  char** args;
  int arg_i = 1;
  int nochdir=0;
  int noclose=0;

  // show usage
  if(argc < 2) {
  ARG_ERROR:
    puts("Usage: daemonize [-p PID] [--nochdir] [--noclose] cmd arg*");
    return 1;
  }
  
  // parse arguments
  for(; arg_i < argc && argv[arg_i][0]=='-'; arg_i++) {
    char* opt = argv[arg_i];
    if(strncmp(opt,"-p",2)==0)
      pid = opt[2]=='\0' ? argv[++arg_i] : argv[arg_i]+2;
    else if (strcmp(opt, "--nochdir")==0)
      nochdir = 1;
    else if (strcmp(opt, "--noclose")==0)
      noclose = 1;
    else
      goto ARG_ERROR; // unknown option
  }
  if(arg_i == argc)
    goto ARG_ERROR; // too few arguments
  args = argv+arg_i;

  // daemonize
  if(daemon(nochdir, noclose)==-1)
    return errno;
  
  // write PID
  if(pid) {
    FILE *f = fopen(pid,"w");
    if(!f)
      return 0;
    fprintf(f, "%d", getpid());
    fclose(f);
  }

  // exec
  execvp(args[0], args);
  
  // delete PID on error
  remove(pid);
  
  return 0;
}

/*
// daemon関数の自作版 (若干いい加減)
// linuxには(?)、デーモンプロセスを作成するための関数(daemon関数)が備わっていたので、そちらを使用するように変更
#include <stdlib.h>
int daemon(int nochdir, int noclose) {
  int ret;
  ret = fork();
  if(ret==-1) return -1;
  if(ret!= 0) exit(0);
  setsid();

  ret = fork();
  if(ret==-1) return -1;
  if(ret!= 0) exit(0);
  if(nochdir==0)
    if(chdir("/")==-1)
      return -1;
  umask(0);

  {
    int fd;
    int fd_limit = sysconf(_SC_OPEN_MAX);
    for(fd=3; fd < fd_limit; fd++)
      close(fd);            
    if(noclose==0) {
      freopen("/dev/null", "w", stdout);
      freopen("/dev/null", "w", stderr);
      freopen("/dev/null", "r", stdin);
    }
  }
  return 0;
}
*/

実行例

適切な例ではないけど、一応動かしてみた結果。

$ echo $PWD
/tmp

$ ./daemonize sh -c 'echo $PWD'

$ ./daemonize --noclose sh -c 'echo $PWD'
/

$ ./daemonize --nochdir --noclose sh -c 'echo $PWD'
/tmp

$ ./daemonize -p/tmp/sleep.pid sleep 1000

$ cat sleep.pid
17069

$ ps alx | grep 17069
F   UID   PID  PPID PRI  NI    VSZ   RSS WCHAN  STAT TTY        TIME COMMAND
0  1000 17069     1  20   0   2868   728 -      Ss   ?          0:00 sleep 1000

nohup, disown

上記コマンド作成後に、同じようなことを行うためのnohupやdisownというコマンドがあることを知った。
それらを使った方が良さそう...。
daemonizeコマンドの(ささいな)利点は、プロセスIDをファイルに保存できることくらいかな。