Преглед изворни кода

libdpkg: Fix varbufprintf to not grow indefinitely

Use the C99 semantics for vsnprintf to get the final formatted string
size, and then call it again to do the actual work. Use varbuf_grow with
the know size, instead of always calling varbufextend, regardles of
needing to extend the buffer, which was making it grow indefinitely.

This also should speed it up a bit by not requiring to call vsnprintf
several times depending on the length of the final string. Only two
calls are required now, always.
Guillem Jover пре 17 година
родитељ
комит
1a8d12285d
3 измењених фајлова са 50 додато и 13 уклоњено
  1. 3 0
      debian/changelog
  2. 28 0
      lib/dpkg/test/t-varbuf.c
  3. 19 13
      lib/dpkg/varbuf.c

+ 3 - 0
debian/changelog

@@ -20,6 +20,9 @@ dpkg (1.15.5) UNRELEASED; urgency=low
   * Make dpkg as strict as dpkg-statoverride on input when validating the
     parsed data from the statdb.
   * Rewrite dpkg-statoverride in C.
+  * Use C99 snprintf function family semantics to avoid having to call them
+    in a loop to grow the varbuf buffer. This should reduce memory usage and
+    be slightly faster on varbufprintf calls.
 
   [ Raphaël Hertzog ]
   * Add versioned dependency on base-files (>= 5.0.0) to dpkg-dev to ensure

+ 28 - 0
lib/dpkg/test/t-varbuf.c

@@ -174,6 +174,33 @@ test_varbuf_substc(void)
 	varbuffree(&vb);
 }
 
+static void
+test_varbuf_printf(void)
+{
+	struct varbuf vb;
+
+	varbufinit(&vb, 5);
+
+	/* Test normal format printing. */
+	varbufprintf(&vb, "format %s number %d", "string", 10);
+	varbufaddc(&vb, '\0');
+	test_pass(vb.used == sizeof("format string number 10"));
+	test_pass(vb.size >= vb.used);
+	test_str(vb.buf, ==, "format string number 10");
+
+	varbufreset(&vb);
+
+	/* Test concatenated format printing. */
+	varbufprintf(&vb, "format %s number %d", "string", 10);
+	varbufprintf(&vb, " extra %s", "string");
+	varbufaddc(&vb, '\0');
+	test_pass(vb.used == sizeof("format string number 10 extra string"));
+	test_pass(vb.size >= vb.used);
+	test_str(vb.buf, ==, "format string number 10 extra string");
+
+	varbuffree(&vb);
+}
+
 static void
 test_varbuf_reset(void)
 {
@@ -205,6 +232,7 @@ test(void)
 	test_varbuf_addc();
 	test_varbuf_dupc();
 	test_varbuf_substc();
+	test_varbuf_printf();
 	test_varbuf_reset();
 
 	/* FIXME: Complete. */

+ 19 - 13
lib/dpkg/varbuf.c

@@ -69,21 +69,27 @@ int varbufprintf(struct varbuf *v, const char *fmt, ...) {
 }
 
 int varbufvprintf(struct varbuf *v, const char *fmt, va_list va) {
-  size_t ou;
-  int r;
   va_list al;
+  int needed, r;
+
+  va_copy(al, va);
+  needed = vsnprintf(NULL, 0, fmt, al);
+  va_end(al);
+
+  if (needed < 0)
+    ohshite("error printing into varbuf variable");
+
+  varbuf_grow(v, needed + 1);
+
+  va_copy(al, va);
+  r = vsnprintf(v->buf + v->used, needed + 1, fmt, al);
+  va_end(al);
+
+  if (r < 0)
+    ohshite("error printing into varbuf variable");
+
+  v->used += r;
 
-  ou= v->used;
-  v->used+= strlen(fmt);
-
-  do {
-    varbufextend(v);
-    va_copy(al, va);
-    r= vsnprintf(v->buf+ou,v->size-ou,fmt,al);
-    va_end(al);
-    if (r < 0) r= (v->size-ou+1) * 2;
-    v->used= ou+r;
-  } while (r >= (int)(v->size - ou - 1));
   return r;
 }