]> git.ipfire.org Git - thirdparty/bacula.git/commitdiff
Fix for plugin obects to properly decode object.
authorRadosław Korzeniewski <radoslaw@korzeniewski.net>
Tue, 8 Jun 2021 13:55:11 +0000 (15:55 +0200)
committerEric Bollengier <eric@baculasystems.com>
Thu, 24 Mar 2022 08:03:02 +0000 (09:03 +0100)
Move get_next_tag() to lib/edit.c and add unittest for it.

.gitignore
bacula/src/cats/Makefile.in
bacula/src/cats/cats.c
bacula/src/lib/Makefile.in
bacula/src/lib/edit.c
bacula/src/lib/protos.h
regress/tests/edit-unittest [new file with mode: 0755]

index 287efbf0116acd4b4b4ea0a462ffd967dea22647..d91a5207d76d9ccf6450fd76bcdeb660160c2943 100644 (file)
@@ -318,6 +318,7 @@ bacula/src/lib/bsnprintf_test
 bacula/src/lib/bsock_test
 bacula/src/lib/bsockcore_test
 bacula/src/lib/crc32_test
+bacula/src/lib/edit_test
 bacula/src/lib/flist_test
 bacula/src/lib/output_test
 bacula/src/lib/sellist_test
index 4955436e8ab9fdf1e9d6f59781a3f5de43219096..57be6a2ec2f656e5f5e67462174ab08e58db03e0 100644 (file)
@@ -125,7 +125,7 @@ libbaccats.a: $(LIBBACCATS_OBJS)
        $(RANLIB) $@
 
 libbacsql.la: Makefile $(LIBBACSQL_LOBJS)
-       @echo "Making $@ ..."     
+       @echo "Making $@ ..."
        $(LIBTOOL_LINK) $(CXX) $(DEFS) $(DEBUG) $(LDFLAGS) -o $@ $(LIBBACSQL_LOBJS) -export-dynamic -rpath $(libdir) -release $(LIBBACSQL_LT_RELEASE) $(DB_LIBS)
 
 libbaccats.la: Makefile cats_null.lo
@@ -294,7 +294,7 @@ uninstall: @LIBTOOL_UNINSTALL_TARGET@ @INCLUDE_UNINSTALL_TARGET@
 # and it also includes system headers.
 # `semi'-automatic since dependencies are generated at distribution time.
 
-depend: 
+depend:
        @$(MV) Makefile Makefile.bak
        @$(SED) "/^# DO NOT DELETE:/,$$ d" Makefile.bak > Makefile
        @$(ECHO) "# DO NOT DELETE: nice dependency list follows" >> Makefile
index 975bcba20826b8d8e3371b2319494f109a2c1d55..3c40dcdc2550feb29e16e4b9391d8dc5d05bc0ed 100644 (file)
@@ -1,4 +1,4 @@
-/* 
+/*
    Bacula(R) - The Network Backup Solution
 
    Copyright (C) 2000-2020 Kern Sibbald
    This notice must be preserved when any source code is
    conveyed and/or propagated.
 
-   Bacula(R) is a registered trademark of Kern Sibbald. 
-*/ 
-/* 
- * Generic catalog class methods. 
- * 
- * Note: at one point, this file was assembled from parts of other files 
- *  by a programmer, and other than "wrapping" in a class, which is a trivial  
- *  change for a C++ programmer, nothing substantial was done, yet all the  
- *  code was recommitted under this programmer's name.  Consequently, we  
- *  undo those changes here.  
- */ 
-#include "bacula.h" 
-#if HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL 
-#include "cats.h" 
+   Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * Generic catalog class methods.
+ *
+ * Note: at one point, this file was assembled from parts of other files
+ *  by a programmer, and other than "wrapping" in a class, which is a trivial
+ *  change for a C++ programmer, nothing substantial was done, yet all the
+ *  code was recommitted under this programmer's name.  Consequently, we
+ *  undo those changes here.
+ */
+
+#include "bacula.h"
+
+#if HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL
+
+#include "cats.h"
 
 static int dbglvl=100;
+
 void append_filter(POOLMEM **buf, char *cond)
 {
    if (*buf[0] != '\0') {
@@ -45,144 +45,122 @@ void append_filter(POOLMEM **buf, char *cond)
    pm_strcat(buf, cond);
 }
 
-bool BDB::bdb_match_database(const char *db_driver, const char *db_name, 
-                             const char *db_address, int db_port) 
-{ 
-   BDB *mdb = this; 
-   bool match; 
-   if (db_driver) { 
-      match = strcasecmp(mdb->m_db_driver, db_driver) == 0 && 
-              bstrcmp(mdb->m_db_name, db_name) && 
-              bstrcmp(mdb->m_db_address, db_address) && 
-              mdb->m_db_port == db_port && 
-              mdb->m_dedicated == false; 
-   } else { 
-      match = bstrcmp(mdb->m_db_name, db_name) && 
-              bstrcmp(mdb->m_db_address, db_address) && 
-              mdb->m_db_port == db_port && 
-              mdb->m_dedicated == false; 
-   } 
-   return match; 
-} 
-BDB *BDB::bdb_clone_database_connection(JCR *jcr, bool mult_db_connections) 
-{ 
-   BDB *mdb = this; 
-   /* 
-    * See if its a simple clone e.g. with mult_db_connections set to false 
-    * then we just return the calling class pointer. 
-    */ 
-   if (!mult_db_connections) { 
-      mdb->m_ref_count++; 
-      return mdb; 
-   } 
-   /* 
-    * A bit more to do here just open a new session to the database. 
-    */ 
-   return db_init_database(jcr, mdb->m_db_driver, mdb->m_db_name, 
+bool BDB::bdb_match_database(const char *db_driver, const char *db_name,
+                             const char *db_address, int db_port)
+{
+   BDB *mdb = this;
+   bool match;
+
+   if (db_driver) {
+      match = strcasecmp(mdb->m_db_driver, db_driver) == 0 &&
+              bstrcmp(mdb->m_db_name, db_name) &&
+              bstrcmp(mdb->m_db_address, db_address) &&
+              mdb->m_db_port == db_port &&
+              mdb->m_dedicated == false;
+   } else {
+      match = bstrcmp(mdb->m_db_name, db_name) &&
+              bstrcmp(mdb->m_db_address, db_address) &&
+              mdb->m_db_port == db_port &&
+              mdb->m_dedicated == false;
+   }
+   return match;
+}
+
+BDB *BDB::bdb_clone_database_connection(JCR *jcr, bool mult_db_connections)
+{
+   BDB *mdb = this;
+   /*
+    * See if its a simple clone e.g. with mult_db_connections set to false
+    * then we just return the calling class pointer.
+    */
+   if (!mult_db_connections) {
+      mdb->m_ref_count++;
+      return mdb;
+   }
+
+   /*
+    * A bit more to do here just open a new session to the database.
+    */
+   return db_init_database(jcr, mdb->m_db_driver, mdb->m_db_name,
              mdb->m_db_user, mdb->m_db_password, mdb->m_db_address,
              mdb->m_db_port, mdb->m_db_socket,
              mdb->m_db_ssl_mode, mdb->m_db_ssl_key,
              mdb->m_db_ssl_cert, mdb->m_db_ssl_ca,
              mdb->m_db_ssl_capath, mdb->m_db_ssl_cipher,
              true, mdb->m_disabled_batch_insert);
-} 
-const char *BDB::bdb_get_engine_name(void) 
-{ 
-   BDB *mdb = this; 
-   switch (mdb->m_db_driver_type) { 
-   case SQL_DRIVER_TYPE_MYSQL: 
-      return "MySQL"; 
-   case SQL_DRIVER_TYPE_POSTGRESQL: 
-      return "PostgreSQL"; 
-   case SQL_DRIVER_TYPE_SQLITE3: 
-      return "SQLite3"; 
-   default: 
-      return "Unknown"; 
-   } 
-} 
-/* 
- * Lock database, this can be called multiple times by the same 
- * thread without blocking, but must be unlocked the number of 
- * times it was locked using db_unlock(). 
- */ 
-void BDB::bdb_lock(const char *file, int line) 
-{ 
-   int errstat; 
-   BDB *mdb = this; 
-   if ((errstat = rwl_writelock_p(&mdb->m_lock, file, line)) != 0) { 
-      berrno be; 
-      e_msg(file, line, M_FATAL, 0, "rwl_writelock failure. stat=%d: ERR=%s\n", 
-            errstat, be.bstrerror(errstat)); 
-   } 
-} 
-/* 
- * Unlock the database. This can be called multiple times by the 
- * same thread up to the number of times that thread called 
- * db_lock()/ 
- */ 
-void BDB::bdb_unlock(const char *file, int line) 
-{ 
-   int errstat; 
-   BDB *mdb = this; 
-   if ((errstat = rwl_writeunlock(&mdb->m_lock)) != 0) { 
-      berrno be; 
-      e_msg(file, line, M_FATAL, 0, "rwl_writeunlock failure. stat=%d: ERR=%s\n", 
-            errstat, be.bstrerror(errstat)); 
-   } 
-} 
-bool BDB::bdb_sql_query(const char *query, int flags) 
-{ 
-   bool retval; 
-   BDB *mdb = this; 
-   bdb_lock(); 
-   retval = sql_query(query, flags); 
-   if (!retval) { 
-      Mmsg(mdb->errmsg, _("Query failed: %s: ERR=%s\n"), query, sql_strerror()); 
-   } 
-   bdb_unlock(); 
-   return retval; 
-} 
-void BDB::print_lock_info(FILE *fp) 
-{ 
-   BDB *mdb = this; 
-   if (mdb->m_lock.valid == RWLOCK_VALID) { 
-      fprintf(fp, "\tRWLOCK=%p w_active=%i w_wait=%i\n",  
-         &mdb->m_lock, mdb->m_lock.w_active, mdb->m_lock.w_wait); 
-   } 
-} 
-
-/* Parse stream of tags, return next one from the stream (it will be null terminated,
- * original buffer will be changed) */
-static char *get_next_tag(char **buf)
+}
+
+const char *BDB::bdb_get_engine_name(void)
 {
-   char *p = *buf;
-   char *tmp = p;
-   skip_nonspaces(&p);
-   skip_spaces(&p);
-   char *c = strpbrk(tmp, " ");
-   if (c) {
-      *c = '\0';
-   } else {
-      Dmsg0(dbglvl, "No tag found!\n");
-      return NULL;
+   BDB *mdb = this;
+   switch (mdb->m_db_driver_type) {
+   case SQL_DRIVER_TYPE_MYSQL:
+      return "MySQL";
+   case SQL_DRIVER_TYPE_POSTGRESQL:
+      return "PostgreSQL";
+   case SQL_DRIVER_TYPE_SQLITE3:
+      return "SQLite3";
+   default:
+      return "Unknown";
    }
-   *buf = p;
+}
+
+/*
+ * Lock database, this can be called multiple times by the same
+ * thread without blocking, but must be unlocked the number of
+ * times it was locked using db_unlock().
+ */
+void BDB::bdb_lock(const char *file, int line)
+{
+   int errstat;
+   BDB *mdb = this;
 
-   Dmsg1(dbglvl, "Found tag: %s\n", tmp);
-   return tmp;
+   if ((errstat = rwl_writelock_p(&mdb->m_lock, file, line)) != 0) {
+      berrno be;
+      e_msg(file, line, M_FATAL, 0, "rwl_writelock failure. stat=%d: ERR=%s\n",
+            errstat, be.bstrerror(errstat));
+   }
+}
+
+/*
+ * Unlock the database. This can be called multiple times by the
+ * same thread up to the number of times that thread called
+ * db_lock()/
+ */
+void BDB::bdb_unlock(const char *file, int line)
+{
+   int errstat;
+   BDB *mdb = this;
+
+   if ((errstat = rwl_writeunlock(&mdb->m_lock)) != 0) {
+      berrno be;
+      e_msg(file, line, M_FATAL, 0, "rwl_writeunlock failure. stat=%d: ERR=%s\n",
+            errstat, be.bstrerror(errstat));
+   }
 }
 
+bool BDB::bdb_sql_query(const char *query, int flags)
+{
+   bool retval;
+   BDB *mdb = this;
+
+   bdb_lock();
+   retval = sql_query(query, flags);
+   if (!retval) {
+      Mmsg(mdb->errmsg, _("Query failed: %s: ERR=%s\n"), query, sql_strerror());
+   }
+   bdb_unlock();
+   return retval;
+}
+
+void BDB::print_lock_info(FILE *fp)
+{
+   BDB *mdb = this;
+   if (mdb->m_lock.valid == RWLOCK_VALID) {
+      fprintf(fp, "\tRWLOCK=%p w_active=%i w_wait=%i\n",
+         &mdb->m_lock, mdb->m_lock.w_active, mdb->m_lock.w_wait);
+   }
+}
 
 bool OBJECT_DBR::parse_plugin_object_string(char **obj_str)
 {
@@ -391,4 +369,4 @@ void parse_restore_object_string(char **r_obj_str, ROBJECT_DBR *robj_r)
       robj_r->object_len, robj_r->object);
 }
 
-#endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL */ 
+#endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL */
index 76248e7bb26981e46a33581658712b8886dd4ce2..b9c3d07d06418c31bafd64f89f83ab5e3469a6d6 100644 (file)
@@ -172,6 +172,14 @@ base64_test: Makefile libbac.la base64.c unittests.o
        $(RMF) base64.o
        $(CXX) $(DEFS) $(DEBUG) -c $(CPPFLAGS) -I$(srcdir) -I$(basedir) $(DINCLUDE) $(CFLAGS) base64.c
 
+edit_test: Makefile libbac.la edit.c unittests.o
+       $(RMF) edit.o
+       $(CXX) -DTEST_PROGRAM $(DEFS) $(DEBUG) -c $(CPPFLAGS) -I$(srcdir) -I$(basedir) $(DINCLUDE) $(CFLAGS) edit.c
+       $(LIBTOOL_LINK) $(CXX) $(LDFLAGS) -L. -o $@ edit.o unittests.o $(DLIB) -lbac -lm $(LIBS) $(OPENSSL_LIBS)
+       $(LIBTOOL_INSTALL) $(INSTALL_PROGRAM) $@ $(DESTDIR)$(sbindir)/
+       $(RMF) edit.o
+       $(CXX) $(DEFS) $(DEBUG) -c $(CPPFLAGS) -I$(srcdir) -I$(basedir) $(DINCLUDE) $(CFLAGS) edit.c
+
 flist_test: Makefile libbac.la flist.c unittests.o
        $(RMF) flist.o
        $(CXX) -DTEST_PROGRAM $(DEFS) $(DEBUG) -c $(CPPFLAGS) -I$(srcdir) -I$(basedir) $(DINCLUDE) $(CFLAGS) flist.c
@@ -260,7 +268,6 @@ crc32_test: Makefile libbac.la org_lib_crc32.c unittests.o
        $(RMF) org_lib_crc32.o
        $(CXX) $(DEFS) $(DEBUG) -c $(CPPFLAGS) -I$(srcdir) -I$(basedir) $(DINCLUDE) $(CFLAGS) org_lib_crc32.c
 
-
 bee_crc32_test: Makefile libbac.la bee_lib_crc32.c unittests.o
        $(RMF) bee_lib_crc32.o
        $(CXX) -DTEST_PROGRAM $(DEFS) $(DEBUG) -c $(CPPFLAGS) -I$(srcdir) -I$(basedir) $(DINCLUDE) $(CFLAGS) bee_lib_crc32.c
@@ -318,7 +325,7 @@ sha1_test: Makefile libbac.la sha1.c unittests.o
 
 bsnprintf_test: Makefile libbac.la bsnprintf.c unittests.o
        $(RMF) bsnprintf.o
-       $(CXX) -DTEST_PROGRAM $(DEFS) $(DEBUG) -c $(CPPFLAGS) -I$(srcdir) -I$(basedir) $(DINCLUDE)  $(CFLAGS) -Wno-format-truncation bsnprintf.c 
+       $(CXX) -DTEST_PROGRAM $(DEFS) $(DEBUG) -c $(CPPFLAGS) -I$(srcdir) -I$(basedir) $(DINCLUDE)  $(CFLAGS) -Wno-format-truncation bsnprintf.c
        $(LIBTOOL_LINK) $(CXX) $(LDFLAGS) -L. -o $@ bsnprintf.o unittests.o $(DLIB) -lbac -lm $(LIBS) $(OPENSSL_LIBS)
        $(LIBTOOL_INSTALL) $(INSTALL_PROGRAM) $@ $(DESTDIR)$(sbindir)/
        $(RMF) bsnprintf.o
index 9cd90d2ed4cb427e293602243c3a5869110f2a3a..b76e98fe1565628719d5b50259f2b050ce8ca64b 100644 (file)
@@ -505,7 +505,7 @@ bool is_name_valid(const char *name, POOLMEM **msg)
  * Check if Bacula Resoure Name is valid
  */
 /*
- * Check if the Volume/resource name has legal characters
+ * Check if the Volume name has legal characters
  * If ua is non-NULL send the message
  */
 bool is_name_valid(const char *name, POOLMEM **msg, const char *accept)
@@ -539,7 +539,7 @@ bool is_name_valid(const char *name, POOLMEM **msg, const char *accept)
    }
    if (len == 0) {
       if (msg) {
-         Mmsg(msg,  _("Name must be at least one character long.\n"));
+         Mmsg(msg,  _("Volume name must be at least one character long.\n"));
       }
       return false;
    }
@@ -578,24 +578,142 @@ char *add_commas(char *val, char *buf)
    return buf;
 }
 
+/* Parse stream of tags, return next one from the stream (it will be null terminated,
+ * original buffer will be changed) */
+char *get_next_tag(char **buf)
+{
+   char *tmp = NULL;
+
+   if (**buf != '\0') {
+      char *p = *buf;
+      tmp = p;
+      p = strchr(*buf, ' ');
+      if (p != NULL){
+         *p++ = '\0';
+         *buf = p;
+      } else {
+         *buf += strlen(tmp);
+      }
+      Dmsg1(900, "Found tag: %s\n", tmp);
+   } else {
+      Dmsg0(900, "No tag found!\n");
+   }
+
+   return tmp;
+}
+
+// #define TEST_PROGRAM
+
 #ifdef TEST_PROGRAM
+#include "unittests.h"
+
 void d_msg(const char*, int, int, const char*, ...)
 {}
-int main(int argc, char *argv[])
+
+// this is a test vector
+// /@kubernetes/ kubernetes:^Ans=plugintest^Adebug^Averify_ssl=0 Container PVCs Kubernetes^APersistent^AVolume^AClaim   5368709120 U 5
+char __po_log1[] = {
+  0x2f, 0x40, 0x6b, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, 0x73,
+  0x2f, 0x20, 0x6b, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, 0x73,
+  0x3a, 0x01, 0x6e, 0x73, 0x3d, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x74,
+  0x65, 0x73, 0x74, 0x01, 0x64, 0x65, 0x62, 0x75, 0x67, 0x01, 0x76, 0x65,
+  0x72, 0x69, 0x66, 0x79, 0x5f, 0x73, 0x73, 0x6c, 0x3d, 0x30, 0x20, 0x43,
+  0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x20, 0x50, 0x56, 0x43,
+  0x73, 0x20, 0x4b, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, 0x73,
+  0x01, 0x50, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x74, 0x01,
+  0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x01, 0x43, 0x6c, 0x61, 0x69, 0x6d,
+  0x20, 0x20, 0x20, 0x35, 0x33, 0x36, 0x38, 0x37, 0x30, 0x39, 0x31, 0x32,
+  0x30, 0x20, 0x55, 0x20, 0x35, 0x00
+};
+unsigned int __po_log1_len = sizeof(__po_log1);
+int __po_log1_nr = 10;
+
+// ASD  123
+char __po_log2[] = {0x41, 0x53, 0x44, 0x20, 0x20, 0x31, 0x32, 0x33, 0x00, 0x00};
+unsigned int __po_log2_len = sizeof(__po_log2);
+
+struct _edit_utime_vect
 {
-   char *str[] = {"3", "3n", "3 hours", "3.5 day", "3 week", "3 m", "3 q", "3 years"};
-   utime_t val;
-   char buf[100];
-   char outval[100];
-
-   for (int i=0; i<8; i++) {
-      strcpy(buf, str[i]);
-      if (!duration_to_utime(buf, &val)) {
-         printf("Error return from duration_to_utime for in=%s\n", str[i]);
-         continue;
+   const char *in;
+   const utime_t val;
+   const char *outval;
+};
+
+_edit_utime_vect __testvect1[] =
+{
+   { "3", 3, "3 secs"},
+   { "3n", 180, "3 mins "},
+   { "3 hours", 10800, "3 hours "},
+   { "3.5 day", 302400, "3 days 12 hours "},
+   { "3 week", 1814400, "21 days "},
+   { "3 m", 7776000, "3 months "},
+   { "3 q", 23587200, "9 months 3 days "},
+   { "3 years", 94608000, "3 years "},
+   { "23587201", 23587201, "9 months 3 days 1 sec"},
+   { NULL, 0, NULL},
+};
+
+int main()
+{
+   Unittests unittest("text_edit_tests");
+
+   {
+      utime_t val;
+      char buf[100];
+      char outval[100];
+
+      for (int i=0; __testvect1[i].in != NULL; i++) {
+         strcpy(buf, __testvect1[i].in);
+         POOL_MEM label;
+         Mmsg(label, "duration_to_utime %s", __testvect1[i].in);
+         bool status = duration_to_utime(buf, &val);
+         ok(status, label.c_str());
+         if (status){
+            edit_utime(val, outval, sizeof(outval));
+            ok(val == __testvect1[i].val, "checking val");
+            ok(strcmp(outval, __testvect1[i].outval) == 0, "checking outval");
+         }
+         // printf("outval='%s'\n", outval);
+      }
+   }
+
+   {
+      char *testvect = __po_log2;
+      char **obj_str = &testvect;
+
+      char *fname = get_next_tag(obj_str);
+      ok(fname != NULL, "checking first tag");
+      ok(strcmp(fname, "ASD") == 0, "checking first tag value");
+
+      char *empty = get_next_tag(obj_str);
+      ok(empty != NULL, "checking empty tag");
+      ok(strlen(empty) == 0, "checking empty value tag");
+
+      char *last = get_next_tag(obj_str);
+      ok(last != NULL, "checking last tag");
+      ok(strcmp(last, "123") == 0, "checking last tag value");
+      ok(obj_str != NULL, "checking obj_str");
+      ok(*obj_str != NULL, "checking obj_str ptr");
+      ok(**obj_str == 0, "checking obj_str char");
+
+      char *afterlast = get_next_tag(obj_str);
+      ok(afterlast == NULL, "checking no tags");
+   }
+
+   {
+      char *testvect = __po_log1;
+      char **obj_str = &testvect;
+
+      for (int a = 0; a < __po_log1_nr; a++){
+         char *tag = get_next_tag(obj_str);
+         POOL_MEM label;
+         Mmsg(label, "checking tag %d", a);
+         ok(tag != NULL, label.c_str());
       }
-      edit_utime(val, outval);
-      printf("in=%s val=%" lld " outval=%s\n", str[i], val, outval);
+      ok(get_next_tag(obj_str) == NULL, "checking the last");
    }
+
+   return report();
 }
+
 #endif
index 50fcd999bf534829c89b42c2775808374e9de6bb..b5f77049cfc6afc69f67268d5af540733a5e32f0 100644 (file)
@@ -239,6 +239,7 @@ bool             is_an_integer           (const char *n);
 #define EXTRA_VALID_RESOURCE_CHAR_GLOB  EXTRA_VALID_RESOURCE_CHAR "[]*?"
 bool             is_name_valid           (const char *name, POOLMEM **msg);
 bool             is_name_valid           (const char *name, POOLMEM **msg, const char *accept);
+char             *get_next_tag(char **buf);
 
 /* jcr.c (most definitions are in src/jcr.h) */
 void     init_last_jobs_list();
diff --git a/regress/tests/edit-unittest b/regress/tests/edit-unittest
new file mode 100755 (executable)
index 0000000..5d224b1
--- /dev/null
@@ -0,0 +1,9 @@
+#!/bin/sh
+#
+# Copyright (C) 2000-2015 Kern Sibbald
+# License: BSD 2-Clause; see file LICENSE-FOSS
+#
+# This is an alist unit test
+#
+. scripts/regress-utils.sh
+do_regress_unittest "edit_test" "src/lib"