6 Mayıs 2020 Çarşamba

std::system - fork + execl Bileşimidir

Giriş
Ansi C++ iki kısımdan oluşuyor. Core language ve STL. Core language içindeki std::system() çağrısı var. Bu çağrıyı kullanmak fork/exec ikilisini kullanmaktan biraz daha kolay.

Metodun imzası şöyle
int system (const char *command);
Örnek
Eğer std::string ile kullanmak istersek şöyle yaparız.
std::string cmd = "...";
system(cmd.c_str());
Yoksa şu hatayı alırız.
cannot convert 'std::string {aka std::basic_string}' to 'const char*' for argument '1' to 'int system(const char*)'|
Örnek
Eğer stringstream ile kullanmak istersek şöyle yaparız.
stringstream cmd;
cmd << "...";
system (cmd.str().c_str());
std::system kabul (shell) tafafından çalıştırılır
Linux için açıklaması şöyle.
The system() library function uses fork(2) to create a child process that executes the shell command specified in command using execl(3) as follows:
...
system() returns after the command has been completed.
Bir başka açıklama şöyle.
The system libc function is not a kernel system call. That's why its man page is system(3) not system(2).

It's implemented on top of fork(2) + execve(2), and the waitpid(2) system calls. In fact that's the first thing the system(3) man page says!
Çalıştırılan komut şöyle.
execl ("/bin/sh", "sh", "-c", command, (char *) NULL);
Ya da şöyle düşünebiliriz.
execl(<shell path>, "sh", "-c", command, (char *)0);
/bin/sh default shell'e bir soft link.  /bin/sh kullanılmasının sebeplerinden birisi şöyle.
sh is simple and commonly available. sh is the tool that is invoked to parse command lines in things like system(cmdline) in many languages. Many OSes including some GNU ones have stopped using bash (the GNU shell) to implement sh for the reason that it has become too bloated to do just that simple thing of parsing command lines and interpreting POSIX sh scripts.
Tabi eğer default shell bash ise gidip bash'i verilen argüman ile çalıştırıyor :)

sh te kendi içinde tekrar fork() exec() metodlarını kullanıp istediğimiz programı çağırıyor. Yani aslında std::system() direkt olarak fork() exec() yapmaktan iki kat daha maliyetli.
Difference between using fork/execvp and system call başlıklı yazıda da faydalı bilgiler var.

Parametre Büyüklüğü
ARG_MAX sabitinden birkaç karakter daha küçük olaiblir. ARG_MAX sabiti limits.h dosyasında tanımlı.

Örnek
Komut içinde dosya yönlendirme yapmak için şöyle yaparız.
std::system("ipconfig > myfile.txt");
Örnek
Şu komutu çalıştırmak isteyelim.
diff <(cat /etc/passwd) <(ls -l /etc)
Bash'e özel <() karakterleri kullandığımız için şöyle yaparız.
std::system("bash -c \"diff <(cat /etc/passwd) <(ls -l /etc)\"");
Ya da şöyle yaparız.
system(R"cmd(bash -c "diff <(cat /etc/passwd) <(ls -l /etc)")cmd");
Örnek
Bazen konsol uygulamarında tuhaf şekilde kullanılır.
std::system("clear");
veya
std::system("pause");
Örnek
Farklı std::system çağrıları başka kabuklar içinde çalıştığı için birbirlerinin ortam değişkenlerini görmezler. Şu kod hatalıdır.
std::system("echo $DISPLAY");
std::system("export DISPLAY=:0.0");
std::system("sudo /opt/vc/bin/tvservice -p && xset dpms force on");
std::system ve sinyaller
Linux'ta std::system() ile bir uygulama çağırılınca, uygulama bitinceye kadar kendi uygulamamız da bazı sinyallere cevap vermez. SIGINT dikkate alınmadığı için Ctr+C ile uygulamamızı kapatamayız.
system() executes a command specified in command by calling /bin/sh -c command, and returns after the command has been completed. During execution of the command, SIGCHLD will be blocked, and SIGINT and SIGQUIT will be ignored.
std::system() ve güvenlik açıkları
Bir diğer önemli nokta ise std::system() metodu verilen komutu shell içinde çalıştırdığı için Unix/Linux altında güvenlik açıklarına sebep olabiliyor. Is glibc's system() call safe? başlıklı yazıda verilen komutun temiz bir komut olup olmadığı kontrol edilmezse, "rm -rf" gibi diski tamamen silen bir işlemin bile yapılabileceğine dair açıklamalar mevcut.

Ayrıca shell özel karakterleri yorumlamaya da çalışacaktır. Bu tür özel karakterlerin escape edilmesi de gerekebilir.

Kendi Gerçekleştirimimiz
Şöyle yaparız.
int system(const char *command)
{
  sigset_t blockMask, origMask;
  struct sigaction saIgnore, saOrigQuit, saOrigInt, saDefault;
  pid_t childPid;
  int status, savedErrno;

  if (command == NULL)                /* Is a shell available? */
    return system(":") == 0;

  /* The parent process (the caller of system()) blocks SIGCHLD
     and ignore SIGINT and SIGQUIT while the child is executing.
     We must change the signal settings prior to forking, to avoid
     possible race conditions. This means that we must undo the
     effects of the following in the child after fork(). */

  sigemptyset(&blockMask);            /* Block SIGCHLD */
  sigaddset(&blockMask, SIGCHLD);
  sigprocmask(SIG_BLOCK, &blockMask, &origMask);

  saIgnore.sa_handler = SIG_IGN;      /* Ignore SIGINT and SIGQUIT */
  saIgnore.sa_flags = 0;
  sigemptyset(&saIgnore.sa_mask);
  sigaction(SIGINT, &saIgnore, &saOrigInt);
  sigaction(SIGQUIT, &saIgnore, &saOrigQuit);

  switch (childPid = fork()) {
  case -1: /* fork() failed */
    status = -1;
    break;          /* Carry on to reset signal attributes */

  case 0: /* Child: exec command */

    /* We ignore possible error returns because the only specified error
       is for a failed exec(), and because errors in these calls can't
       affect the caller of system() (which is a separate process) */

    saDefault.sa_handler = SIG_DFL;
    saDefault.sa_flags = 0;
    sigemptyset(&saDefault.sa_mask);

    if (saOrigInt.sa_handler != SIG_IGN)
      sigaction(SIGINT, &saDefault, NULL);
    if (saOrigQuit.sa_handler != SIG_IGN)
      sigaction(SIGQUIT, &saDefault, NULL);

    sigprocmask(SIG_SETMASK, &origMask, NULL);

    execl("/bin/sh", "sh", "-c", command, (char *) NULL);
    _exit(127);                     /* We could not exec the shell */

  default: /* Parent: wait for our child to terminate */

    /* We must use waitpid() for this task; using wait() could inadvertently
       collect the status of one of the caller's other children */

    while (waitpid(childPid, &status, 0) == -1) {
      if (errno != EINTR) {       /* Error other than EINTR */
        status = -1;
        break;                  /* So exit loop */
    }
  }
  break;
  }

  /* Unblock SIGCHLD, restore dispositions of SIGINT and SIGQUIT */

  savedErrno = errno;                 /* The following may change 'errno' */

  sigprocmask(SIG_SETMASK, &origMask, NULL);
  sigaction(SIGINT, &saOrigInt, NULL);
  sigaction(SIGQUIT, &saOrigQuit, NULL);

  errno = savedErrno;

  return status;
}







Hiç yorum yok:

Yorum Gönder