2

I have a simple program using sscanf. The problem I am having is that after matching %[^-]s, sscanf seems to just stop matching.

Here is simple code to see what I mean. The %s match after %[^-]s %s is totally ignored.

NOTE: I need that %[^-]s as I need my program to match both of the possible strings.

NOTE 2: I know the code is totally insecure etc. This is just an example!

#include <stdio.h>

int main(void) {
    int matches;
    int num1, num2, num3, num4;
    char *s1[10];
    char *s2[10];
    char *s3[40];
    char *s4[50];
    char *s5[50];
    char *s6[50];
    char *s7[50];

    char fileTest[] = "29 0 8:4 / / rw,relatime shared:1 - ext4 /dev/sda4 rw,errors=remount-ro";
    // char fileTest[] = "160 48 179:56 /inte /var/oil/gaol/org.something.org/media/internal ro,nosuid,relatime - ext4 /dev/mmccc rw,data=ordered";

    matches = sscanf(fileTest, "%d %d %d:%d %s %s %[^-]s %s",
                     &num1, &num2, &num3, &num4, s1, s2, s3, s4);

    printf("matches: %d\n", matches);
    printf("num1: %d\n", num1);
    printf("num2: %d\n", num2);
    printf("num3: %d\n", num3);
    printf("num4: %d\n", num4);
    printf("s1: %s\n", s1);
    printf("s2: %s\n", s2);
    printf("s3: %s\n", s3);
    printf("s4: %s\n", s4);

    return 0;
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
Merc
  • 16,277
  • 18
  • 79
  • 122

3 Answers3

7

Note that the s after the %[…] scan set is a literal s, not part of the scan set. In context, %[^-]s will never match the s, so any following conversion fails. The %[^-] part gobbles anything that isn't a dash - (and s isn't a -, so it gets chomped, along with spaces and everything else), then the s isn't matched (because the conversion stops at a - or end of string), so the scanning fails there and the final %s is never matched.

See the POSIX specification of sscanf(). Read it. Reread it. Re-reread it. Re-re-reread it. Today! And the same tomorrow. You can scale back to twice a day for the rest of the week, then once a day for another week, and once a week for a month, and then once a month for the rest of the year, and at least once a year thereafter. The scanf() family of functions are probably the most difficult standard C functions to use really well.

There are other major problems with your code. In particular, char *s1[10]; should be char s1[10];, and similarly for the other arrays. Or you need to go through major exercises in allocating space for the arrays to point to, etc. And, as you're probably aware, the %s (and %[^-]) conversions do not limit the inputs. Use %9s or %49[^-], etc, for appropriate sizes. See also How to prevent scanf() causing a buffer overflow in C?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
5

Format specifier %[^-] matches everything until a - is encountered; So after having matched this, the next character in the buffer to check against the format string is a - (if any). But if you let this pattern follow a s, i.e. %[^-]s, then nothing will match any more, because the - never matches the required s in the format string.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Stephan Lechner
  • 34,891
  • 4
  • 35
  • 58
2

There are multiple problems in your code:

  • the sscanf format for a character class is %[chars] or %[^chars], there is no trailing s after the ]. In your example, sscanf() will try to match an s after the third string and fail, preventing the conversion of the subsequent %s.

  • the destination arrays s1 through s4 should be char arrays, not arrays of char *.

  • the formats should specify the maximum number of characters to store into the destination arrays. You did mention the code to be unsafe, so you are probably aware of this, but it is better to make it a habit to do so.

  • since %[^-] stops at - or the end of the input string, the last string parsed by %s will be - or the match count will reflect the absence of a - separator. If you want the rest of the input, you should use %[^\n]

Here is a corrected version:

#include <stdio.h>

int main(void) {
    int matches;
    int num1, num2, num3, num4;
    char s1[10], s2[10], s3[40], s4[50];

    char fileTest[] = "29 0 8:4 / / rw,relatime shared:1 - ext4 /dev/sda4 rw,errors=remount-ro";
    // char fileTest[] = "160 48 179:56 /inte /var/oil/gaol/org.something.org/media/internal ro,nosuid,relatime - ext4 /dev/mmccc rw,data=ordered";

    matches = sscanf(fileTest, "%d %d %d:%d %9s %9s %39[^-] - %49[^\n]",
                     &num1, &num2, &num3, &num4, s1, s2, s3, s4);

    printf("matches: %d\n", matches);
    printf("num1: %d\n", num1);
    printf("num2: %d\n", num2);
    printf("num3: %d\n", num3);
    printf("num4: %d\n", num4);
    printf("s1: %s\n", s1);
    printf("s2: %s\n", s2);
    printf("s3: %s\n", s3);
    printf("s4: %s\n", s4);

    return 0;
}

Output:

matches: 8
num1: 29
num2: 0
num3: 8
num4: 4
s1: /
s2: /
s3: rw,relatime shared:1
s4: ext4 /dev/sda4 rw,errors=remount-ro
chqrlie
  • 131,814
  • 10
  • 121
  • 189