ccanlint: make a license enum, and parse the license string to set it.
[ccan] / tools / ccanlint / tests / license_exists.c
1 #include <tools/ccanlint/ccanlint.h>
2 #include <sys/types.h>
3 #include <sys/stat.h>
4 #include <fcntl.h>
5 #include <unistd.h>
6 #include <limits.h>
7 #include <errno.h>
8 #include <stdlib.h>
9 #include <stdio.h>
10 #include <err.h>
11 #include <ccan/talloc/talloc.h>
12 #include <ccan/str/str.h>
13 #include <ccan/str_talloc/str_talloc.h>
14
15 static struct doc_section *find_license_tag(const struct manifest *m)
16 {
17         struct doc_section *d;
18
19         list_for_each(m->info_file->doc_sections, d, list) {
20                 if (!streq(d->function, m->basename))
21                         continue;
22                 if (streq(d->type, "license"))
23                         return d;
24         }
25         return NULL;
26 }
27
28 static enum license which_license(struct doc_section *d)
29 {
30         if (strstarts(d->lines[0], "GPL")) {
31                 if (strchr(d->lines[0], '3'))
32                         return LICENSE_GPLv3;
33                 else if (strchr(d->lines[0], '2')) {
34                         if (strreg(NULL, d->lines[0], "or (any )?later", NULL))
35                                 return LICENSE_GPLv2_PLUS;
36                         else
37                                 return LICENSE_GPLv2;
38                 }
39                 return LICENSE_GPL;
40         }
41
42         if (strstarts(d->lines[0], "LGPL")) {
43                 if (strchr(d->lines[0], '3'))
44                         return LICENSE_LGPLv3;
45                 else if (strchr(d->lines[0], '2')) {
46                         if (strreg(NULL, d->lines[0], "or (any )?later", NULL))
47                                 return LICENSE_LGPLv2_PLUS;
48                         else
49                                 return LICENSE_LGPLv2;
50                 }
51                 return LICENSE_LGPL;
52         }
53         if (streq(d->lines[0], "BSD-MIT")
54             || streq(d->lines[0], "MIT"))
55                 return LICENSE_MIT;
56         if (streq(d->lines[0], "BSD (3 clause)"))
57                 return LICENSE_BSD;
58         if (strreg(NULL, d->lines[0], "[Pp]ublic [Dd]omain"))
59                 return LICENSE_PUBLIC_DOMAIN;
60
61         return LICENSE_UNKNOWN;
62 }
63
64 static const char *expected_link(enum license license)
65 {
66         switch (license) {
67         case LICENSE_LGPLv2_PLUS:
68         case LICENSE_LGPLv2:
69                 return "../../licenses/LGPL-2.1";
70         case LICENSE_LGPLv3:
71         case LICENSE_LGPL:
72                 return "../../licenses/LGPL-3";
73
74         case LICENSE_GPLv2_PLUS:
75         case LICENSE_GPLv2:
76                 return "../../licenses/GPL-2";
77
78         case LICENSE_GPLv3:
79         case LICENSE_GPL:
80                 return "../../licenses/GPL-3";
81
82         case LICENSE_BSD:
83                 return "../../licenses/BSD-3CLAUSE";
84
85         case LICENSE_MIT:
86                 return "../../licenses/BSD-MIT";
87
88         default:
89                 return NULL;
90         }
91 }
92
93 static void handle_license_link(struct manifest *m, struct score *score)
94 {
95         struct doc_section *d = find_license_tag(m);
96         const char *link = talloc_asprintf(m, "%s/LICENSE", m->dir);
97         const char *ldest = expected_link(m->license);
98         char *q;
99
100         printf(
101         "Most modules want a copy of their license, so usually we create a\n"
102         "LICENSE symlink into ../../licenses to avoid too many copies.\n");
103
104         /* FIXME: make ask printf-like */
105         q = talloc_asprintf(m, "Set up link to %s (license is %s)?",
106                             ldest, d->lines[0]);
107         if (ask(q)) {
108                 if (symlink(ldest, link) != 0)
109                         err(1, "Creating symlink %s -> %s", link, ldest);
110         }
111 }
112
113 extern struct ccanlint license_exists;
114
115 static void check_has_license(struct manifest *m,
116                               bool keep,
117                               unsigned int *timeleft, struct score *score)
118 {
119         char buf[PATH_MAX];
120         ssize_t len;
121         char *license = talloc_asprintf(m, "%s/LICENSE", m->dir);
122         const char *expected;
123         struct doc_section *d;
124
125         d = find_license_tag(m);
126         if (!d) {
127                 score->error = talloc_strdup(score, "No License: tag in _info");
128                 return;
129         }
130
131         m->license = which_license(d);
132
133         /* If they have a license tag at all, we pass. */
134         score->pass = true;
135
136         expected = expected_link(m->license);
137
138         len = readlink(license, buf, sizeof(buf));
139         if (len < 0) {
140                 /* Could be a real file... OK if not a standard license. */
141                 if (errno == EINVAL) {
142                         if (!expected) {
143                                 score->score = score->total;
144                                 return;
145                         }
146                         score->error
147                                 = talloc_asprintf(score,
148                                           "License in _info is '%s',"
149                                           " expect LICENSE symlink '%s'",
150                                           d->lines[0], expected);
151                         return;
152                 }
153                 if (errno == ENOENT) {
154                         /* Public domain doesn't really need a file. */
155                         if (m->license == LICENSE_PUBLIC_DOMAIN) {
156                                 score->score = score->total;
157                                 return;
158                         }
159                         score->error = talloc_strdup(score,
160                                                      "LICENSE does not exist");
161                         if (expected)
162                                 license_exists.handle = handle_license_link;
163                         return;
164                 }
165                 err(1, "readlink on %s", license);
166         }
167         if (len >= sizeof(buf))
168                 errx(1, "Reading symlink %s gave huge result", license);
169
170         buf[len] = '\0';
171
172         if (!strstarts(buf, "../../licenses/")) {
173                 score->error = talloc_asprintf(score,
174                                                "Expected symlink to"
175                                                " ../../licenses/..."
176                                                " not %s", buf);
177                 return;
178         }
179
180         if (!expected) {
181                 score->error = talloc_asprintf(score,
182                                           "License in _info is unknown '%s',"
183                                           " but LICENSE symlink is '%s'",
184                                           d->lines[0], buf);
185                 return;
186         }
187
188         if (!streq(buf, expected)) {
189                 score->error = talloc_asprintf(score,
190                                        "Expected symlink to %s not %s",
191                                        expected, buf);
192                 return;
193         }
194         score->pass = true;
195         score->score = score->total;
196 }
197
198 struct ccanlint license_exists = {
199         .key = "license_exists",
200         .name = "Module has License: entry in _info, and LICENSE symlink/file",
201         .check = check_has_license,
202         .needs = "info_exists"
203 };
204 REGISTER_TEST(license_exists);