티스토리 뷰

1. --except-files 등록하기

A. rm.c 파일에 Long type 옵션 등록

Short type option-b‘invalid option’으로 인식되는 문제를 해결하지 못하였지만, 일단 Long type option--except-files를 등록하였다.

 

struct option
{
  const char *name;
  /* has_arg can't be an enum because some compilers complain about
     type mismatches in all the code that assumes it is an int.  */
  int has_arg;
  int *flag;
  int val;
};

/coreutils/lib/getopt-ext.h 파일에는 option 구조체가 정의되어 있다.

 

static struct option const long_opts[] =
{
  // mine
  {"except-files", no_argument, NULL, 'b'},
  
  {"force", no_argument, NULL, 'f'},
  {"interactive", optional_argument, NULL, INTERACTIVE_OPTION},

그리고 해당 구조체는 위 코드처럼 /coreutils/src/rm.c 파일에서 사용된다. 구조체의 멤버 변수 값을 할당하기 위해 name에는 except-files라는 옵션 이름을 주었다. 이 옵션을 사용하는 데 argument는 필요 없으므로 no_argument, flagNULL로 주었다. valLong type 옵션과 동일한 기능을 하는 Short type 옵션을 입력하는 듯하여 ‘b’를 입력하였다. 왜냐하면 강제 삭제 Long type 옵션인 ‘--force'valShort type 옵션인 ‘-f’가 왔기 때문이다.

 

 

B. 테스트

make로 컴파일을 진행한 후 터미널 창에 /home/kmi0817/coreutils/src/rm --except-files test를 입력하였다.

 

그 결과 0419 보고서 2-C) rm.c 파일의 main() 함수 수정에서 추가한 아래 코드가 출력되었다.

 

  // mine
  if (x.remove_except_files)
    {
      fprintf(stderr, "If you want to stop inputting files, you input '!no'\n\n");
      fprintf (stderr, "Which files to exclude from deletion?: ");
      if (!yesno ())
        return EXIT_SUCCESS;
    }

, --except-files가 인식되었다는 의미이다. 실제 삭제가 진행되지는 않고, 위 문구만 단순 출력한다. 그리고 삭제에서 제외할 파일을 반복해서 묻지도 않는다. 위 코드는 단순히 -b--except-files 옵션이 인식되는지 테스트하기 위한 용도였기 때문이다.

솔직히 그동안 -b 옵션이 인식되지 않았기 때문에 구조체 val 멤버변수 값으로 b를 할당한 --except-files 옵션이 작동할 것이라고 예상하지 못하였었다. 일단 옵션이 인식되는 --except-files로 옵션 기능 구현을 시작하고 -b 옵션 인식 문제 해도 함께 진행할 예정이다.

 

 

 

 

 

2. -b 인식 문제 원인 파악 시도: rm 옵션 처리 과정 따라가기

새로 등록한 -b 옵션을 인식하지 못하는 이유를 밝히기 위해서는 기존 rm 명령어의 옵션이 어떻게 처리되는지 흐름을 파악해야 한다고 생각하였다. 따라서 /coreutils 디렉터리 내 파일에 테스트 문구를 출력하여 옵션 처리 흐름을 알아보았다.

 

A. rm.c 파일의 main() 함수: 옵션 인식 과정

int main(int argc, char **argv) {
  bool preserve_root = true;
  struct rm_options x;
  /* TEST */
  fprintf(stderr, "1) x 생성\n");
  bool prompt_once = false;
  int c;

  initialize_main(&argc, &argv);
  /* TEST */
  fprintf(stderr, "2) initialize main\n");

  set_program_name(argv[0]);
  /* TEST */
  fprintf(stderr, "3) set_program_name\n");

  setlocale(LC_ALL, "");
  bindtextdomain(PACKAGE, LOCALEDIR);
  textdomain(PACKAGE);

  atexit(close_stdin);

  rm_option_init(&x);
  /* TEST */
  fprintf(stderr, "4) rm_option_init\n");

  /* Try to disable the ability to unlink a directory.  */
  priv_set_remove_linkdir();

  /* TEST */
  fprintf(stderr, "5) before while...\n");
  while ((c = getopt_long(argc, argv, "dfirvIR", long_opts, NULL)) != -1)
  {
    /* TEST */
    fprintf(stderr, "6) in while...\n");

위 코드는 /coreutils/src/rm.c 파일에서 main() 함수 부분이다. Invalid option이 출력되는 부분이 어디인지 알아내기 위해 main() 함수에서 fprintf(stderr, “…”)를 추가하였다.

 

그 결과 테스트 문구 5번과 6번 사이에서 invalid option 문구가 출력되었다. 따라서 while문의 조건문에서 문제가 발생함을 알 수 있었다. 저번 주 보고서에서는 무작정 -b가 부적절한 옵션이라고 출력되는 원인이 getopt_long()에 있다고 생각하였지만, 이번에는 getopt_long()의의 반환 값이 문제라는 확실한 증거를 찾아냈다.

 

B. getopt1.h 파일의 getopt_long() 함수

int
getopt_long (int argc, char *__getopt_argv_const *argv, const char *options,
         const struct option *long_options, int *opt_index)
{
  return _getopt_internal (argc, (char **) argv, options, long_options,
               opt_index, 0, 0);
}

getopt_long() 함수는 /coreutils/lib 디렉터리에 위치하였다. 이 함수는 _getopt_internal()이라는 함수를 반환한다.

 

 

C. getopt.c 파일의 _getopt_internal() 함수

int
_getopt_internal (int argc, char **argv, const char *optstring,
          const struct option *longopts, int *longind, int long_only,
          int posixly_correct)
{
  int result;

  getopt_data.optind = optind;
  getopt_data.opterr = opterr;

  result = _getopt_internal_r (argc, argv, optstring, longopts,
                   longind, long_only, &getopt_data,
                   posixly_correct);

  optind = getopt_data.optind;
  optarg = getopt_data.optarg;
  optopt = getopt_data.optopt;

  return result;
}

/coreutils/lib/getopt.c 파일에 _getopt_internal() 함수가 정의되어 있다.

이 함수에서 사용되는 getopt_data_getopt_data 구조체의 인스턴스이다. _getopt_data 구조체는 getopt_int.h 파일에서 정의되어 있다.

_getopt_internal_r() 함수의 반환 값이 result 변수에 할당되는데, _getopt_internal_r() 함수는 _getopt_internal() 함수와 동일한 파일(getopt.c)에 정의되어 있다. 아래 코드가 _getopt_internal_r() 함수 내용이며 길이가 매우 길다. 코드를 다 분석하지는 못하였다.

int _getopt_internal_r (int argc, char **argv, const char *optstring,
            const struct option *longopts, int *longind,
            int long_only, struct _getopt_data *d, int posixly_correct)
{
  int print_errors = d->opterr;

  if (argc < 1)
    return -1;

d->optarg = NULL;

// d의 optind가 없거나, 초기화되지 않았을 경우
  if (d->optind == 0 || !d->__initialized)
    optstring = _getopt_initialize (argc, argv, optstring, d, posixly_correct);
  else if (optstring[0] == '-' || optstring[0] == '+')
    optstring++;

  if (optstring[0] == ':')
    print_errors = 0;

  /* Test whether ARGV[optind] points to a non-option argument.  */
#define NONOPTION_P (argv[d->optind][0] != '-' || argv[d->optind][1] == '\0')

  if (d->__nextchar == NULL || *d->__nextchar == '\0')
    {
      /* Advance to the next ARGV-element.  */

      /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been
     moved back by the user (who may also have changed the arguments).  */
      if (d->__last_nonopt > d->optind)
    d->__last_nonopt = d->optind;
      if (d->__first_nonopt > d->optind)
    d->__first_nonopt = d->optind;

      if (d->__ordering == PERMUTE)
    {
      /* If we have just processed some options following some non-options,
         exchange them so that the options come first.  */

      if (d->__first_nonopt != d->__last_nonopt
          && d->__last_nonopt != d->optind)
        exchange (argv, d);
      else if (d->__last_nonopt != d->optind)
        d->__first_nonopt = d->optind;

      /* Skip any additional non-options
         and extend the range of non-options previously skipped.  */

      while (d->optind < argc && NONOPTION_P)
        d->optind++;
      d->__last_nonopt = d->optind;
    }

      /* The special ARGV-element '--' means premature end of options.
     Skip it like a null option,
     then exchange with previous non-options as if it were an option,
     then skip everything else like a non-option.  */

      if (d->optind != argc && !strcmp (argv[d->optind], "--"))
    {
      d->optind++;

      if (d->__first_nonopt != d->__last_nonopt
          && d->__last_nonopt != d->optind)
        exchange (argv, d);
      else if (d->__first_nonopt == d->__last_nonopt)
        d->__first_nonopt = d->optind;
      d->__last_nonopt = argc;

      d->optind = argc;
    }

      /* If we have done all the ARGV-elements, stop the scan
     and back over any non-options that we skipped and permuted.  */

      if (d->optind == argc)
    {
      /* Set the next-arg-index to point at the non-options
         that we previously skipped, so the caller will digest them.  */
      if (d->__first_nonopt != d->__last_nonopt)
        d->optind = d->__first_nonopt;
      return -1;
    }

      /* If we have come to a non-option and did not permute it,
     either stop the scan or describe it to the caller and pass it by.  */

      if (NONOPTION_P)
    {
      if (d->__ordering == REQUIRE_ORDER)
        return -1;
      d->optarg = argv[d->optind++];
      return 1;
    }

      /* We have found another option-ARGV-element.
     Check whether it might be a long option.  */
      if (longopts)
    {
      if (argv[d->optind][1] == '-')
        {
          /* "--foo" is always a long option.  The special option
         "--" was handled above.  */
          d->__nextchar = argv[d->optind] + 2;
          return process_long_option (argc, argv, optstring, longopts,
                      longind, long_only, d,
                      print_errors, "--");
        }

      /* If long_only and the ARGV-element has the form "-f",
         where f is a valid short option, don't consider it an
         abbreviated form of a long option that starts with f.
         Otherwise there would be no way to give the -f short
         option.

         On the other hand, if there's a long option "fubar" and
         the ARGV-element is "-fu", do consider that an
         abbreviation of the long option, just like "--fu", and
         not "-f" with arg "u".

         This distinction seems to be the most useful approach.  */
      if (long_only && (argv[d->optind][2]
                || !strchr (optstring, argv[d->optind][1])))
        {
          int code;
          d->__nextchar = argv[d->optind] + 1;
          code = process_long_option (argc, argv, optstring, longopts,
                      longind, long_only, d,
                      print_errors, "-");
          if (code != -1)
        return code;
        }
    }

      /* It is not a long option.  Skip the initial punctuation.  */
      d->__nextchar = argv[d->optind] + 1;
    }

  /* Look at and handle the next short option-character.  */

  {
    char c = *d->__nextchar++;
    const char *temp = strchr (optstring, c);

    /* Increment 'optind' when we start to process its last character.  */
    if (*d->__nextchar == '\0')
      ++d->optind;

    if (temp == NULL || c == ':' || c == ';') {
        if (print_errors)
        fprintf (stderr, _("%s: invalid option -- '%c'\n"), argv[0], c);
        d->optopt = c;
        return '?';
    }

    /* Convenience. Treat POSIX -W foo same as long option --foo */
    if (temp[0] == 'W' && temp[1] == ';' && longopts != NULL)
      {
    /* This is an option that requires an argument.  */
    if (*d->__nextchar != '\0')
      d->optarg = d->__nextchar;
    else if (d->optind == argc)
      {
        if (print_errors)
          fprintf (stderr,
               _("%s: option requires an argument -- '%c'\n"),
               argv[0], c);

        d->optopt = c;
        if (optstring[0] == ':')
          c = ':';
        else
          c = '?';
        return c;
      }
    else
      d->optarg = argv[d->optind];

    d->__nextchar = d->optarg;
    d->optarg = NULL;
    return process_long_option (argc, argv, optstring, longopts, longind,
                    0 /* long_only */, d, print_errors, "-W ");
      }
    if (temp[1] == ':')
      {
    if (temp[2] == ':')
      {
        /* This is an option that accepts an argument optionally.  */
        if (*d->__nextchar != '\0')
          {
        d->optarg = d->__nextchar;
        d->optind++;
          }
        else
          d->optarg = NULL;
        d->__nextchar = NULL;
      }
    else
      {
        /* This is an option that requires an argument.  */
        if (*d->__nextchar != '\0')
          {
        d->optarg = d->__nextchar;
        /* If we end this ARGV-element by taking the rest as an arg,
           we must advance to the next element now.  */
        d->optind++;
          }
        else if (d->optind == argc)
          {
        if (print_errors)
          fprintf (stderr,
               _("%s: option requires an argument -- '%c'\n"),
               argv[0], c);

        d->optopt = c;
        if (optstring[0] == ':')
          c = ':';
        else
          c = '?';
          }
        else
          /* We already incremented 'optind' once;
         increment it again when taking next ARGV-elt as argument.  */
          d->optarg = argv[d->optind++];
        d->__nextchar = NULL;
      }
      }
    return c;
  }
}
728x90