1

For a simple C project of a filesystem in a file, I have to make a command for writing the partitions table. It just contains the number of partitions and the size of them, pretty simple.

It should work like mk_part -s size [-s size ...] [name].

[name] is the filename of the disk, it's optionnal because there is a default one provided.

I don't know much getopt_long (and getopt) but all I read is that I while get options in a while so the two way of processing for me would be :

  1. store all the sizes in an array and then write them in the table.
  2. write size directly during parsing

For the first choice the difficulty is that I don't know the number of partitions. But I still could majorate this number by argc or better by (argc-1)/2 and it would work.

For the second choice I don't know which file to write.

So what is the best alternative to get all those arguments and how can I get this optionnal name ?

user3593232
  • 111
  • 1
  • 3

2 Answers2

3

getopt can handle both repeated and optional args just fine. For repeated args each invocation of getopt will give you the next arg. getopt doesn't care that it is repeated. For the arg at the end, just need to check for its presence once all the options are parsed. Below is code modified from the example in the getopt man page to handle your scenario:

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int
main(int argc, char *argv[])
{
    int opt;

    while ((opt = getopt(argc, argv, "s:")) != -1) {
        switch (opt) {
        case 's':
            printf("size=%d\n", atoi(optarg));
            break;
        default: /* '?' */
            exit(EXIT_FAILURE);
        }
    }

    if (optind < argc) {
        printf("name=%s\n", argv[optind]);
    } else {
        printf("optional name arg not present\n");
    }

    exit(EXIT_SUCCESS);
}

And here is some sample runs of the program showing it handling repeated options and the arg at the end.

$ ./a.out -s 10 -s 20 -s 30
size=10
size=20
size=30
optional name arg not present

$ ./a.out -s 1 my_name
size=1
name=my_name
kaylum
  • 13,833
  • 2
  • 22
  • 31
  • Thank you but actually the difficulty is in getting all sizes before writing into the optionnal/default file, so it's about storing the optarg of each -s. – user3593232 Dec 10 '15 at 05:38
  • @user3593232 I'm not getting you. The above code will allow you to parse all the sizes. The file write code would go after that. Of course the above code is just a sample and only prints out the sizes. Your real code would save the parsed sizes into an array, list or some other structure. That is, replace the above `printf("size=%d\n", atoi(optarg));` line with code that stores that same `optarg` value. – kaylum Dec 10 '15 at 05:39
  • "problem" is that I don't know the size of the array. But it's not a real problem since I can majorate it with argc... I admit it's not a good question. What disturbed me at first was the fact that I will declare an array which won't be fulfilled. In my case it's not a big deal, but in case there are lot of possible different repeated options it could be a big lost to declare an array of argc for each one. I may be a little paranoïd ! – user3593232 Dec 10 '15 at 07:31
0

I think you're overthinking this. I know it's always tempting to try to avoid malloc's, but the efficiency of option parsing is never (*) important. You only parse options once, and in the cost of initializing a new process, finding the executable, linking and loading it, and all the rest of the process of starting up a command, the time it takes to parse options is probably not even noise.

So just do it in the simplest way possible. Here's one possible outline:

int main(int argc, char* argv) {
  /* These variables describe the options */
  int  nparts = 0;              // Number of partitions
  unsigned long* parts = NULL;  // Array of partitions (of size nparts)
  const char* diskname="/the/default/name"; // Disk's filename

  for (;;) {
    switch (getopt(argc, argv, "s:")) {
      case '?':
        /* Print usage message */
        exit(1);
      case 's':
        /* Some error checking missing */
        parts = realloc(parts, ++nparts * sizeof *parts);
        parts[nparts - 1] = strtoul(optarg, NULL, 0);
        continue;
      case -1:
        break;
    }
    break;
  }
  if (optind < argc) diskname = argv[optind++];
  if (optind != argc) {
    /* print error message */ 
    exit(1);
  }
  return do_partitions(diskname, parts, nparts);
}

The above code is missing a lot of error checking and other niceties, but it's short and to the point. It simply reallocs the partition array every time a new size is found. (That's probably not as awful as you think it is, because realloc itself is probably clever enough to increase the allocation size exponentially. But even if it were awful, it's not going to happen often enough to even notice.)

The trick with continue and break is a common way of nesting a switch inside a for. In the switch, continue will continue the for loop, while break will break out of the switch; since all the switch actions which do not terminate the for loop continue, whatever follows the switch block is only executed for a switch action which explicitly breaks. So the break following the switch block breaks the for loop in precisely those cases where the switch action did a break.

You might want to check that there was at least one partition size defined before call the function which does the repartitioning.

rici
  • 234,347
  • 28
  • 237
  • 341