tvote.c: add function to perform safe vote increments - vote - simple cgi voting system for web and gopher
 (HTM) git clone git://src.adamsgaard.dk/vote
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit d5823861bfb88b89fc65a8844cd7e9a623bb0690
 (DIR) parent c214ae3ad448784c1da35ec3b14dec92fc92fccd
 (HTM) Author: Anders Damsgaard <anders@adamsgaard.dk>
       Date:   Mon, 28 Sep 2020 13:17:43 +0200
       
       vote.c: add function to perform safe vote increments
       
       Diffstat:
         M vote.c                              |     152 +++++++++++++++++++------------
       
       1 file changed, 96 insertions(+), 56 deletions(-)
       ---
 (DIR) diff --git a/vote.c b/vote.c
       t@@ -15,6 +15,7 @@
        #define OUT(s) (fputs((s), stdout))
        #define POLLS_DIR "polls"
        
       +static char fname[PATH_MAX];
        static char poll[1024];
        static char create[2];
        static char question[4096];
       t@@ -39,6 +40,23 @@ http_status(int statuscode)
                }
        }
        
       +char *
       +pollfile(const char *poll_name, const char *postfix)
       +{
       +        char buf[PATH_MAX];
       +
       +        strlcpy(buf, poll_name, sizeof(buf));
       +        escapechars(buf);
       +        if (snprintf(fname, sizeof(fname), "%s/%s%s",
       +                POLLS_DIR, buf, postfix) < 0) {
       +                http_status(500);
       +                err(1, "show_poll: snprintf fname %s/%s%s",
       +                        POLLS_DIR, buf, postfix);
       +        }
       +
       +        return fname;
       +}
       +
        void
        print_html_head()
        {
       t@@ -55,31 +73,31 @@ print_html_foot()
                        "</html>\n");
        }
        
       -void
       -print_poll_line(char *line)
       +int
       +print_poll_line(char *line, int intable)
        {
       -        printf("<tr><td>");
       -        while (*line) {
       -                switch(*line) {
       -                case '\t':
       -                        printf("</td><td>");
       -                        break;
       -                default:
       -                        putchar(*line);
       -                        break;
       -                }
       -                (void)*line++;
       +        size_t c;
       +
       +        if (sscanf(line, "%ld\t%s", &c, options) == 2) {
       +                if (!intable)
       +                        puts("<br><table>");
       +                printf("<tr><td>%ld</td><td>%s</td></tr>\n", c, options);
       +                return 1;
       +        } else {
       +                if (intable)
       +                        puts("</table>");
       +                printf("%s<br>\n", line);
       +                return 0;
                }
       -        puts("</td></tr>");
        }
        
        void
       -print_poll_file(FILE *fp, const char *poll_name)
       +print_poll_file(FILE *fp)
        {
                char *line = NULL;
       -        size_t linesize = 0;
       +        size_t linesize = 0, lineno = 0;
                ssize_t linelen;
       -        unsigned int lineno = 0;
       +        int intable = 0;
        
                while ((linelen = getline(&line, &linesize, fp)) != -1) {
                        lineno++;
       t@@ -87,18 +105,18 @@ print_poll_file(FILE *fp, const char *poll_name)
                                line[--linelen] = '\0';
        
                        if (lineno == 1) {
       -                        printf("<h1>%s: <i>", poll_name);
       +                        printf("<h2>");
                                fwrite(line, linelen, 1, stdout);
       -                        printf("</i></h1>");
       -                        printf("<table>\n");
       +                        printf("</h2>\n");
                        } else {
       -                        print_poll_line(line);
       +                        intable = print_poll_line(line, intable);
                        }
       -                /* puts("<br>"); */
                }
                free(line);
       -        if (ferror(fp))
       +        if (ferror(fp)) {
       +                http_status(500);
                        err(1, "print_poll_file: getline");
       +        }
                puts("</table>");
        }
        
       t@@ -106,16 +124,9 @@ int
        create_poll_file(const char *name, const char *question, const char *options)
        {
                FILE *fp;
       -        char fname[PATH_MAX], buf[PATH_MAX];
                struct stat sb;
                size_t col;
       -
       -        strlcpy(buf, name, sizeof(buf));
       -        escapechars(buf);
       -        if (snprintf(fname, sizeof(fname), "%s/%s", POLLS_DIR, buf) < 0) {
       -                http_status(500);
       -                err(1, "create_poll_file: snprintf fname %s/%s", POLLS_DIR, buf);
       -        }
       +        char *fname;
        
                if (!*name || !*question || !*options) {
                        puts("<p><b>Error: Could not create poll</b></p>");
       t@@ -130,6 +141,7 @@ create_poll_file(const char *name, const char *question, const char *options)
                        return -1;
                }
        
       +        fname = pollfile(name, "");
                if (stat(fname, &sb) == 0) {
                        printf("<p>Poll '%s' already exists</p>", name);
                        return -1;
       t@@ -163,7 +175,7 @@ create_poll_file(const char *name, const char *question, const char *options)
                                                break;
                                        }
                                }
       -                        /* fputc('\n', fp); */
       +                        fputc('\n', fp);
                                fclose(fp);
                        }
                }
       t@@ -171,24 +183,15 @@ create_poll_file(const char *name, const char *question, const char *options)
        }
        
        void
       -show_poll(const char *poll_name)
       +show_poll(const char *poll)
        {
                FILE *fp;
       -        char fname[PATH_MAX];
       -        char buf[PATH_MAX];
        
       -        strlcpy(buf, poll_name, sizeof(buf));
       -        escapechars(buf);
       -        if (snprintf(fname, sizeof(fname), "%s/%s", POLLS_DIR, buf) < 0) {
       -                http_status(500);
       -                err(1, "show_poll: snprintf fname %s/%s", POLLS_DIR, buf);
       -        }
       -
       -        if (!(fp = fopen(fname, "r"))) {
       +        if (!(fp = fopen(pollfile(poll, ""), "r"))) {
                        http_status(404);
                        exit(1);
                } else {
       -                print_poll_file(fp, poll_name);
       +                print_poll_file(fp);
                        fclose(fp);
                }
        }
       t@@ -199,12 +202,10 @@ list_polls()
                FTS *ftsp;
                FTSENT *p;
                int fts_options = FTS_COMFOLLOW | FTS_LOGICAL | FTS_NOCHDIR;
       -        /* int fts_options = FTS_NOCHDIR | FTS_PHYSICAL; */
       -        /* char *path = POLLS_DIR; */
                char *paths[] = { (char*)POLLS_DIR, NULL };
        
                if ((ftsp = fts_open(paths, fts_options, NULL)) == NULL) {
       -                fprintf(stderr, "could not fts_open");
       +                http_status(500);
                        err(1, "list_polls: fts_open");
                }
        
       t@@ -226,19 +227,55 @@ list_polls()
                puts("</ul>");
        }
        
       -/*
        void
       -increment_option(FILE *fp)
       +increment_option(char *poll, size_t n)
        {
       -        while ((ch = fgetc(ft)) != EOF) {
       -                if (ch == 'i') {
       -                        fseek(ft, -1, SEEK_CUR);
       -                        fputc('a',ft);
       -                        fseek(ft, 0, SEEK_CUR);
       +        FILE *fp, *fp_tmp;
       +        size_t v, lineno = 0;
       +        char *line = NULL, *fname = NULL;
       +        static char fname_tmp[PATH_MAX];
       +        size_t linesize = 0;
       +        ssize_t linelen;
       +        struct stat sb;
       +
       +        fname = pollfile(poll, "_lock");
       +        strlcpy(fname_tmp, fname, sizeof(fname_tmp));
       +        while (stat(fname_tmp, &sb) == 0)
       +                usleep(100);
       +        if (!(fp_tmp = fopen(fname_tmp, "w"))) {
       +                http_status(500);
       +                err(1, "increment_option: fopen fp_tmp");
       +        }
       +
       +        fname = pollfile(poll, "");
       +        if (!(fp = fopen(fname, "r"))) {
       +                http_status(404);
       +                err(1, "increment_option: fopen fp");
       +        }
       +
       +        while ((linelen = getline(&line, &linesize, fp)) != -1) {
       +                if (sscanf(line, "%ld\t%s", &v, options) != 2)
       +                        fputs(line, fp_tmp);
       +                else {
       +                        if (++lineno == n)
       +                                v++;
       +                        fprintf(fp_tmp, "%ld\t%s\n", v, options);
                        }
                }
       +        
       +        free(line);
       +        if (ferror(fp) || ferror(fp_tmp)) {
       +                http_status(500);
       +                err(1, "increment_option: getline");
       +        }
       +        fclose(fp);
       +        fclose(fp_tmp);
       +
       +        if (rename(fname_tmp, fname) != 0) {
       +                http_status(500);
       +                err(1, "increment_option: rename");
       +        }
        }
       -*/
        
        void
        print_poll_create_form()
       t@@ -281,8 +318,10 @@ parse_query()
        {
                char *query, *p;
        
       -        if (!(query = getenv("QUERY_STRING")))
       +        if (!(query = getenv("QUERY_STRING"))) {
                        query = "";
       +                return;
       +        }
        
                if ((p = getparam(query, "create"))) {
                        if (decodeparam(create, sizeof(create), p) == -1) {
       t@@ -345,6 +384,7 @@ main()
                                show_poll(poll);
                } else if (*poll) {
                        show_poll(poll);
       +                increment_option(poll, 2);
                } else {
                        list_polls();
                        print_poll_create_form();