4G/LTE - add watchdog and more
[tomato.git] / release / src / router / httpd / wl.c
blobada63d3634ce09c9e6fb4ab1b7292bcd8d1007a4
1 /*
3 Tomato Firmware
4 Copyright (C) 2006-2009 Jonathan Zarate
6 */
8 #include "tomato.h"
10 #include <ctype.h>
11 #include <wlutils.h>
12 #include <sys/ioctl.h>
14 #ifndef WL_BSS_INFO_VERSION
15 #error WL_BSS_INFO_VERSION
16 #endif
17 #if WL_BSS_INFO_VERSION < 108
18 #error WL_BSS_INFO_VERSION < 108
19 #endif
22 static int unit = 0;
23 static int subunit = 0;
25 static void check_wl_unit(const char *unitarg)
27 char ifname[12], *wlunit;
28 unit = 0; subunit = 0;
30 wlunit = (unitarg && *unitarg) ? (char *)unitarg :
31 webcgi_safeget("_wl_unit", nvram_safe_get("wl_unit"));
32 snprintf(ifname, sizeof(ifname), "wl%s", wlunit);
33 get_ifname_unit(ifname, &unit, &subunit);
35 _dprintf("check_wl_unit: unitarg: %s, _wl_unit: %s, ifname: %s, unit: %d, subunit: %d\n",
36 unitarg, webcgi_safeget("_wl_unit", nvram_safe_get("wl_unit")), ifname, unit, subunit);
39 static void wl_restore(char *wif, int unit, int ap, int radio)
41 if (ap > 0) {
42 wl_ioctl(wif, WLC_SET_AP, &ap, sizeof(ap));
44 if (!radio) set_radio(1, unit);
45 eval("wl", "-i", wif, "up"); // without this the router may reboot
46 #if WL_BSS_INFO_VERSION >= 108
47 // no idea why this voodoo sequence works to wake up wl -- zzz
48 eval("wl", "-i", wif, "ssid", "");
49 eval("wl", "-i", wif, "ssid", nvram_safe_get(wl_nvname("ssid", unit, 0)));
50 #endif
52 set_radio(radio, unit);
55 // allow to scan using up to MAX_WLIF_SCAN wireless ifaces
56 #define MAX_WLIF_SCAN 3
58 typedef struct {
59 int unit_filter;
60 char comma;
61 struct {
62 int ap;
63 int radio;
64 } wif[MAX_WLIF_SCAN];
65 } scan_list_t;
67 static int start_scan(int idx, int unit, int subunit, void *param)
69 scan_list_t *rp = param;
70 wl_scan_params_t sp;
71 char *wif;
72 int zero = 0;
73 int retry;
75 if ((idx >= MAX_WLIF_SCAN) || (rp->unit_filter >= 0 && rp->unit_filter != unit)) return 0;
77 wif = nvram_safe_get(wl_nvname("ifname", unit, 0));
78 memset(&sp, 0xff, sizeof(sp)); // most default to -1
79 sp.ssid.SSID_len = 0;
80 sp.bss_type = DOT11_BSSTYPE_ANY; // =2
81 sp.channel_num = 0;
83 if (wl_ioctl(wif, WLC_GET_AP, &(rp->wif[idx].ap), sizeof(rp->wif[idx].ap)) < 0) {
84 // Unable to get AP mode
85 return 0;
87 if (rp->wif[idx].ap > 0) {
88 wl_ioctl(wif, WLC_SET_AP, &zero, sizeof(zero));
91 // set scan type based on the ap mode
92 sp.scan_type = rp->wif[idx].ap ? DOT11_SCANTYPE_PASSIVE : -1 /* default */;
94 rp->wif[idx].radio = get_radio(unit);
95 if (!(rp->wif[idx].radio)) set_radio(1, unit);
97 retry = 3 * 10;
98 while (retry--) {
99 if (wl_ioctl(wif, WLC_SCAN, &sp, WL_SCAN_PARAMS_FIXED_SIZE) == 0)
100 return 1;
101 if (retry) usleep(100000);
104 // Unable to start scan
105 wl_restore(wif, unit, rp->wif[idx].ap, rp->wif[idx].radio);
106 return 0;
109 static int get_scan_results(int idx, int unit, int subunit, void *param)
111 scan_list_t *rp = param;
113 if ((idx >= MAX_WLIF_SCAN) || (rp->unit_filter >= 0 && rp->unit_filter != unit)) return 0;
115 // get results
117 char *wif;
118 wl_scan_results_t *results;
119 wl_bss_info_t *bssi;
120 int r;
121 int retry;
123 wif = nvram_safe_get(wl_nvname("ifname", unit, 0));
125 results = malloc(WLC_IOCTL_MAXLEN + sizeof(*results));
126 if (!results) {
127 // Not enough memory
128 wl_restore(wif, unit, rp->wif[idx].ap, rp->wif[idx].radio);
129 return 0;
131 results->buflen = WLC_IOCTL_MAXLEN;
132 results->version = WL_BSS_INFO_VERSION;
134 // Keep trying to obtain scan results for up to 4 secs
135 // Passive scan may require more time, although 1 extra sec is almost always enough.
136 retry = 4 * 10;
137 r = -1;
138 while (retry--) {
139 r = wl_ioctl(wif, WLC_SCAN_RESULTS, results, WLC_IOCTL_MAXLEN);
140 if (r >= 0) break;
141 usleep(100000);
144 wl_restore(wif, unit, rp->wif[idx].ap, rp->wif[idx].radio);
146 if (r < 0) {
147 free(results);
148 // Unable to obtain scan results
149 return 0;
152 // format for javascript
154 int i;
155 int j;
156 int k;
157 char c;
158 char ssid[64];
159 char mac[32];
160 char *ssidj;
161 int channel;
163 bssi = &results->bss_info[0];
164 for (i = 0; i < results->count; ++i) {
166 channel = CHSPEC_CHANNEL(bssi->chanspec);
167 if (CHSPEC_IS40(bssi->chanspec))
168 channel = channel + (CHSPEC_SB_LOWER(bssi->chanspec) ? -2 : 2);
170 j = bssi->SSID_len;
171 if (j < 0) j = 0;
172 if (j > 32) j = 32;
173 if (nvram_get_int("wlx_scrubssid")) {
174 for (k = j - 1; k >= 0; --k) {
175 c = bssi->SSID[k];
176 if (!isprint(c)) c = '?';
177 ssid[k] = c;
180 else {
181 memcpy(ssid, bssi->SSID, j);
183 ssid[j] = 0;
184 ssidj = js_string(ssid);
186 web_printf("%c['%s','%s',%u,%u,%d,%d,[", rp->comma,
187 ether_etoa(bssi->BSSID.octet, mac), ssidj ? ssidj : "",
188 channel,
189 bssi->capability, bssi->RSSI, bssi->phy_noise);
190 rp->comma = ',';
191 free(ssidj);
193 for (j = 0; j < bssi->rateset.count; ++j) {
194 web_printf("%s%u", j ? "," : "", bssi->rateset.rates[j]);
196 web_printf("],%d,%d]", bssi->n_cap, bssi->nbss_cap);
198 bssi = (wl_bss_info_t*)((uint8*)bssi + bssi->length);
200 free(results);
202 return 1;
205 // returns: ['bssid','ssid',channel,capabilities,rssi,noise,[rates,]], or [null,'error message']
206 void asp_wlscan(int argc, char **argv)
208 scan_list_t rp;
210 memset(&rp, 0, sizeof(rp));
211 rp.comma = ' ';
212 rp.unit_filter = (argc > 0) ? atoi(argv[0]) : (-1);
214 web_puts("\nwlscandata = [");
216 // scan
218 if (foreach_wif(0, &rp, start_scan) == 0) {
219 web_puts("[null,'Unable to start scan.']];\n");
220 return;
222 sleep(1);
224 // get results
226 if (foreach_wif(0, &rp, get_scan_results) == 0) {
227 web_puts("[null,'Unable to obtain scan results.']];\n");
228 return;
231 web_puts("];\n");
234 void wo_wlradio(char *url)
236 char *enable;
237 char sunit[10];
239 check_wl_unit(NULL);
241 parse_asp("saved.asp");
242 if (nvram_get_int(wl_nvname("radio", unit, 0))) {
243 if ((enable = webcgi_get("enable")) != NULL) {
244 web_close();
245 sleep(2);
246 sprintf(sunit, "%d", unit);
247 eval("radio", atoi(enable) ? "on" : "off", sunit);
248 return;
253 static int read_noise(int unit)
255 int v;
257 // WLC_GET_PHY_NOISE = 135
258 if (wl_ioctl(nvram_safe_get(wl_nvname("ifname", unit, 0)), 135, &v, sizeof(v)) == 0) {
259 char s[32];
260 sprintf(s, "%d", v);
261 nvram_set(wl_nvname("tnoise", unit, 0), s);
262 return v;
264 return -99;
267 static int get_wlnoise(int client, int unit)
269 int v;
271 if (client) {
272 v = read_noise(unit);
274 else {
275 v = nvram_get_int(wl_nvname("tnoise", unit, 0));
276 if ((v >= 0) || (v < -100)) v = -99;
278 return v;
281 static int print_wlnoise(int idx, int unit, int subunit, void *param)
283 web_printf("%c%d", (idx == 0) ? ' ' : ',', get_wlnoise(wl_client(unit, 0), unit));
284 return 0;
287 void asp_wlnoise(int argc, char **argv)
289 web_puts("\nwlnoise = [");
290 foreach_wif(0, NULL, print_wlnoise);
291 web_puts(" ];\n");
294 void wo_wlmnoise(char *url)
296 int ap;
297 int i;
298 char *wif;
300 check_wl_unit(NULL);
302 parse_asp("mnoise.asp");
303 web_close();
304 sleep(3);
306 int radio = get_radio(unit);
308 wif = nvram_safe_get(wl_nvname("ifname", unit, 0));
309 if (wl_ioctl(wif, WLC_GET_AP, &ap, sizeof(ap)) < 0) return;
311 i = 0;
312 wl_ioctl(wif, WLC_SET_AP, &i, sizeof(i));
314 for (i = 10; i > 0; --i) {
315 sleep(1);
316 read_noise(unit);
319 wl_restore(wif, unit, ap, radio);
322 static int wl_chanfreq(uint ch, int band)
324 if ((band == WLC_BAND_2G && (ch < 1 || ch > 14)) || (ch > 200))
325 return -1;
326 else if ((band == WLC_BAND_2G) && (ch == 14))
327 return 2484;
328 else
329 return ch * 5 + ((band == WLC_BAND_2G) ? 4814 : 10000) / 2;
332 static int not_wlclient(int idx, int unit, int subunit, void *param)
334 return (!wl_client(unit, subunit));
337 // returns '1' if all wireless interfaces are in client mode, '0' otherwise
338 void asp_wlclient(int argc, char **argv)
340 web_puts(foreach_wif(1, NULL, not_wlclient) ? "0" : "1");
343 static int print_wlstats(int idx, int unit, int subunit, void *param)
345 int phytype;
346 int rate, client, nbw;
347 int chanspec, channel, mhz, band, scan;
348 int chanim_enab;
349 int interference;
350 char retbuf[WLC_IOCTL_SMLEN];
351 scb_val_t rssi;
352 char *ifname, *ctrlsb;
354 ifname = nvram_safe_get(wl_nvname("ifname", unit, 0));
355 client = wl_client(unit, 0);
357 /* Get configured phy type */
358 wl_ioctl(ifname, WLC_GET_PHYTYPE, &phytype, sizeof(phytype));
360 if (wl_ioctl(ifname, WLC_GET_RATE, &rate, sizeof(rate)) < 0)
361 rate = 0;
363 if (wl_ioctl(ifname, WLC_GET_BAND, &band, sizeof(band)) < 0)
364 band = nvram_get_int(wl_nvname("nband", unit, 0));
366 channel = nvram_get_int(wl_nvname("channel", unit, 0));
367 scan = 0;
368 interference = -1;
370 if (wl_phytype_n(phytype)) {
371 if (wl_iovar_getint(ifname, "chanspec", &chanspec) != 0) {
372 ctrlsb = nvram_safe_get(wl_nvname("nctrlsb", unit, 0));
373 nbw = nvram_get_int(wl_nvname("nbw", unit, 0));
375 else {
376 channel = CHSPEC_CHANNEL(chanspec);
377 if (CHSPEC_IS40(chanspec))
378 channel = channel + (CHSPEC_SB_LOWER(chanspec) ? -2 : 2);
379 ctrlsb = CHSPEC_SB_LOWER(chanspec) ? "lower" : (CHSPEC_SB_UPPER(chanspec) ? "upper" : "none");
380 nbw = CHSPEC_IS40(chanspec) ? 40 : 20;
383 else {
384 channel_info_t ch;
385 if (wl_ioctl(ifname, WLC_GET_CHANNEL, &ch, sizeof(ch)) == 0) {
386 scan = (ch.scan_channel > 0);
387 channel = (scan) ? ch.scan_channel : ch.hw_channel;
389 ctrlsb = "";
390 nbw = 20;
393 mhz = (channel) ? wl_chanfreq(channel, band) : 0;
394 if (wl_iovar_getint(ifname, "chanim_enab", (int*)(void*)&chanim_enab) != 0)
395 chanim_enab = 0;
396 if (chanim_enab) {
397 if (wl_iovar_getbuf(ifname, "chanim_state", &chanspec, sizeof(chanspec), retbuf, WLC_IOCTL_SMLEN) == 0)
398 interference = *(int*)retbuf;
401 memset(&rssi, 0, sizeof(rssi));
402 if (client) {
403 if (wl_ioctl(ifname, WLC_GET_RSSI, &rssi, sizeof(rssi)) != 0)
404 rssi.val = -100;
407 // [ radio, is_client, channel, freq (mhz), rate, nctrlsb, nbw, rssi, noise, interference ]
408 web_printf("%c{ radio: %d, client: %d, channel: %c%d, mhz: %d, rate: %d, ctrlsb: '%s', nbw: %d, rssi: %d, noise: %d, intf: %d }\n",
409 (idx == 0) ? ' ' : ',',
410 get_radio(unit), client, (scan ? '-' : ' '), channel, mhz, rate, ctrlsb, nbw, rssi.val, get_wlnoise(client, unit), interference);
412 return 0;
415 void asp_wlstats(int argc, char **argv)
417 int include_vifs = (argc > 0) ? atoi(argv[0]) : 0;
419 web_puts("\nwlstats = [");
420 foreach_wif(include_vifs, NULL, print_wlstats); // AB multiSSID
421 web_puts("];\n");
424 static void web_print_wlchan(uint chan, int band)
426 int mhz;
427 if ((mhz = wl_chanfreq(chan, band)) > 0)
428 web_printf(",[%d, %d]", chan, mhz);
429 else
430 web_printf(",[%d, 0]", chan);
433 static int _wlchanspecs(char *ifname, char *country, int band, int bw, int ctrlsb)
435 chanspec_t c = 0, *chanspec;
436 int buflen;
437 wl_uint32_list_t *list;
438 int count, i = 0;
440 char *buf = (char *)malloc(WLC_IOCTL_MAXLEN);
441 if (!buf)
442 return 0;
444 strcpy(buf, "chanspecs");
445 buflen = strlen(buf) + 1;
447 c |= (band == WLC_BAND_5G) ? WL_CHANSPEC_BAND_5G : WL_CHANSPEC_BAND_2G;
448 c |= (bw == 20) ? WL_CHANSPEC_BW_20 : WL_CHANSPEC_BW_40;
450 chanspec = (chanspec_t *)(buf + buflen);
451 *chanspec = c;
452 buflen += (sizeof(chanspec_t));
453 strncpy(buf + buflen, country, WLC_CNTRY_BUF_SZ);
454 buflen += WLC_CNTRY_BUF_SZ;
456 /* Add list */
457 list = (wl_uint32_list_t *)(buf + buflen);
458 list->count = WL_NUMCHANSPECS;
459 buflen += sizeof(uint32)*(WL_NUMCHANSPECS + 1);
461 if (wl_ioctl(ifname, WLC_GET_VAR, buf, buflen) < 0) {
462 free((void *)buf);
463 return 0;
466 count = 0;
467 list = (wl_uint32_list_t *)buf;
468 for (i = 0; i < list->count; i++) {
469 c = (chanspec_t)list->element[i];
470 /* Skip upper.. (take only one of the lower or upper) */
471 if (bw == 40 && (CHSPEC_CTL_SB(c) != ctrlsb))
472 continue;
473 /* Create the actual control channel number from sideband */
474 int chan = CHSPEC_CHANNEL(c);
475 if (bw == 40)
476 chan += ((ctrlsb == WL_CHANSPEC_CTL_SB_UPPER) ? 2 : -2);
477 web_print_wlchan(chan, band);
478 count++;
481 free((void *)buf);
482 return count;
485 static void _wlchannels(char *ifname, char *country, int band)
487 int i;
488 wl_channels_in_country_t *cic;
490 cic = (wl_channels_in_country_t *)malloc(WLC_IOCTL_MAXLEN);
491 if (cic) {
492 cic->buflen = WLC_IOCTL_MAXLEN;
493 strcpy(cic->country_abbrev, country);
494 cic->band = band;
496 if (wl_ioctl(ifname, WLC_GET_CHANNELS_IN_COUNTRY, cic, cic->buflen) == 0) {
497 for (i = 0; i < cic->count; i++) {
498 web_print_wlchan(cic->channel[i], band);
501 free((void *)cic);
505 void asp_wlchannels(int argc, char **argv)
507 char buf[WLC_CNTRY_BUF_SZ];
508 int band, phytype, nphy;
509 int bw, ctrlsb, chanspec;
510 char *ifname;
512 // args: unit, nphy[1|0], bw, band, ctrlsb
514 check_wl_unit(argc > 0 ? argv[0] : NULL);
516 ifname = nvram_safe_get(wl_nvname("ifname", unit, 0));
517 wl_ioctl(ifname, WLC_GET_COUNTRY, buf, sizeof(buf));
518 if (wl_ioctl(ifname, WLC_GET_BAND, &band, sizeof(band)) != 0)
519 band = nvram_get_int(wl_nvname("nband", unit, 0));
520 wl_iovar_getint(ifname, "chanspec", &chanspec);
522 if (argc > 1)
523 nphy = atoi(argv[1]);
524 else {
525 wl_ioctl(ifname, WLC_GET_PHYTYPE, &phytype, sizeof(phytype));
526 nphy = wl_phytype_n(phytype);
529 bw = (argc > 2) ? atoi(argv[2]) : 0;
530 bw = bw ? : CHSPEC_IS40(chanspec) ? 40 : 20;
532 if (argc > 3) band = atoi(argv[3]) ? : band;
534 if (argc > 4) {
535 if (strcmp(argv[4], "upper") == 0)
536 ctrlsb = WL_CHANSPEC_CTL_SB_UPPER;
537 else
538 ctrlsb = WL_CHANSPEC_CTL_SB_LOWER;
540 else
541 ctrlsb = CHSPEC_CTL_SB(chanspec);
543 web_puts("\nwl_channels = [\n[0, 0]");
544 if (nphy) {
545 if (!_wlchanspecs(ifname, buf, band, bw, ctrlsb) && band == WLC_BAND_2G && bw == 40)
546 _wlchanspecs(ifname, buf, band, 20, ctrlsb);
548 else
549 _wlchannels(ifname, buf, band);
550 web_puts("];\n");
553 static int print_wlbands(int idx, int unit, int subunit, void *param)
555 char *phytype, *phylist, *ifname;
556 char comma = ' ';
557 int list[WLC_BAND_ALL];
558 int i;
560 ifname = nvram_safe_get(wl_nvname("ifname", unit, 0));
561 phytype = nvram_safe_get(wl_nvname("phytype", unit, 0));
563 web_printf("%c[", (idx == 0) ? ' ' : ',');
565 if (phytype[0] == 'n' ||
566 phytype[0] == 'l' ||
567 phytype[0] == 's' ||
568 phytype[0] == 'c' ||
569 phytype[0] == 'h') {
570 /* Get band list. Assume both the bands in case of error */
571 if (wl_ioctl(ifname, WLC_GET_BANDLIST, list, sizeof(list)) < 0) {
572 for (i = WLC_BAND_5G; i <= WLC_BAND_2G; i++) {
573 web_printf("%c'%d'", comma, i);
574 comma = ',';
577 else {
578 if (list[0] > 2)
579 list[0] = 2;
580 for (i = 1; i <= list[0]; i++) {
581 web_printf("%c'%d'", comma, list[i]);
582 comma = ',';
586 else {
587 /* Get available phy types of the currently selected wireless interface */
588 phylist = nvram_safe_get(wl_nvname("phytypes", unit, 0));
589 for (i = 0; i < strlen(phylist); i++) {
590 web_printf("%c'%d'", comma, phylist[i] == 'a' ? WLC_BAND_5G : WLC_BAND_2G);
591 comma = ',';
595 web_puts("]");
597 return 0;
600 void asp_wlbands(int argc, char **argv)
602 int include_vifs = (argc > 0) ? atoi(argv[0]) : 0;
604 web_puts("\nwl_bands = [");
605 foreach_wif(include_vifs, NULL, print_wlbands); // AB multiSSID
606 web_puts(" ];\n");
609 static int print_wif(int idx, int unit, int subunit, void *param)
611 char unit_str[] = "000000";
612 char *ssidj;
614 char *next;
615 char cap[WLC_IOCTL_SMLEN];
616 char caps[WLC_IOCTL_SMLEN];
617 int max_no_vifs = 0;
619 if (subunit > 0) {
620 snprintf(unit_str, sizeof(unit_str), "%d.%d", unit, subunit);
621 } else {
622 snprintf(unit_str, sizeof(unit_str), "%d", unit);
624 // wl_iovar_get(wl_nvname("ifname", unit, 0), "cap", (void *)caps, WLC_IOCTL_SMLEN);
625 // wl_iovar_get("eth1", "cap", (void *)caps, WLC_IOCTL_SMLEN);
626 max_no_vifs = 1;
627 wl_iovar_get(nvram_safe_get(wl_nvname("ifname", unit, 0)), "cap", (void *)caps, WLC_IOCTL_SMLEN);
628 foreach(cap, caps, next) {
629 if (!strcmp(cap, "mbss16"))
630 max_no_vifs = 16;
631 if (!strcmp(cap, "mbss4"))
632 max_no_vifs = 4;
637 int up = 0;
638 int sfd;
639 struct ifreq ifr;
641 if ((sfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) {
642 _dprintf("[%s %d]: error opening socket %m\n", __FUNCTION__, __LINE__);
645 if (sfd >= 0) {
646 strcpy(ifr.ifr_name, nvram_safe_get(wl_nvname("ifname", unit, subunit)));
647 if (ioctl(sfd, SIOCGIFFLAGS, &ifr) == 0)
648 if (ifr.ifr_flags & (IFF_UP | IFF_RUNNING))
649 up = 1;
650 close(sfd);
653 char *ifname;
654 ifname = nvram_safe_get(wl_nvname("ifname", unit, subunit));
655 struct ether_addr bssid;
657 wl_ioctl(ifname, WLC_GET_BSSID, &bssid, ETHER_ADDR_LEN);
659 // [ifname, unitstr, unit, subunit, ssid, hwaddr, up, wmode, bssid]
660 ssidj = js_string(nvram_safe_get(wl_nvname("ssid", unit, subunit)));
661 web_printf("%c['%s','%s',%d,%d,'%s','%s',%d,%d,'%s','%02X:%02X:%02X:%02X:%02X:%02X']", (idx == 0) ? ' ' : ',',
662 nvram_safe_get(wl_nvname("ifname", unit, subunit)),
663 unit_str, unit, subunit, ssidj,
664 // // virtual inteface MAC address
665 nvram_safe_get(wl_nvname("hwaddr", unit, subunit)), up, max_no_vifs, // AB multiSSID
666 nvram_safe_get(wl_nvname("mode", unit, subunit)),
667 bssid.octet[0], bssid.octet[1], bssid.octet[2], bssid.octet[3], bssid.octet[4], bssid.octet[5]
669 free(ssidj);
671 return 0;
674 void asp_wlifaces(int argc, char **argv)
676 int include_vifs = (argc > 0) ? atoi(argv[0]) : 0;
678 web_puts("\nwl_ifaces = [");
679 foreach_wif(include_vifs, NULL, print_wif);
680 web_puts("];\n");
683 void asp_wlcountries(int argc, char **argv)
685 char s[128], *p, *code, *country;
686 FILE *f;
687 int i = 0;
689 web_puts("\nwl_countries = [");
690 if ((f = popen("wl country list", "r")) != NULL) {
691 // skip the header line
692 fgets(s, sizeof(s), f);
693 while (fgets(s, sizeof(s), f)) {
694 p = s;
695 if ((code = strsep(&p, " \t\n")) && p) {
696 country = strsep(&p, "\n");
697 if ((country && *country && strcmp(code, country) != 0) ||
698 // special case EU code since the driver may not have a name for it
699 (strcmp(code, "EU") == 0)) {
700 if (!country || *country == 0) country = code;
701 p = js_string(country);
702 web_printf("%c['%s', '%s']", i++ ? ',' : ' ', code, p);
703 free(p);
707 fclose(f);
709 web_puts("];\n");