From c4068760a6735db6c5b785827ccf2bb9f95eb25c Mon Sep 17 00:00:00 2001 From: "A. Wilcox" Date: Thu, 30 Jul 2020 04:08:43 +0000 Subject: user/sane-airscan: Update to 0.99.10 --- user/sane-airscan/APKBUILD | 11 +- user/sane-airscan/git.patch | 1212 ------------------------------------------- 2 files changed, 4 insertions(+), 1219 deletions(-) delete mode 100644 user/sane-airscan/git.patch (limited to 'user') diff --git a/user/sane-airscan/APKBUILD b/user/sane-airscan/APKBUILD index c106204f8..1694d3d0f 100644 --- a/user/sane-airscan/APKBUILD +++ b/user/sane-airscan/APKBUILD @@ -1,8 +1,8 @@ # Contributor: A. Wilcox # Maintainer: A. Wilcox pkgname=sane-airscan -pkgver=0.99.8 -pkgrel=1 +pkgver=0.99.10 +pkgrel=0 pkgdesc="Universal scanner driver for AirScan (eSCL) scanners" url=" " arch="all" @@ -11,9 +11,7 @@ depends="" makedepends="avahi-dev glib-dev libjpeg-turbo-dev libpng-dev libsoup-dev libxml2-dev meson ninja sane-dev" subpackages="$pkgname-doc" -source="sane-airscan-$pkgver.tar.gz::https://github.com/alexpevzner/sane-airscan/archive/$pkgver.tar.gz - git.patch - " +source="sane-airscan-$pkgver.tar.gz::https://github.com/alexpevzner/sane-airscan/archive/$pkgver.tar.gz" build() { meson \ @@ -36,5 +34,4 @@ package() { rm "$pkgdir"/etc/sane.d/dll.conf } -sha512sums="2828deeea31297c64b9927bb2b2b982f16a16cbb446cf2ca685b13f79cb264e94c017b8930fe04004636a6e1122f1882bd58252a3302c0a13b3fc60566072155 sane-airscan-0.99.8.tar.gz -a195c23edaf399b6fc1ba7693afd34fa87504c9c8ff10d1d793b97c1b592bff90214cd18af764451a3af9860884509fa7701037322a551226a0bcd29473ca1cc git.patch" +sha512sums="7a3d98e01ad883856eb79c522aff50243b399717a6ba2db07eec66e49a83d365ad4db6d3f87718592f3b85832a2098e86ae094d9f06ed951ebc23887cddb75b7 sane-airscan-0.99.10.tar.gz" diff --git a/user/sane-airscan/git.patch b/user/sane-airscan/git.patch deleted file mode 100644 index 818580553..000000000 --- a/user/sane-airscan/git.patch +++ /dev/null @@ -1,1212 +0,0 @@ -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. - - -- Copyright (C) 19yy -+ Copyright (C) - - 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[1](#note1) | | - | 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[2](#note1) | | - | 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[2](#note2) | --| Lexmark CX317dn | Yes[3](#note3) | Yes[3](#note3) | -+| Kyocera ECOSYS M2040dn | Yes | Yes[3](#note2) | -+| Lexmark CX317dn | Yes[4](#note3) | Yes[4](#note3) | - | Lexmark MC2535adwe | Yes | | --| Ricoh MP C3003 | No | Yes[4](#note3) | -+| Ricoh MP C3003 | No | Yes[5](#note3) | - | 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. - --[2]: this device requires manual action on its front -+[2]: 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. -+ -+[3]: this device requires manual action on its front - panel to initiate WSD scan: Send->WSD Scan->From Computer - --[3]: when low in memory, this device may scan at 400 DPI -+[4]: 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. - --[4]: by default, WSD scan command is disabled on this -+[5]: 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; -- cgit v1.2.3-60-g2f50