fcntl F_GETLK всегда возвращает true

Я пытаюсь создать демон с одним экземпляром, используя файл блокировки, но fcntl() не работает должным образом...

int creat_lock_file (char * pid_fn)
{
  struct flock pid_lck = {F_WRLCK, SEEK_SET,   0,      0,     0 };

  /* Open/Create pid file */
  int pid_fd = open (pid_fn, O_CREAT | O_WRONLY, 0640);
  if (pid_fd == -1)
  {
    syslog (LOG_ERR, "Unable to open PID file > [Errno: %s]",    strerror(errno));
    return -1;
  }

  /* Place write lock on pid file */
  if (fcntl(pid_fd, F_SETLK, &pid_lck) == -1) {
    /* Unhandled error ocured */
    syslog (LOG_ERR, "Unhandled error ocured while locking PID file > [Errno: %s]", strerror(errno));
    close (pid_fd);
    return -1;
  }

  /* Write PID to lock file */
  char pid_lock_buf[11];
  sprintf (pid_lock_buf, "%ld\n", (long) getpid ());
  write (pid_fd, pid_lock_buf, strlen (pid_lock_buf)+1);

  return 0;
}

int get_lock_file_status (char * pid_fn)
{
  struct flock pid_lck = {F_WRLCK, SEEK_SET,   0,      0,     0 };

  /* Open/Create pid file */
  int pid_fd = open (pid_fn, O_CREAT | O_WRONLY, 0640);
  if (pid_fd == -1)
  {
    syslog (LOG_ERR, "Unable to open PID file > [Errno: %s]", strerror(errno));
    return -1;
  }

  /* try locking pid file*/
  if(fcntl(pid_fd, F_GETLK, &pid_lck) == -1)
  {
    if(errno == EAGAIN || errno == EACCES) /* file already locked, close fd and return -1 */
    {
      close (pid_fd);
      return -1;
    }
    else /* Unhandled error ocured */
    {
      syslog (LOG_ERR, "Unhandled error ocured while locking PID file > [Errno: %s]", strerror(errno));
      close (pid_fd);
      return -1;
    }
  }

  close (pid_fd);
  return 0;
}

поэтому я вызываю get_lock_file_status и выхожу, если он возвращает -1, чтобы убедиться, что никакой другой экземпляр не запущен, кроме того, что я делаю некоторые вещи (fork chdir и т. д.) и вызываю creat_lock_file для создания ящика и блокировки файла pid после успешного создания демона...

при выполнении и запуске программа работает так, как ожидалось, запускает создает файл блокировки и записывает в него pid, но когда второй экземпляр запускается, второй экземпляр просто открывает тот же файл блокировки и записывает в него свой собственный pid!

Что я делаю не так? разве второй экземпляр не должен возвращать -1 в get_lock_file_status?


person Aristos Miliaressis    schedule 13.03.2015    source источник
comment
это кажется неправильным подходом. для программирования блокировки файла функция flock() кажется правильным подходом. Эта функция в Linux подробно описана в 'man 2 flock' или по адресу: ‹linux.die.net/man/2/flock  -  person user3629249    schedule 14.03.2015
comment
@user3629249: user3629249: fcntl(2) с F_SET/GET/LK в порядке, а также POSIX, а flock(2) — нет (хотя он широко доступен). Эти два метода имеют различное поведение w.r.t. блокировка наследования и некоторые другие вещи. fcntl() может устанавливать блокировки на области файлов, а flock() не может.   -  person Ulfalizer    schedule 14.03.2015


Ответы (1)


Вы неправильно проверяете результат F_GETLK. fcntl(2) с F_GETLK возвращает только -1 при ошибках. Правильный способ проверить, можно ли получить блокировку, — это проверить, установлено ли в поле l_type в struct flock значение F_UNLCK, как показано ниже:

/* try locking pid file*/
if(fcntl(pid_fd, F_GETLK, &pid_lck) == -1) {
    syslog (LOG_ERR, "Unhandled error ocured while locking PID file > [Errno: %s]", strerror(errno));
    close(pid_fd);
    return -1;
}
close(pid_fd);
return (pid_lck.l_type == F_UNLCK) ? 0 : -1;

Должна быть возможность объединить creat_lock_file() и get_lock_file_status() в одну функцию, которая открывает файл (и создает его, если он не существует), пытается установить для него блокировку и возвращает информацию об успешной блокировке (например, файл дескриптор или -1).

Между прочим, вы должны truncate(2) преобразовать файл PID в нулевые байты, прежде чем записывать в него PID. Скажем, PID вашего процесса равен 5, а старый PID в файле PID равен 123. Запись «5» сделает содержимое файла PID «523». Усечение файла до нуля байт решает эту проблему. (O_TRUNC не будет работать, так как вы будете очищать файл при его открытии, чтобы проверить, установлена ​​ли блокировка.)

Удаление файла PID с помощью unlink(2) при выходе из программы также довольно распространено. Таким образом, отсутствие файла указывает на то, что демон не работает (хотя это не является надежным, поскольку процесс или система могут выйти из строя).

person Ulfalizer    schedule 14.03.2015
comment
Привет и спасибо за ответ, когда я читал ваш ответ, я подумал о том, что вы сказали, проверять, существует ли файл, не очень хорошая идея, потому что программа может ошибиться и не успеть unlink() этот файл, но что, если я прочитаю файл и попробуйте найти pid, чтобы увидеть, работает ли какой-либо процесс с тем же pid? Это хорошая идея? - person Aristos Miliaressis; 14.03.2015
comment
@AristosMiliaressis: использование блокировки файлов для проверки работы демона будет работать, даже если демон выйдет из строя и не удалит файл PID. Если демон дает сбой, то блокировка файла снимается автоматически (поскольку она привязана к процессу), и новый экземпляр демона сможет заблокировать файл (что говорит ему, что никакой другой экземпляр не запущен). - person Ulfalizer; 14.03.2015
comment
Для проверки, действительно ли демон работает, например. оболочка, проверка, есть ли процесс с таким же PID, как в файле, должна работать, ага. Это может завершиться ошибкой, если демон выйдет из строя, и какой-то другой процесс получит тот же PID, но это было бы довольно редко. (И вы также можете проверить, какая команда запустила процесс, с помощью ps(1), чтобы убедиться.) - person Ulfalizer; 14.03.2015
comment
Я никогда не выполнял блокировку файлов из оболочки, но это тоже должно быть возможно (если вы хотите, чтобы сценарий оболочки делал то же самое, что и ваш код, например). См., например, этот ответ: заголовок stackoverflow.com/questions/1715137/. - person Ulfalizer; 14.03.2015
comment
@AristosMiliaressis: Вы что-нибудь нашли? Я немного отредактировал ответ. Я проверю завтра и удостоверюсь, что код работает на моей машине (хотя, судя по нему, он должен работать, при условии, что вы не вызовете get_lock_file_status() снова после вызова creat_lock_file()). - person Ulfalizer; 14.03.2015
comment
@AristosMiliaressis: я попробовал ваш код и обнаружил настоящую проблему, с которой вы столкнулись. Смотрите обновленный ответ. Я должен иногда угадывать меньше. :) - person Ulfalizer; 15.03.2015