summaryrefslogtreecommitdiff
path: root/user/sane-airscan/git.patch
diff options
context:
space:
mode:
Diffstat (limited to 'user/sane-airscan/git.patch')
-rw-r--r--user/sane-airscan/git.patch1212
1 files changed, 1212 insertions, 0 deletions
diff --git a/user/sane-airscan/git.patch b/user/sane-airscan/git.patch
new file mode 100644
index 000000000..818580553
--- /dev/null
+++ b/user/sane-airscan/git.patch
@@ -0,0 +1,1212 @@
+diff --git a/COPYING b/COPYING
+index 60549be..d159169 100644
+--- a/COPYING
++++ b/COPYING
+@@ -1,12 +1,12 @@
+- GNU GENERAL PUBLIC LICENSE
+- Version 2, June 1991
++ GNU GENERAL PUBLIC LICENSE
++ Version 2, June 1991
+
+- Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
++ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+- Preamble
++ Preamble
+
+ The licenses for most software are designed to take away your
+ freedom to share and change it. By contrast, the GNU General Public
+@@ -15,7 +15,7 @@ software--to make sure the software is free for all its users. This
+ General Public License applies to most of the Free Software
+ Foundation's software and to any other program whose authors commit to
+ using it. (Some other Free Software Foundation software is covered by
+-the GNU Library General Public License instead.) You can apply it to
++the GNU Lesser General Public License instead.) You can apply it to
+ your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+@@ -55,8 +55,8 @@ patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+ modification follow.
+-
+- GNU GENERAL PUBLIC LICENSE
++
++ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+@@ -110,7 +110,7 @@ above, provided that you also meet all of these conditions:
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+-
++
+ These requirements apply to the modified work as a whole. If
+ identifiable sections of that work are not derived from the Program,
+ and can be reasonably considered independent and separate works in
+@@ -168,7 +168,7 @@ access to copy from a designated place, then offering equivalent
+ access to copy the source code from the same place counts as
+ distribution of the source code, even though third parties are not
+ compelled to copy the source along with the object code.
+-
++
+ 4. You may not copy, modify, sublicense, or distribute the Program
+ except as expressly provided under this License. Any attempt
+ otherwise to copy, modify, sublicense or distribute the Program is
+@@ -225,7 +225,7 @@ impose that choice.
+
+ This section is intended to make thoroughly clear what is believed to
+ be a consequence of the rest of this License.
+-
++
+ 8. If the distribution and/or use of the Program is restricted in
+ certain countries either by patents or by copyrighted interfaces, the
+ original copyright holder who places the Program under this License
+@@ -255,7 +255,7 @@ make exceptions for this. Our decision will be guided by the two goals
+ of preserving the free status of all derivatives of our free software and
+ of promoting the sharing and reuse of software generally.
+
+- NO WARRANTY
++ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+ FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+@@ -277,9 +277,9 @@ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGES.
+
+- END OF TERMS AND CONDITIONS
+-
+- How to Apply These Terms to Your New Programs
++ END OF TERMS AND CONDITIONS
++
++ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+ possible use to the public, the best way to achieve this is to make it
+@@ -291,7 +291,7 @@ convey the exclusion of warranty; and each file should have at least
+ the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+- Copyright (C) 19yy <name of author>
++ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+@@ -303,17 +303,16 @@ the "copyright" line and a pointer to where the full notice is found.
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+- You should have received a copy of the GNU General Public License
+- along with this program; if not, write to the Free Software
+- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+-
++ You should have received a copy of the GNU General Public License along
++ with this program; if not, write to the Free Software Foundation, Inc.,
++ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ Also add information on how to contact you by electronic and paper mail.
+
+ If the program is interactive, make it output a short notice like this
+ when it starts in an interactive mode:
+
+- Gnomovision version 69, Copyright (C) 19yy name of author
++ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+@@ -336,5 +335,5 @@ necessary. Here is a sample; alter the names:
+ This General Public License does not permit incorporating your program into
+ proprietary programs. If your program is a subroutine library, you may
+ consider it more useful to permit linking proprietary applications with the
+-library. If this is what you want to do, use the GNU Library General
++library. If this is what you want to do, use the GNU Lesser General
+ Public License instead of this License.
+diff --git a/LICENSE b/LICENSE
+index fe71f02..db02cb9 100644
+--- a/LICENSE
++++ b/LICENSE
+@@ -16,8 +16,8 @@ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+-Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+-MA 02111-1307, USA.
++Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
++MA 02110-1301 USA.
+
+ As a special exception, the authors of sane-airscan give permission for
+ additional uses of the libraries contained in this release of sane-airscan.
+diff --git a/Makefile b/Makefile
+index 9d4e082..70555a8 100644
+--- a/Makefile
++++ b/Makefile
+@@ -17,7 +17,7 @@
+
+ CC = gcc
+ COMPRESS = gzip
+-CFLAGS = -O2 -g -W -Wall -Werror
++CFLAGS += -O2 -g -W -Wall -Werror $(CPPFLAGS)
+ MANDIR = /usr/share/man/
+ PKG_CONFIG = /usr/bin/pkg-config
+ STRIP = -s
+@@ -82,7 +82,7 @@ $(BACKEND): $(OBJDIR)airscan.o $(LIBAIRSCAN) airscan.sym
+ $(CC) -o $(BACKEND) -shared $(OBJDIR)/airscan.o $(LIBAIRSCAN) $(airscan_LDFLAGS)
+
+ $(DISCOVER): $(OBJDIR)discover.o $(LIBAIRSCAN)
+- $(CC) -o $(DISCOVER) discover.c $(CPPFLAGS) $(airscan_CFLAGS) $(LIBAIRSCAN) $(airscan_LIBS) -fPIE
++ $(CC) -o $(DISCOVER) discover.c $(CPPFLAGS) $(airscan_CFLAGS) $(LIBAIRSCAN) $(airscan_LIBS) $(LDFLAGS) -fPIE
+
+ $(LIBAIRSCAN): $(OBJ) Makefile
+ ar cru $(LIBAIRSCAN) $(OBJ)
+@@ -105,6 +105,13 @@ clean:
+ rm -f test $(BACKEND) tags
+ rm -rf $(OBJDIR)
+
++uninstall:
++ rm -f $(DESTDIR)$(PREFIX)$(BINDIR)/$(DISCOVER)
++ rm -f $(DESTDIR)$(PREFIX)$(CONFDIR)/dll.d/airscan
++ rm -f $(DESTDIR)$(PREFIX)$(LIBDIR)/sane/$(BACKEND)
++ rm -f $(DESTDIR)$(PREFIX)$(MANDIR)/man1/$(MAN_DISCOVER)*
++ rm -f $(DESTDIR)$(PREFIX)$(MANDIR)/man5/$(MAN_BACKEND)*
++
+ man: $(MAN_DISCOVER) $(MAN_BACKEND)
+
+ $(MAN_DISCOVER): $(MAN_DISCOVER).md
+@@ -114,7 +121,7 @@ $(MAN_BACKEND): $(MAN_BACKEND).md
+ ronn --roff --manual=$(MAN_BACKEND_TITLE) $(MAN_BACKEND).md
+
+ test: $(BACKEND) test.c
+- $(CC) -o test test.c $(BACKEND) -Wl,-rpath . ${airscan_CFLAGS}
++ $(CC) -o test test.c $(BACKEND) -Wl,-rpath . $(LDFLAGS) ${airscan_CFLAGS}
+
+ test-decode: test-decode.c $(LIBAIRSCAN)
+- $(CC) -o test-decode test-decode.c $(CPPFLAGS) $(airscan_CFLAGS) $(LIBAIRSCAN) $(airscan_LIBS)
++ $(CC) -o test-decode test-decode.c $(CPPFLAGS) $(airscan_CFLAGS) $(LIBAIRSCAN) $(LDFLAGS) $(airscan_LIBS)
+diff --git a/README.md b/README.md
+index 469a2cd..f05f4d0 100644
+--- a/README.md
++++ b/README.md
+@@ -51,10 +51,13 @@ Legend:
+ | Canon D570 | Yes | |
+ | Canon ImageCLASS MF743cdw | Yes<sup>[1](#note1)</sup> | |
+ | Canon imageRUNNER ADVANCE 4545/4551| Yes | Yes |
++| Canon imageRUNNER C3120L | Yes | Yes |
+ | Canon i-SENSYS MF641C | No | Yes |
+-| Canon Lide 400 | Yes | |
++| Canon Lide 400 | Yes<sup>[2](#note1)</sup> | |
+ | Canon MF745C/746C | Yes | Yes |
++| Canon PIXMA MG5500 Series | No | Yes |
+ | Canon PIXMA MG7700 Series | Yes | |
++| Canon PIXMA TS5000 Series | Yes | |
+ | Canon PIXMA TS 9550 Series | Yes | |
+ | Canon TR4529 (PIXMA TR4500 Series) | Yes | |
+ | Canon TR7500 Series | No | Yes |
+@@ -89,10 +92,10 @@ Legend:
+ | HP OfficeJet Pro 8730 | Yes | Yes |
+ | HP OfficeJet Pro 9010 series | Yes | |
+ | HP Smart Tank Plus 550 series | Yes | |
+-| Kyocera ECOSYS M2040dn | Yes | Yes<sup>[2](#note2)</sup> |
+-| Lexmark CX317dn | Yes<sup>[3](#note3)</sup> | Yes<sup>[3](#note3)</sup> |
++| Kyocera ECOSYS M2040dn | Yes | Yes<sup>[3](#note2)</sup> |
++| Lexmark CX317dn | Yes<sup>[4](#note3)</sup> | Yes<sup>[4](#note3)</sup> |
+ | Lexmark MC2535adwe | Yes | |
+-| Ricoh MP C3003 | No | Yes<sup>[4](#note3)</sup> |
++| Ricoh MP C3003 | No | Yes<sup>[5](#note3)</sup> |
+ | Samsung M288x Series | No | Yes |
+ | Xerox VersaLink B405 | Yes | |
+ | TODO | | |
+@@ -102,15 +105,21 @@ Legend:
+ scanning on its web console: Home->Menu->Preferences->Network->TCP/IP
+ Settings->Network Link Scan Settings->On.
+
+-<a name="note2"><sup>[2]</sup></a>: this device requires manual action on its front
++<a name="note3"><sup>[2]</sup></a>: this USB device supports IPP over USB protocol, which
++allows it to be used with network protocols like eSCL. To enable IPP over USB, you need
++to install additional program: either [ippusbxd](https://github.com/OpenPrinting/ippusbxd), which
++comes with some distros, or [ipp-usb](https://github.com/OpenPrinting/ipp-usb). 'ipp-usb`
++works better, binary packages available for many popular distros.
++
++<a name="note3"><sup>[3]</sup></a>: this device requires manual action on its front
+ panel to initiate WSD scan: Send->WSD Scan->From Computer
+
+-<a name="note3"><sup>[3]</sup></a>: when low in memory, this device may scan at 400 DPI
++<a name="note4"><sup>[4]</sup></a>: when low in memory, this device may scan at 400 DPI
+ instead of requested 600 DPI. As sane-airscan reports image parameters to SANE before actual
+ image is received, and then adjust actual image to reported parameters, image will
+ be scaled down by the factor 2/3 at this case. Lower resolutions works well.
+
+-<a name="note3"><sup>[4]</sup></a>: by default, WSD scan command is disabled on this
++<a name="note5"><sup>[5]</sup></a>: by default, WSD scan command is disabled on this
+ device and needs to be enabled before use: Click [Configuration], click [Initial Settings]
+ under [Scanner], and then set [Prohibit WSD Scan Command] to [Do not Prohibit] (from
+ http://support.ricoh.com/bb_v1oi/pub_e/oi_view/0001047/0001047003/view/scanner/int/0095.htm)
+diff --git a/airscan-device.c b/airscan-device.c
+index f76fa23..82e267a 100644
+--- a/airscan-device.c
++++ b/airscan-device.c
+@@ -119,7 +119,6 @@ struct device {
+ SANE_Int read_line_end; /* If read_line_num>read_line_end
+ no more lines left in image */
+ SANE_Int read_line_off; /* Current offset in the line */
+- SANE_Int read_skip_lines; /* How many lines to skip */
+ SANE_Int read_skip_bytes; /* How many bytes to skip at line
+ beginning */
+ };
+@@ -487,6 +486,8 @@ static void
+ device_probe_endpoint (device *dev, zeroconf_endpoint *endpoint)
+ {
+ /* Switch endpoint */
++ log_assert(dev->log, endpoint->proto != ID_PROTO_UNKNOWN);
++
+ if (dev->endpoint_current == NULL ||
+ dev->endpoint_current->proto != endpoint->proto) {
+ device_proto_set(dev, endpoint->proto);
+@@ -851,8 +852,8 @@ device_geom;
+ * First of all, we use 3 different units to deal with geometrical
+ * parameters:
+ * 1) we communicate with frontend in millimeters
+- * 2) we communicate with scanner in pixels, assuming protoc-specific DPI
+- * (defined by devcaps::units)
++ * 2) we communicate with scanner in pixels, assuming
++ * protocol-specific DPI (defined by devcaps::units)
+ * 3) when we deal with image, sizes are in pixels in real resolution
+ *
+ * Second, scanner returns minimal and maximal window size, but
+@@ -1289,6 +1290,7 @@ device_read_next (device *dev)
+ SANE_Parameters params;
+ image_decoder *decoder = dev->decoders[dev->proto_ctx.params.format];
+ int wid, hei;
++ int skip_lines = 0;
+
+ log_assert(dev->log, decoder != NULL);
+
+@@ -1330,7 +1332,7 @@ device_read_next (device *dev)
+ /* Setup image clipping */
+ if (dev->job_skip_x >= wid || dev->job_skip_y >= hei) {
+ /* Trivial case - just skip everything */
+- dev->read_skip_lines = hei;
++ dev->read_line_end = 0;
+ dev->read_skip_bytes = 0;
+ line_capacity = dev->opt.params.bytes_per_line;
+ } else {
+@@ -1352,12 +1354,12 @@ device_read_next (device *dev)
+ dev->read_skip_bytes = bpp * (dev->job_skip_x - win.x_off);
+ }
+
+- dev->read_skip_lines = 0;
+ if (win.y_off != dev->job_skip_y) {
+- dev->read_skip_lines = dev->job_skip_y - win.y_off;
++ skip_lines = dev->job_skip_y - win.y_off;
+ }
+
+ line_capacity = math_max(dev->opt.params.bytes_per_line, wid * bpp);
++ line_capacity += dev->read_skip_bytes;
+ }
+
+ /* Initialize image decoding */
+@@ -1366,7 +1368,14 @@ device_read_next (device *dev)
+
+ dev->read_line_num = 0;
+ dev->read_line_off = dev->opt.params.bytes_per_line;
+- dev->read_line_end = hei - dev->read_skip_lines;
++ dev->read_line_end = hei - skip_lines;
++
++ for (;skip_lines > 0; skip_lines --) {
++ err = image_decoder_read_line(decoder, dev->read_line_buf);
++ if (err != NULL) {
++ goto DONE;
++ }
++ }
+
+ /* Wake up reader */
+ pollable_signal(dev->read_pollable);
+@@ -1408,8 +1417,9 @@ device_read_decode_line (device *dev)
+ return SANE_STATUS_EOF;
+ }
+
+- if (n < dev->read_skip_lines || n >= dev->read_line_end) {
+- memset(dev->read_line_buf, 0xff, dev->opt.params.bytes_per_line);
++ if (n >= dev->read_line_end) {
++ memset(dev->read_line_buf + dev->read_skip_bytes, 0xff,
++ dev->opt.params.bytes_per_line);
+ } else {
+ error err = image_decoder_read_line(decoder, dev->read_line_buf);
+
+@@ -1419,7 +1429,7 @@ device_read_decode_line (device *dev)
+ }
+ }
+
+- dev->read_line_off = dev->read_skip_bytes;
++ dev->read_line_off = 0;
+ dev->read_line_num ++;
+
+ return SANE_STATUS_GOOD;
+@@ -1482,12 +1492,14 @@ device_read (device *dev, SANE_Byte *data, SANE_Int max_len, SANE_Int *len_out)
+ /* Read line by line */
+ for (len = 0; status == SANE_STATUS_GOOD && len < max_len; ) {
+ if (dev->read_line_off == dev->opt.params.bytes_per_line) {
+- status = device_read_decode_line (dev);
++ status = device_read_decode_line(dev);
+ } else {
+ SANE_Int sz = math_min(max_len - len,
+ dev->opt.params.bytes_per_line - dev->read_line_off);
+
+- memcpy(data, dev->read_line_buf + dev->read_line_off, sz);
++ memcpy(data, dev->read_line_buf + dev->read_skip_bytes +
++ dev->read_line_off, sz);
++
+ data += sz;
+ dev->read_line_off += sz;
+ len += sz;
+diff --git a/airscan-escl.c b/airscan-escl.c
+index dbaa370..163bdd1 100644
+--- a/airscan-escl.c
++++ b/airscan-escl.c
+@@ -335,16 +335,16 @@ escl_devcaps_source_parse (xml_rd *xml, devcaps_source **out)
+ goto DONE;
+ }
+
+- if (src->max_wid_px != 0 && src->max_hei_px != 0 )
++ if (src->max_wid_px != 0 && src->max_hei_px != 0)
+ {
+ /* Validate window size */
+- if (src->min_wid_px >= src->max_wid_px )
++ if (src->min_wid_px > src->max_wid_px)
+ {
+ err = ERROR("Invalid scan:MinWidth or scan:MaxWidth");
+ goto DONE;
+ }
+
+- if (src->min_hei_px >= src->max_hei_px)
++ if (src->min_hei_px > src->max_hei_px)
+ {
+ err = ERROR("Invalid scan:MinHeight or scan:MaxHeight");
+ goto DONE;
+diff --git a/airscan-http.c b/airscan-http.c
+index 6758368..ca6f8b0 100644
+--- a/airscan-http.c
++++ b/airscan-http.c
+@@ -114,6 +114,7 @@ http_uri_new_relative (const http_uri *base, const char *path,
+
+ /* Free the URI
+ */
++#ifndef __clang_analyzer__
+ void
+ http_uri_free (http_uri *uri)
+ {
+@@ -123,6 +124,7 @@ http_uri_free (http_uri *uri)
+ g_free(uri);
+ }
+ }
++#endif
+
+ /* Get URI string
+ */
+@@ -689,9 +691,21 @@ http_client_onerror (http_client *client,
+ void
+ http_client_cancel (http_client *client)
+ {
+- while (client->pending->len != 0) {
+- http_query_cancel(client->pending->pdata[0]);
++ size_t i, len = client->pending->len;
++ size_t sz = len * sizeof(http_query*);
++ http_query **qlist = g_alloca(len);
++
++ /* Note, without this stupid copying of client->pending->pdata,
++ * clang analyzer doesn't understand that http_query_cancel()
++ * has a side effect of removing http_query pointer from the
++ * array and erroneously claims that memory used after free
++ */
++ memcpy(qlist, client->pending->pdata, sz);
++ for (i = 0; i < len; i ++) {
++ http_query_cancel(qlist[i]);
+ }
++
++ log_assert(client->log, client->pending->len == 0);
+ }
+
+ /* Cancel all pending queries with matching address family and uintptr
+@@ -988,12 +1002,13 @@ http_query_cancel (http_query *q)
+ g_object_ref(q->msg);
+ soup_session_cancel_message(http_session, q->msg, SOUP_STATUS_CANCELLED);
+ soup_message_set_status(q->msg, SOUP_STATUS_CANCELLED);
+- g_object_unref(q->msg);
+
+ log_debug(q->client->log, "HTTP %s %s: %s", q->msg->method,
+ http_uri_str(q->uri),
+ soup_status_get_phrase(SOUP_STATUS_CANCELLED));
+
++ g_object_unref(q->msg);
++
+ http_query_free(q);
+ }
+
+@@ -1225,6 +1240,8 @@ http_start_stop (bool start)
+ g_object_set_property(G_OBJECT(http_session),
+ SOUP_SESSION_SSL_STRICT, &val);
+ } else {
++ ll_node *node;
++
+ soup_session_abort(http_session);
+ g_object_unref(http_session);
+ http_session = NULL;
+@@ -1232,10 +1249,8 @@ http_start_stop (bool start)
+ /* Note, soup_session_abort() may leave some requests
+ * pending, so we must free them here explicitly
+ */
+- while (!ll_empty(&http_query_list)) {
+- ll_node *node = ll_first(&http_query_list);
++ while ((node = ll_pop_beg(&http_query_list)) != NULL) {
+ http_query *q = OUTER_STRUCT(node, http_query, list_node);
+-
+ http_query_free(q);
+ }
+ }
+diff --git a/airscan-math.c b/airscan-math.c
+index d801304..ee7f301 100644
+--- a/airscan-math.c
++++ b/airscan-math.c
+@@ -99,7 +99,6 @@ math_range_merge (SANE_Range *out, const SANE_Range *r1, const SANE_Range *r2)
+ SANE_Word quant = math_lcm(r1->quant, r2->quant);
+ SANE_Word min, max, bounds_min, bounds_max;
+
+- min = math_min(r1->min, r2->min);
+ bounds_min = math_max(r1->min, r2->min);
+ bounds_max = math_min(r1->max, r2->max);
+
+diff --git a/airscan-netif.c b/airscan-netif.c
+index a762377..a086792 100644
+--- a/airscan-netif.c
++++ b/airscan-netif.c
+@@ -92,6 +92,41 @@ netif_distance_get (const struct sockaddr *addr)
+ return distance;
+ }
+
++/* Check that interface has non-link-local address
++ * of particular address family
++ */
++bool
++netif_has_non_link_local_addr (int af, int ifindex)
++{
++ struct ifaddrs *ifa;
++
++ for (ifa = netif_ifaddrs; ifa != NULL; ifa = ifa->ifa_next) {
++ struct sockaddr *addr;
++
++ /* Skip interface without address */
++ if ((addr = ifa->ifa_addr) == NULL) {
++ continue;
++ }
++
++ /* Check address family against requested */
++ if (addr->sa_family != af) {
++ continue;
++ }
++
++ /* Skip link-local addresses */
++ if (ip_sockaddr_is_linklocal(addr)) {
++ continue;
++ }
++
++ /* Check interface index */
++ if (ifindex == (int) if_nametoindex(ifa->ifa_name)) {
++ return true;
++ }
++ }
++
++ return false;
++}
++
+ /* Get list of network interfaces addresses
+ */
+ netif_addr*
+diff --git a/airscan-wsdd.c b/airscan-wsdd.c
+index 15bf02a..c53105b 100644
+--- a/airscan-wsdd.c
++++ b/airscan-wsdd.c
+@@ -26,6 +26,12 @@
+ #define WSDD_RETRANSMIT_MAX 250 /* Max retransmit time */
+ #define WSDD_DISCOVERY_TIME 2500 /* Overall discovery time */
+
++/* This delay is taken, if we have discovered, say, device's IPv6
++ * addresses and have a strong suspicion that device has not yet
++ * discovered IPv4 addresses as well
++ */
++#define WSDD_PUBLISH_DELAY 1000
++
+ /* WS-Discovery stable endpoint path
+ */
+ #define WSDD_STABLE_ENDPOINT \
+@@ -49,12 +55,13 @@ typedef struct {
+ * device discovery
+ */
+ typedef struct {
+- zeroconf_finding finding; /* Base class */
+- const char *address; /* Device "address" in WS-SD sense */
+- ll_head xaddrs; /* List of wsdd_xaddr */
+- http_client *http_client; /* HTTP client */
+- ll_node list_node; /* In wsdd_finding_list */
+- bool published; /* This finding is published */
++ zeroconf_finding finding; /* Base class */
++ const char *address; /* Device "address" in WS-SD sense */
++ ll_head xaddrs; /* List of wsdd_xaddr */
++ http_client *http_client; /* HTTP client */
++ ll_node list_node; /* In wsdd_finding_list */
++ eloop_timer *publish_timer; /* WSDD_PUBLISH_DELAY timer */
++ bool published; /* This finding is published */
+ } wsdd_finding;
+
+ /* wsdd_xaddr represents device transport address
+@@ -207,9 +214,8 @@ wsdd_xaddr_list_purge (ll_head *list)
+ {
+ ll_node *node;
+
+- while ((node = ll_first(list)) != NULL) {
++ while ((node = ll_pop_beg(list)) != NULL) {
+ wsdd_xaddr *xaddr = OUTER_STRUCT(node, wsdd_xaddr, list_node);
+- ll_del(&xaddr->list_node);
+ wsdd_xaddr_free(xaddr);
+ }
+ }
+@@ -269,6 +275,10 @@ wsdd_finding_free (wsdd_finding *wsdd)
+ http_client_cancel(wsdd->http_client);
+ http_client_free(wsdd->http_client);
+
++ if (wsdd->publish_timer != NULL) {
++ eloop_timer_cancel(wsdd->publish_timer);
++ }
++
+ zeroconf_endpoint_list_free(wsdd->finding.endpoints);
+ g_free((char*) wsdd->address);
+ wsdd_xaddr_list_purge(&wsdd->xaddrs);
+@@ -282,17 +292,100 @@ wsdd_finding_free (wsdd_finding *wsdd)
+ static void
+ wsdd_finding_publish (wsdd_finding *wsdd)
+ {
+- if (!wsdd->published) {
+- wsdd->published = true;
+- zeroconf_finding_publish(&wsdd->finding);
++ if (wsdd->published) {
++ return;
++ }
++
++ wsdd->published = true;
++ wsdd->finding.endpoints = zeroconf_endpoint_list_sort_dedup(
++ wsdd->finding.endpoints);
++
++ if (wsdd->publish_timer != NULL) {
++ log_debug(wsdd_log, "\"%s\": publish-delay timer canceled",
++ wsdd->finding.model);
++
++ eloop_timer_cancel(wsdd->publish_timer);
++ wsdd->publish_timer = NULL;
++ }
++
++ zeroconf_finding_publish(&wsdd->finding);
++}
++
++/* WSDD_PUBLISH_DELAY timer callback
++ */
++static void
++wsdd_finding_publish_delay_timer_callback (void *data)
++{
++ wsdd_finding *wsdd = data;
++
++ wsdd->publish_timer = NULL;
++ log_debug(wsdd_log, "\"%s\": publish-delay timer expired",
++ wsdd->finding.model);
++
++ wsdd_finding_publish(wsdd);
++}
++
++/* Publish wsdd_finding with optional delay
++ */
++static void
++wsdd_finding_publish_delay (wsdd_finding *wsdd)
++{
++ bool delay = false;
++
++ if (wsdd->published) {
++ return;
++ }
++
++ /* Continue discovery, if interface has IPv4/IPv6 address,
++ * and we have not yet discovered address of the same address
++ * family of device
++ *
++ * Some devices doesn't return their IPv4 endpoints, if
++ * metadata is queried via IPv6, and visa versa. This is
++ * possible that we will finish discovery of the particular
++ * address family, before we'll ever know that the device
++ * may have address of another address family, so part
++ * of addresses will be never discovered (see #44 for details).
++ *
++ * To prevent this situation, we continue discovery with
++ * some reasonable delay, if network interface has IPv4/IPv6
++ * address, but device is not yet.
++ */
++
++ if (netif_has_non_link_local_addr(AF_INET, wsdd->finding.ifindex) &&
++ !zeroconf_endpoint_list_has_non_link_local_addr(AF_INET,
++ wsdd->finding.endpoints)) {
++ log_debug(wsdd_log,
++ "\"%s\": IPv4 address expected but not yet discovered",
++ wsdd->finding.model);
++ delay = true;
++ }
++
++ if (netif_has_non_link_local_addr(AF_INET6, wsdd->finding.ifindex) &&
++ !zeroconf_endpoint_list_has_non_link_local_addr(AF_INET6,
++ wsdd->finding.endpoints)) {
++ log_debug(wsdd_log,
++ "\"%s\": IPv6 address expected but not yet discovered",
++ wsdd->finding.model);
++ delay = true;
++ }
++
++ if (delay) {
++ if (wsdd->publish_timer == NULL) {
++ wsdd->publish_timer = eloop_timer_new(WSDD_PUBLISH_DELAY,
++ wsdd_finding_publish_delay_timer_callback, wsdd);
++ }
++
++ return;
+ }
++
++ wsdd_finding_publish(wsdd);
+ }
+
+-/* Add wsdd_finding to the wsdd_finding_list.
+- * If finding already present, does nothing and returns NULL
++/* Get existent finding or add a new one
+ */
+ static wsdd_finding*
+-wsdd_finding_add (int ifindex, const char *address)
++wsdd_finding_get (int ifindex, const char *address)
+ {
+ ll_node *node;
+ wsdd_finding *wsdd;
+@@ -302,7 +395,7 @@ wsdd_finding_add (int ifindex, const char *address)
+ wsdd = OUTER_STRUCT(node, wsdd_finding, list_node);
+ if (wsdd->finding.ifindex == ifindex &&
+ !strcmp(wsdd->address, address)) {
+- return NULL;
++ return wsdd;
+ }
+ }
+
+@@ -343,6 +436,25 @@ wsdd_finding_by_address (ip_addr addr)
+ return NULL;
+ }
+
++/* Check if finding already has particular xaddr
++ */
++static bool
++wsdd_finding_has_xaddr (wsdd_finding *wsdd, const wsdd_xaddr *xaddr)
++{
++ ll_node *node;
++ wsdd_xaddr *xaddr2;
++
++ for (LL_FOR_EACH(node, &wsdd->xaddrs)) {
++ xaddr2 = OUTER_STRUCT(node, wsdd_xaddr, list_node);
++
++ if (http_uri_equal(xaddr->uri, xaddr2->uri)) {
++ return true;
++ }
++ }
++
++ return false;
++}
++
+ /* Delete wsdd_finding from the wsdd_finding_list
+ */
+ static void
+@@ -532,24 +644,7 @@ DONE:
+ g_free(manufacturer);
+
+ if (http_client_num_pending(wsdd->http_client) == 0) {
+- zeroconf_endpoint *endpoint;
+-
+- wsdd->finding.endpoints = zeroconf_endpoint_list_sort_dedup(
+- wsdd->finding.endpoints);
+-
+- log_debug(wsdd_log, "\"%s\": address: %s",
+- wsdd->finding.model, wsdd->address);
+- log_debug(wsdd_log, "\"%s\": uuid: %s",
+- wsdd->finding.model, wsdd->finding.uuid.text);
+- log_debug(wsdd_log, "\"%s\": discovered endpoints:",
+- wsdd->finding.model);
+-
+- for (endpoint = wsdd->finding.endpoints; endpoint != NULL;
+- endpoint = endpoint->next) {
+- log_debug(wsdd_log, " %s", http_uri_str(endpoint->uri));
+- }
+-
+- wsdd_finding_publish(wsdd);
++ wsdd_finding_publish_delay(wsdd);
+ }
+ }
+
+@@ -738,28 +833,42 @@ wsdd_resolver_message_dispatch (wsdd_resolver *resolver,
+ goto DONE;
+ }
+
+- /* Add a finding. Do nothing if device already exist */
+- wsdd = wsdd_finding_add(resolver->ifindex, msg->address);
+- if (wsdd == NULL) {
+- goto DONE;
+- }
++ /* Add a finding or get existent one */
++ wsdd = wsdd_finding_get(resolver->ifindex, msg->address);
+
+- /* If device is not scanner or (which is very unlikely)
+- * has no xaddrs, just publish it without endpoints,
+- * so zeroconf stuff will know that there is no need to
+- * wait anymore for a device with this UUID, and we
+- * are done
++ /* Import newly discovered xaddrs and initiate metadata
++ * query
+ */
+- if (!msg->is_scanner || ll_empty(&msg->xaddrs)) {
+- wsdd_finding_publish(wsdd);
+- goto DONE;
++ while ((node = ll_pop_beg(&msg->xaddrs)) != NULL) {
++ xaddr = OUTER_STRUCT(node, wsdd_xaddr, list_node);
++
++ if (wsdd_finding_has_xaddr(wsdd, xaddr)) {
++ wsdd_xaddr_free(xaddr);
++ continue;
++ }
++
++ ll_push_end(&wsdd->xaddrs, &xaddr->list_node);
++ if (msg->is_scanner) {
++ wsdd_finding_get_metadata(wsdd, resolver->ifindex, xaddr);
++ }
+ }
+
+- /* Initiate metadata query */
+- ll_cat(&wsdd->xaddrs, &msg->xaddrs);
+- for (LL_FOR_EACH(node, &wsdd->xaddrs)) {
+- xaddr = OUTER_STRUCT(node, wsdd_xaddr, list_node);
+- wsdd_finding_get_metadata(wsdd, resolver->ifindex, xaddr);
++ /* If there is no pending metadata queries, it may mean
++ * one of the following:
++ * 1) device is not scanner, metadata won't be requested
++ * 2) there is no xaddrs (which is very unlikely, but
++ * just in case...)
++ * 3) device already known and all metadata queries
++ * already finished
++ *
++ * At this case we can publish device now
++ */
++ if (http_client_num_pending(wsdd->http_client) == 0) {
++ if (msg->is_scanner) {
++ wsdd_finding_publish_delay(wsdd);
++ } else {
++ wsdd_finding_publish(wsdd);
++ }
+ }
+ break;
+
+@@ -774,6 +883,7 @@ wsdd_resolver_message_dispatch (wsdd_resolver *resolver,
+ /* Cleanup and exit */
+ DONE:
+ wsdd_message_free(msg);
++ log_trace(wsdd_log, "");
+ }
+
+
+diff --git a/airscan-zeroconf.c b/airscan-zeroconf.c
+index dfff5b8..293a05d 100644
+--- a/airscan-zeroconf.c
++++ b/airscan-zeroconf.c
+@@ -129,11 +129,8 @@ zeroconf_device_add (zeroconf_finding *finding)
+ }
+
+ ll_init(&device->findings);
+-
+- device->ifaces_cap = ZEROCONF_DEVICE_IFACES_INITIAL_LEN;
+- device->ifaces = g_malloc(device->ifaces_cap * sizeof(*device->ifaces));
+-
+ ll_push_end(&zeroconf_device_list, &device->node_list);
++
+ return device;
+ }
+
+@@ -233,7 +230,12 @@ zeroconf_device_ifaces_add (zeroconf_device *device, int ifindex)
+ {
+ if (!zeroconf_device_ifaces_lookup(device, ifindex)) {
+ if (device->ifaces_len == device->ifaces_cap) {
+- device->ifaces_cap *= 2;
++ if (device->ifaces_cap == 0) {
++ device->ifaces_cap = ZEROCONF_DEVICE_IFACES_INITIAL_LEN;
++ } else {
++ device->ifaces_cap *= 2;
++ }
++
+ device->ifaces = g_realloc(device->ifaces,
+ device->ifaces_cap * sizeof(*device->ifaces));
+ }
+@@ -260,7 +262,7 @@ zeroconf_device_rebuild_sets (zeroconf_device *device)
+ finding = OUTER_STRUCT(node, zeroconf_finding, list_node);
+ proto = zeroconf_method_to_proto(finding->method);
+
+- zeroconf_device_ifaces_add(device, finding->ifindex );
++ zeroconf_device_ifaces_add(device, finding->ifindex);
+ if (proto != ID_PROTO_UNKNOWN) {
+ device->protocols |= 1 << proto;
+ }
+@@ -316,19 +318,26 @@ static void
+ zeroconf_device_borrow_findings (zeroconf_device *device,
+ int ifindex, ll_head *output)
+ {
+- ll_node *node, *next;
+- zeroconf_finding *finding;
++ ll_node *node;
++ ll_head leftover;
+
+- for (node = ll_first(&device->findings); node != NULL; node = next) {
+- next = ll_next(&device->findings, node);
++ ll_init(&leftover);
++
++ while ((node = ll_pop_beg(&device->findings)) != NULL) {
++ zeroconf_finding *finding;
+
+ finding = OUTER_STRUCT(node, zeroconf_finding, list_node);
++
+ if (finding->ifindex == ifindex) {
+ finding->device = NULL;
+ ll_push_end(output, node);
++ } else {
++ ll_push_end(&leftover, node);
+ }
+ }
+
++ ll_cat(&device->findings, &leftover);
++
+ if (ll_empty(&device->findings)) {
+ zeroconf_device_del(device);
+ return;
+@@ -556,7 +565,7 @@ zeroconf_ident_split (const char *ident, unsigned int *devid, ID_PROTO *proto)
+
+ /* Decode proto and devid */
+ *proto = zeroconf_ident_proto_decode(*ident);
+- if (*proto == NUM_ID_PROTO) {
++ if (*proto == ID_PROTO_UNKNOWN) {
+ return NULL;
+ }
+
+@@ -774,6 +783,25 @@ zeroconf_endpoint_list_sort_dedup (zeroconf_endpoint *list)
+ return list;
+ }
+
++/* Check if endpoints list contains a non-link-local address
++ * of the specified address family
++ */
++bool
++zeroconf_endpoint_list_has_non_link_local_addr (int af,
++ const zeroconf_endpoint *list)
++{
++ for (;list != NULL; list = list->next) {
++ const struct sockaddr *addr = http_uri_addr(list->uri);
++ if (addr != NULL && addr->sa_family == af) {
++ if (!ip_sockaddr_is_linklocal(addr)) {
++ return true;
++ }
++ }
++ }
++
++ return false;
++}
++
+ /******************** Static configuration *********************/
+ /* Look for device's static configuration by device name
+ */
+@@ -850,7 +878,7 @@ zeroconf_finding_publish (zeroconf_finding *finding)
+ */
+ device = zeroconf_device_find_by_uuid(finding->uuid);
+ if (device != NULL && finding->name != NULL) {
+- if (device->ifaces_len == 1 && device->ifaces[0] == finding->ifindex ){
++ if (device->ifaces_len == 1 && device->ifaces[0] == finding->ifindex){
+ /* Case 2: all findings belongs to the same network
+ * interface; upgrade anonymous device to named
+ */
+@@ -1055,6 +1083,41 @@ zeroconf_device_list_qsort_cmp (const void *p1, const void *p2)
+ return cmp;
+ }
+
++/* Format list of protocols, for zeroconf_device_list_log
++ */
++static void
++zeroconf_device_list_fmt_protocols (char *buf, size_t buflen, unsigned int protocols)
++{
++ ID_PROTO proto;
++ size_t off = 0;
++
++ buf[0] = '\0';
++ for (proto = 0; proto < NUM_ID_PROTO; proto ++) {
++ if ((protocols & (1 << proto)) != 0) {
++ off += snprintf(buf + off, buflen - off, " %s",
++ id_proto_name(proto));
++ }
++ }
++
++ if (buf[0] == '\0') {
++ strcpy(buf, " none");
++ }
++}
++
++/* Log device information in a context of zeroconf_device_list_get
++ */
++static void
++zeroconf_device_list_log (zeroconf_device *device, const char *name, unsigned int protocols)
++{
++ char buf[64];
++
++ zeroconf_device_list_fmt_protocols(buf, sizeof(buf), device->protocols);
++ log_debug(zeroconf_log, "%s: supported protocols:%s", name, buf);
++
++ zeroconf_device_list_fmt_protocols(buf, sizeof(buf), protocols);
++ log_debug(zeroconf_log, "%s: chosen protocols:%s", name, buf);
++}
++
+ /* Get list of devices, in SANE format
+ */
+ const SANE_Device**
+@@ -1065,10 +1128,14 @@ zeroconf_device_list_get (void)
+ const SANE_Device **dev_list = sane_device_array_new();
+ ll_node *node;
+
++ log_debug(zeroconf_log, "zeroconf_device_list_get: requested");
++
+ /* Wait until device table is ready */
+ zeroconf_initscan_wait();
+
+ /* Build list of devices */
++ log_debug(zeroconf_log, "zeroconf_device_list_get: building list of devices");
++
+ dev_count = 0;
+
+ for (dev_conf = conf.devices; dev_conf != NULL; dev_conf = dev_conf->next) {
+@@ -1097,8 +1164,18 @@ zeroconf_device_list_get (void)
+ zeroconf_device_name_model(device, &name, &model);
+ protocols = zeroconf_device_protocols(device);
+
++ zeroconf_device_list_log(device, name, protocols);
++
+ if (zeroconf_find_static_by_name(name) != NULL) {
+ /* Static configuration overrides discovery */
++ log_debug(zeroconf_log,
++ "%s: skipping, device clashes statically configured", name);
++ continue;
++ }
++
++ if (protocols == 0) {
++ log_debug(zeroconf_log,
++ "%s: skipping, no of supported protocols discovered", name);
+ continue;
+ }
+
+@@ -1327,6 +1404,8 @@ zeroconf_init (void)
+ log_trace(zeroconf_log, " protocol = %s", s);
+
+ s = "?";
++ (void) s; /* Silence CLANG analyzer warning */
++
+ switch (conf.wsdd_mode) {
+ case WSDD_FAST: s = "fast"; break;
+ case WSDD_FULL: s = "full"; break;
+diff --git a/airscan.h b/airscan.h
+index 3aca207..2a36333 100644
+--- a/airscan.h
++++ b/airscan.h
+@@ -130,8 +130,10 @@ ll_push_beg (ll_head *head, ll_node *node)
+ static inline void
+ ll_del (ll_node *node)
+ {
+- node->ll_prev->ll_next = node->ll_next;
+- node->ll_next->ll_prev = node->ll_prev;
++ ll_node *p = node->ll_prev, *n = node->ll_next;
++
++ p->ll_next = n;
++ n->ll_prev = p;
+
+ /* Make double-delete safe */
+ node->ll_next = node->ll_prev = node;
+@@ -143,14 +145,19 @@ ll_del (ll_node *node)
+ static inline ll_node*
+ ll_pop_beg (ll_head *head)
+ {
+- ll_node *node;
++ ll_node *node, *next;
+
+- if (ll_empty(head)) {
+- return NULL;
++ node = head->node.ll_next;
++ if (node == &head->node) {
++ return NULL; /* List is empty if it is looped to itself */
+ }
+
+- node = head->node.ll_next;
+- ll_del(node);
++ next = node->ll_next;
++ next->ll_prev = &head->node;
++ head->node.ll_next = next;
++
++ /* Make double-delete safe */
++ node->ll_next = node->ll_prev = node;
+
+ return node;
+ }
+@@ -161,14 +168,19 @@ ll_pop_beg (ll_head *head)
+ static inline ll_node*
+ ll_pop_end (ll_head *head)
+ {
+- ll_node *node;
++ ll_node *node, *prev;
+
+- if (ll_empty(head)) {
+- return NULL;
++ node = head->node.ll_prev;
++ if (node == &head->node) {
++ return NULL; /* List is empty if it is looped to itself */
+ }
+
+- node = head->node.ll_prev;
+- ll_del(node);
++ prev = node->ll_prev;
++ prev->ll_next = &head->node;
++ head->node.ll_prev = prev;
++
++ /* Make double-delete safe */
++ node->ll_next = node->ll_prev = node;
+
+ return node;
+ }
+@@ -702,6 +714,12 @@ typedef enum {
+ NETIF_DISTANCE
+ netif_distance_get (const struct sockaddr *addr);
+
++/* Check that interface has non-link-local address
++ * of particular address family
++ */
++bool
++netif_has_non_link_local_addr (int af, int ifindex);
++
+ /* Compare addresses by distance. Returns:
+ * <0, if addr1 is closer that addr2
+ * >0, if addr2 is farther that addr2
+@@ -2055,6 +2073,13 @@ zeroconf_endpoint_list_sort (zeroconf_endpoint *list);
+ zeroconf_endpoint*
+ zeroconf_endpoint_list_sort_dedup (zeroconf_endpoint *list);
+
++/* Check if endpoints list contains a non-link-local address
++ * of the specified address family
++ */
++bool
++zeroconf_endpoint_list_has_non_link_local_addr (int af,
++ const zeroconf_endpoint *list);
++
+ /******************** MDNS Discovery ********************/
+ /* Called by zeroconf to notify MDNS about initial scan timer expiration
+ */
+@@ -2594,6 +2619,7 @@ log_panic (log_ctx *log, const char *fmt, ...);
+ if (!(expr)) { \
+ log_panic(log,"file %s: line %d (%s): assertion failed: (%s)",\
+ __FILE__, __LINE__, __PRETTY_FUNCTION__, #expr); \
++ __builtin_unreachable(); \
+ } \
+ } while (0)
+
+@@ -2603,6 +2629,7 @@ log_panic (log_ctx *log, const char *fmt, ...);
+ do { \
+ log_panic(log,"file %s: line %d (%s): internal error", \
+ __FILE__, __LINE__, __PRETTY_FUNCTION__); \
++ __builtin_unreachable(); \
+ } while (0)
+
+ #endif
+diff --git a/sane-airscan.5 b/sane-airscan.5
+index 84ed929..32656e1 100644
+--- a/sane-airscan.5
++++ b/sane-airscan.5
+@@ -1,7 +1,7 @@
+ .\" generated with Ronn/v0.7.3
+ .\" http://github.com/rtomayko/ronn/tree/0.7.3
+ .
+-.TH "SANE\-AIRSCAN" "5" "May 2020" "" "AirScan (eSCL) and WSD SANE backend"
++.TH "SANE\-AIRSCAN" "5" "July 2020" "" "AirScan (eSCL) and WSD SANE backend"
+ .
+ .SH "NAME"
+ \fBsane\-airscan\fR \- SANE backend for AirScan (eSCL) and WSD scanners and MFP
+@@ -82,7 +82,7 @@ To manually configure a device, add the following section to the configuration f
+ [devices]
+ "Kyocera eSCL" = http://192\.168\.1\.102:9095/eSCL, eSCL
+ "Kyocera WSD" = http://192\.168\.1\.102:5358/WSDScanner, WSD
+-"Device I don\'t want to see" = disable
++"Device I do not want to see" = disable
+ .
+ .fi
+ .
+@@ -146,8 +146,8 @@ Debuggung facilities can be controlled using the \fB[debug]\fR section of the co
+ enable = false | true
+
+ ; Enable protocol trace and configure output directory
+-; for trace files\. To specify path relative to user\'s
+-; home directory, start it with tilda character, followed
++; for trace files\. Like in shell, to specify path relative to
++; the home directory, start it with tilda character, followed
+ ; by slash, i\.e\., "~/airscan/trace"\. The directory will
+ ; be created automatically\.
+ trace = path
+diff --git a/sane-airscan.5.md b/sane-airscan.5.md
+index 4461900..6c6ec10 100644
+--- a/sane-airscan.5.md
++++ b/sane-airscan.5.md
+@@ -46,7 +46,7 @@ file:
+ [devices]
+ "Kyocera eSCL" = http://192.168.1.102:9095/eSCL, eSCL
+ "Kyocera WSD" = http://192.168.1.102:5358/WSDScanner, WSD
+- "Device I don't want to see" = disable
++ "Device I do not want to see" = disable
+
+ The `[devices]` section contains all manually configured devices, one line per
+ device, and each line contains a device name on a left side of equation and
+@@ -109,8 +109,8 @@ of the configuration file:
+ enable = false | true
+
+ ; Enable protocol trace and configure output directory
+- ; for trace files. To specify path relative to user's
+- ; home directory, start it with tilda character, followed
++ ; for trace files. Like in shell, to specify path relative to
++ ; the home directory, start it with tilda character, followed
+ ; by slash, i.e., "~/airscan/trace". The directory will
+ ; be created automatically.
+ trace = path
+diff --git a/test-decode.c b/test-decode.c
+index 5b2879e..af774a5 100644
+--- a/test-decode.c
++++ b/test-decode.c
+@@ -26,7 +26,7 @@ typedef struct {
+
+ /* Print error message and exit
+ */
+-void
++void __attribute__((noreturn))
+ die (const char *format, ...)
+ {
+ va_list ap;
+@@ -218,6 +218,7 @@ main (int argc, char **argv)
+ save_write(save, line);
+ }
+
++ g_free(line);
+ save_close(save);
+
+ return 0;