diff -pruN 2.1.43-2/debian/changelog 2.1.48-1/debian/changelog
--- 2.1.43-2/debian/changelog	2018-06-15 11:29:27.000000000 +0000
+++ 2.1.48-1/debian/changelog	2018-08-28 20:15:11.000000000 +0000
@@ -1,3 +1,15 @@
+targetcli-fb (2.1.48-1) unstable; urgency=medium
+
+  * d/compat: upgrade debhelper compatibility level to 11
+  * d/control: update standards version to 4.2.1
+  * d/control: depend on rtslib-fb being at least version 2.1.62
+  * d/control: replace DBUS dependency with Python GObject introspection
+  * d/patches: rediff patches
+  * d/patches: remove patches already merged upstream
+  * New upstream version
+
+ -- Christophe Vu-Brugier <cvubrugier@fastmail.fm>  Tue, 28 Aug 2018 22:15:11 +0200
+
 targetcli-fb (2.1.43-2) unstable; urgency=medium
 
   [ Christian Seiler ]
diff -pruN 2.1.43-2/debian/compat 2.1.48-1/debian/compat
--- 2.1.43-2/debian/compat	2016-09-26 07:52:54.000000000 +0000
+++ 2.1.48-1/debian/compat	2018-08-28 20:15:11.000000000 +0000
@@ -1 +1 @@
-9
+11
diff -pruN 2.1.43-2/debian/control 2.1.48-1/debian/control
--- 2.1.43-2/debian/control	2018-06-15 11:29:10.000000000 +0000
+++ 2.1.48-1/debian/control	2018-08-28 20:15:11.000000000 +0000
@@ -5,15 +5,15 @@ Maintainer: Debian LIO Target Packagers
 Uploaders: Christophe Vu-Brugier <cvubrugier@fastmail.fm>,
            Ritesh Raj Sarraf <rrs@debian.org>,
            Christian Seiler <christian@iwakd.de>
-Build-Depends: debhelper (>= 9),
+Build-Depends: debhelper (>= 11),
                dh-python,
                python3-all,
                python3-configshell-fb,
-               python3-dbus,
-               python3-rtslib-fb,
+               python3-gi,
+               python3-rtslib-fb (>= 2.1.62),
                python3-setuptools,
                python3-six
-Standards-Version: 3.9.8
+Standards-Version: 4.2.1
 Homepage: https://github.com/open-iscsi/targetcli-fb
 Vcs-Git: https://salsa.debian.org/linux-blocks-team/targetcli-fb.git
 Vcs-Browser: https://salsa.debian.org/linux-blocks-team/targetcli-fb.git
@@ -23,8 +23,8 @@ Architecture: all
 Depends: ${misc:Depends},
          ${python3:Depends},
          python3-configshell-fb,
-         python3-dbus,
-         python3-rtslib-fb,
+         python3-gi,
+         python3-rtslib-fb (>= 2.1.62),
          python3-six,
 Conflicts: targetcli, lio-utils
 Description: Command shell for managing the Linux LIO kernel target
diff -pruN 2.1.43-2/debian/patches/0001-Fix-incorrect-iqn-format-in-manpage-example.patch 2.1.48-1/debian/patches/0001-Fix-incorrect-iqn-format-in-manpage-example.patch
--- 2.1.43-2/debian/patches/0001-Fix-incorrect-iqn-format-in-manpage-example.patch	2016-09-26 07:52:54.000000000 +0000
+++ 2.1.48-1/debian/patches/0001-Fix-incorrect-iqn-format-in-manpage-example.patch	1970-01-01 00:00:00.000000000 +0000
@@ -1,27 +0,0 @@
-From: Andy Grover <agrover@redhat.com>
-Date: Wed, 10 Aug 2016 16:39:23 -0700
-Subject: Fix incorrect iqn format in manpage example
-
-Fixes #63
-
-Signed-off-by: Andy Grover <agrover@redhat.com>
----
- targetcli.8 | 4 ++--
- 1 file changed, 2 insertions(+), 2 deletions(-)
-
-diff --git a/targetcli.8 b/targetcli.8
-index fed6569..ae931be 100644
---- a/targetcli.8
-+++ b/targetcli.8
-@@ -53,9 +53,9 @@ $ sudo targetcli
- .br
- /> backstores/fileio create test /tmp/test.img 100m
- .br
--/> iscsi/ create iqn.2006-04.example.com:test-target
-+/> iscsi/ create iqn.2006-04.com.example:test-target
- .br
--/> cd iscsi/iqn.2006-04.example.com:test-target/tpg1/
-+/> cd iscsi/iqn.2006-04.com.example:test-target/tpg1/
- .br
- tpg1/> luns/ create /backstores/fileio/test
- .br
diff -pruN 2.1.43-2/debian/patches/0002-Change-the-URL-of-the-GitHub-repo-to-the-open-iscsi-.patch 2.1.48-1/debian/patches/0002-Change-the-URL-of-the-GitHub-repo-to-the-open-iscsi-.patch
--- 2.1.43-2/debian/patches/0002-Change-the-URL-of-the-GitHub-repo-to-the-open-iscsi-.patch	2016-09-26 07:52:54.000000000 +0000
+++ 2.1.48-1/debian/patches/0002-Change-the-URL-of-the-GitHub-repo-to-the-open-iscsi-.patch	1970-01-01 00:00:00.000000000 +0000
@@ -1,49 +0,0 @@
-From: Christophe Vu-Brugier <cvubrugier@fastmail.fm>
-Date: Mon, 22 Aug 2016 16:09:13 +0200
-Subject: Change the URL of the GitHub repo to the open-iscsi organization
-
-Signed-off-by: Christophe Vu-Brugier <cvubrugier@fastmail.fm>
----
- README.md   | 4 ++--
- setup.py    | 2 +-
- targetcli.8 | 2 +-
- 3 files changed, 4 insertions(+), 4 deletions(-)
-
-diff --git a/README.md b/README.md
-index 294c4f9..8f651df 100644
---- a/README.md
-+++ b/README.md
-@@ -13,8 +13,8 @@ targetcli-fb development
- targetcli-fb is licensed under the Apache 2.0 license. Contributions are welcome.
- 
-  * Mailing list: [targetcli-fb-devel](https://lists.fedorahosted.org/mailman/listinfo/targetcli-fb-devel)
-- * Source repo: [GitHub](https://github.com/agrover/targetcli-fb)
-- * Bugs: [GitHub](https://github.com/agrover/targetcli-fb/issues) or [Trac](https://fedorahosted.org/targetcli-fb/)
-+ * Source repo: [GitHub](https://github.com/open-iscsi/targetcli-fb)
-+ * Bugs: [GitHub](https://github.com/open-iscsi/targetcli-fb/issues) or [Trac](https://fedorahosted.org/targetcli-fb/)
-  * Tarballs: [fedorahosted](https://fedorahosted.org/releases/t/a/targetcli-fb/)
-  * Playlist of instructional screencast videos: [YouTube](https://www.youtube.com/playlist?list=PLC2C75481A3ABB067)
- 
-diff --git a/setup.py b/setup.py
-index 052fb43..7b44304 100755
---- a/setup.py
-+++ b/setup.py
-@@ -28,7 +28,7 @@ setup(
-     license = 'Apache 2.0',
-     maintainer = 'Andy Grover',
-     maintainer_email = 'agrover@redhat.com',
--    url = 'http://github.com/agrover/targetcli-fb',
-+    url = 'http://github.com/open-iscsi/targetcli-fb',
-     packages = ['targetcli'],
-     scripts = ['scripts/targetcli'],
-     classifiers = [
-diff --git a/targetcli.8 b/targetcli.8
-index ae931be..7b131de 100644
---- a/targetcli.8
-+++ b/targetcli.8
-@@ -463,4 +463,4 @@ Man page written by Andy Grover <agrover@redhat.com>.
- .SH REPORTING BUGS
- Report bugs via <targetcli-fb-devel@lists.fedorahosted.org>
- .br
--or <https://github.com/agrover/targetcli-fb/issues>
-+or <https://github.com/open-iscsi/targetcli-fb/issues>
diff -pruN 2.1.43-2/debian/patches/0003-Use-etc-rtslib-fb-target-instead-of-etc-target.patch 2.1.48-1/debian/patches/0003-Use-etc-rtslib-fb-target-instead-of-etc-target.patch
--- 2.1.43-2/debian/patches/0003-Use-etc-rtslib-fb-target-instead-of-etc-target.patch	2016-10-06 09:41:01.000000000 +0000
+++ 2.1.48-1/debian/patches/0003-Use-etc-rtslib-fb-target-instead-of-etc-target.patch	2018-08-28 20:15:11.000000000 +0000
@@ -8,14 +8,14 @@ is compatible with the startup logic in
 out of the box.
 ---
  targetcli.8          | 8 ++++----
- targetcli/ui_root.py | 2 +-
- 2 files changed, 5 insertions(+), 5 deletions(-)
+ targetcli/ui_root.py | 4 ++--
+ 2 files changed, 6 insertions(+), 6 deletions(-)
 
 diff --git a/targetcli.8 b/targetcli.8
-index 7b131de..3d2dccb 100644
+index 45ea3e3..7bc2075 100644
 --- a/targetcli.8
 +++ b/targetcli.8
-@@ -352,7 +352,7 @@ The LUN should now be accessible via FCoE.
+@@ -355,7 +355,7 @@ The LUN should now be accessible via FCoE.
  Save the current configuration settings to a file, from which settings
  will be restored if the system is rebooted. By default, this will save
  the configuration to
@@ -24,7 +24,7 @@ index 7b131de..3d2dccb 100644
  .P
  This command is executed from the configuration root node.
  .P
-@@ -412,7 +412,7 @@ existing ACLs; and
+@@ -415,7 +415,7 @@ existing ACLs; and
  to change working path to newly-created nodes.  Global settings
  are user-specific and are saved to ~/.targetcli/ upon exit, unlike
  other groups, which are system-wide and kept in
@@ -33,7 +33,7 @@ index 7b131de..3d2dccb 100644
  .SS BACKSTORE-SPECIFIC
  .B attribute
  .br
-@@ -450,9 +450,9 @@ to enforce or disable authentication for the full-feature phase
+@@ -453,9 +453,9 @@ to enforce or disable authentication for the full-feature phase
  /iscsi/<target_iqn>/tpgX/acls/<initiator_iqn> configuration node. Set
  the userid and password for full-feature phase for this ACL.
  .SH FILES
@@ -46,15 +46,17 @@ index 7b131de..3d2dccb 100644
  .BR targetctl (8),
  .BR tcmu-runner (8)
 diff --git a/targetcli/ui_root.py b/targetcli/ui_root.py
-index defdad0..438e7ef 100644
+index a54845f..bc9f200 100644
 --- a/targetcli/ui_root.py
 +++ b/targetcli/ui_root.py
-@@ -31,7 +31,7 @@ from .ui_backstore import complete_path, UIBackstores
+@@ -33,8 +33,8 @@ from .ui_backstore import complete_path, UIBackstores
  from .ui_node import UINode
  from .ui_target import UIFabricModule
  
 -default_save_file = "/etc/target/saveconfig.json"
+-universal_prefs_file = "/etc/target/targetcli.conf"
 +default_save_file = "/etc/rtslib-fb-target/saveconfig.json"
- kept_backups = 10
++universal_prefs_file = "/etc/rtslib-fb-target/targetcli.conf"
  
  class UIRoot(UINode):
+     '''
diff -pruN 2.1.43-2/debian/patches/0004-Properly-detect-errors-when-writing-backup-files.-Cl.patch 2.1.48-1/debian/patches/0004-Properly-detect-errors-when-writing-backup-files.-Cl.patch
--- 2.1.43-2/debian/patches/0004-Properly-detect-errors-when-writing-backup-files.-Cl.patch	2018-06-15 11:28:17.000000000 +0000
+++ 2.1.48-1/debian/patches/0004-Properly-detect-errors-when-writing-backup-files.-Cl.patch	1970-01-01 00:00:00.000000000 +0000
@@ -1,67 +0,0 @@
-From: Christian Seiler <christian@iwakd.de>
-Date: Thu, 23 Mar 2017 19:36:18 +0100
-Subject: Properly detect errors when writing backup files. (Closes: #80) (#81)
-
-* Properly detect errors when writing backup files. (Closes: #80)
-
-If the backup directory does not exist, properly detect that and show a
-warning message to the user, so that they don't think that their
-configuration was backed up, when it fact wasn't.
-
-Additionally, try to automatically create the backup directory if it
-does not exist.
-
-Signed-off-by: Christian Seiler <christian@iwakd.de>
-
-* Don't automatically create backup directory if it doesn't exist.
-
-After discussion on the issue tracker, it was decided to not
-auto-create the backup-directory if it doesn't exist yet.
-
-If the directory doesn't exist, the user will now see the warning.
-
-Signed-off-by: Christian Seiler <christian@iwakd.de>
----
- targetcli/ui_root.py | 28 ++++++++++++++++++----------
- 1 file changed, 18 insertions(+), 10 deletions(-)
-
-diff --git a/targetcli/ui_root.py b/targetcli/ui_root.py
-index 438e7ef..3fe48e0 100644
---- a/targetcli/ui_root.py
-+++ b/targetcli/ui_root.py
-@@ -71,17 +71,25 @@ class UIRoot(UINode):
-             backup_name = "saveconfig-" + \
-                 datetime.now().strftime("%Y%m%d-%H:%M:%S") + ".json"
-             backupfile = backup_dir + "/" + backup_name
--            with ignored(IOError):
-+            backup_error = None
-+            try:
-                 shutil.copy(savefile, backupfile)
--
--            # Kill excess backups
--            backups = sorted(glob(os.path.dirname(savefile) + "/backup/*.json"))
--            files_to_unlink = list(reversed(backups))[kept_backups:]
--            for f in files_to_unlink:
--                os.unlink(f)
--
--            self.shell.log.info("Last %d configs saved in %s." % \
--                                    (kept_backups, backup_dir))
-+            except IOError as ioe:
-+                backup_error = ioe.strerror or "Unknown error"
-+
-+            if backup_error == None:
-+                # Kill excess backups
-+                backups = sorted(glob(os.path.dirname(savefile) + "/backup/*.json"))
-+                files_to_unlink = list(reversed(backups))[kept_backups:]
-+                for f in files_to_unlink:
-+                    with ignored(IOError):
-+                        os.unlink(f)
-+
-+                self.shell.log.info("Last %d configs saved in %s." % \
-+                                        (kept_backups, backup_dir))
-+            else:
-+                self.shell.log.warning("Could not create backup file %s: %s." % \
-+                                           (backupfile, backup_error))
- 
-         self.rtsroot.save_to_file(savefile)
- 
diff -pruN 2.1.43-2/debian/patches/series 2.1.48-1/debian/patches/series
--- 2.1.43-2/debian/patches/series	2018-06-15 11:28:17.000000000 +0000
+++ 2.1.48-1/debian/patches/series	2018-08-28 20:15:11.000000000 +0000
@@ -1,4 +1 @@
-0001-Fix-incorrect-iqn-format-in-manpage-example.patch
-0002-Change-the-URL-of-the-GitHub-repo-to-the-open-iscsi-.patch
 0003-Use-etc-rtslib-fb-target-instead-of-etc-target.patch
-0004-Properly-detect-errors-when-writing-backup-files.-Cl.patch
diff -pruN 2.1.43-2/Makefile 2.1.48-1/Makefile
--- 2.1.43-2/Makefile	2016-04-08 00:43:16.000000000 +0000
+++ 2.1.48-1/Makefile	2018-01-26 19:34:06.000000000 +0000
@@ -6,8 +6,6 @@ VERSION = $$(basename $$(git describe --
 all:
 	@echo "Usage:"
 	@echo
-	@echo "  make deb         - Builds debian packages."
-	@echo "  make rpm         - Builds rpm packages."
 	@echo "  make release     - Generates the release tarball."
 	@echo
 	@echo "  make clean       - Cleanup the local repository build files."
@@ -17,20 +15,8 @@ clean:
 	@rm -fv ${NAME}/*.pyc ${NAME}/*.html
 	@rm -frv doc
 	@rm -frv ${NAME}.egg-info MANIFEST build
-	@rm -frv debian/tmp
-	@rm -fv build-stamp
-	@rm -fv dpkg-buildpackage.log dpkg-buildpackage.version
-	@rm -frv *.rpm
-	@rm -fv debian/files debian/*.log debian/*.substvars
-	@rm -frv debian/${PKGNAME}-doc/ debian/python2.5-${PKGNAME}/
-	@rm -frv debian/python2.6-${PKGNAME}/ debian/python-${PKGNAME}/
 	@rm -frv results
-	@rm -fv rpm/*.spec *.spec rpm/sed* sed*
 	@rm -frv ${NAME}-*
-	@rm -frv *.rpm warn${NAME}.txt build${NAME}
-	@rm -fv debian/*.debhelper.log debian/*.debhelper debian/*.substvars debian/files
-	@rm -fvr debian/${NAME}-frozen/ debian/${NAME}-python2.5/
-	@rm -fvr debian/${NAME}-python2.6/ debian/${NAME}/ debian/${NAME}-doc/
 	@rm -frv log/
 	@echo "Finished cleanup."
 
@@ -50,41 +36,6 @@ build/release-stamp:
 	@echo "Fixing version string..."
 	@sed -i "s/__version__ = .*/__version__ = '${VERSION}'/g" \
 		build/${PKGNAME}-${VERSION}/${NAME}/__init__.py
-	@echo "Generating rpm specfile from template..."
-	@cd build/${PKGNAME}-${VERSION}; \
-		for spectmpl in rpm/*.spec.tmpl; do \
-			sed -i "s/Version:\( *\).*/Version:\1${VERSION}/g" $${spectmpl}; \
-			mv $${spectmpl} $$(basename $${spectmpl} .tmpl); \
-		done; \
-		rmdir rpm
-	@echo "Generating rpm changelog..."
-	@( \
-		version=${VERSION}; \
-		author=$$(git show HEAD --format="format:%an <%ae>" -s); \
-		date=$$(git show HEAD --format="format:%ad" -s \
-			| awk '{print $$1,$$2,$$3,$$5}'); \
-		hash=$$(git show HEAD --format="format:%H" -s); \
-	   	echo '* '"$${date} $${author} $${version}-1"; \
-		echo "  - Generated from git commit $${hash}."; \
-	) >> $$(ls build/${PKGNAME}-${VERSION}/*.spec)
-	@echo "Generating debian changelog..."
-	@( \
-		version=${VERSION}; \
-		author=$$(git show HEAD --format="format:%an <%ae>" -s); \
-		date=$$(git show HEAD --format="format:%aD" -s); \
-		day=$$(git show HEAD --format='format:%ai' -s \
-			| awk '{print $$1}' \
-			| awk -F '-' '{print $$3}' | sed 's/^0/ /g'); \
-		date=$$(echo $${date} \
-			| awk '{print $$1, "'"$${day}"'", $$3, $$4, $$5, $$6}'); \
-		hash=$$(git show HEAD --format="format:%H" -s); \
-		echo "${PKGNAME} ($${version}) unstable; urgency=low"; \
-		echo; \
-		echo "  * Generated from git commit $${hash}."; \
-		echo; \
-		echo " -- $${author}  $${date}"; \
-		echo; \
-	) > build/${PKGNAME}-${VERSION}/debian/changelog
 	@find build/${PKGNAME}-${VERSION}/ -exec \
 		touch -t $$(date -d @$$(git show -s --format="format:%at") \
 			+"%Y%m%d%H%M.%S") {} \;
@@ -97,27 +48,3 @@ build/release-stamp:
 	@echo "Generated release tarball:"
 	@echo "    $$(ls dist/${PKGNAME}-${VERSION}.tar.gz)"
 	@touch build/release-stamp
-
-deb: release build/deb-stamp
-build/deb-stamp:
-	@echo "Building debian packages..."
-	@cd build/${PKGNAME}-${VERSION}; \
-		dpkg-buildpackage -rfakeroot -us -uc
-	@mv build/*_${VERSION}_*.deb dist/
-	@echo "Generated debian packages:"
-	@for pkg in $$(ls dist/*_${VERSION}_*.deb); do echo "  $${pkg}"; done
-	@touch build/deb-stamp
-
-rpm: release build/rpm-stamp
-build/rpm-stamp:
-	@echo "Building rpm packages..."
-	@mkdir -p build/rpm
-	@build=$$(pwd)/build/rpm; dist=$$(pwd)/dist/; rpmbuild \
-		--define "_topdir $${build}" --define "_sourcedir $${dist}" \
-		--define "_rpmdir $${build}" --define "_buildir $${build}" \
-		--define "_srcrpmdir $${build}" -ba build/${PKGNAME}-${VERSION}/*.spec
-	@mv build/rpm/*-${VERSION}*.src.rpm dist/
-	@mv build/rpm/*/*-${VERSION}*.rpm dist/
-	@echo "Generated rpm packages:"
-	@for pkg in $$(ls dist/*-${VERSION}*.rpm); do echo "  $${pkg}"; done
-	@touch build/rpm-stamp
diff -pruN 2.1.43-2/README.md 2.1.48-1/README.md
--- 2.1.43-2/README.md	2016-04-08 00:43:16.000000000 +0000
+++ 2.1.48-1/README.md	2018-01-26 19:34:06.000000000 +0000
@@ -13,20 +13,19 @@ targetcli-fb development
 targetcli-fb is licensed under the Apache 2.0 license. Contributions are welcome.
 
  * Mailing list: [targetcli-fb-devel](https://lists.fedorahosted.org/mailman/listinfo/targetcli-fb-devel)
- * Source repo: [GitHub](https://github.com/agrover/targetcli-fb)
- * Bugs: [GitHub](https://github.com/agrover/targetcli-fb/issues) or [Trac](https://fedorahosted.org/targetcli-fb/)
+ * Source repo: [GitHub](https://github.com/open-iscsi/targetcli-fb)
+ * Bugs: [GitHub](https://github.com/open-iscsi/targetcli-fb/issues) or [Trac](https://fedorahosted.org/targetcli-fb/)
  * Tarballs: [fedorahosted](https://fedorahosted.org/releases/t/a/targetcli-fb/)
  * Playlist of instructional screencast videos: [YouTube](https://www.youtube.com/playlist?list=PLC2C75481A3ABB067)
 
-In-repo packaging
------------------
-Packaging scripts for RPM and DEB are included, but these are to make end-user
-custom packaging easier -- distributions tend to maintain their own packaging
-scripts separately. If you run into issues with packaging, start with opening
-a bug on your distro's bug reporting system.
-
-Some people do use these scripts, so we want to keep them around. Fixes for
-any breakage you encounter are welcome.
+Packages
+--------
+targetcli-fb is packaged for a number of Linux distributions including
+RHEL,
+[Fedora](https://apps.fedoraproject.org/packages/targetcli),
+openSUSE, Arch Linux,
+[Gentoo](https://packages.gentoo.org/packages/sys-block/targetcli-fb), and
+[Debian](https://tracker.debian.org/pkg/targetcli-fb).
 
 "fb" -- "free branch"
 ---------------------
diff -pruN 2.1.43-2/rpm/targetcli.spec.tmpl 2.1.48-1/rpm/targetcli.spec.tmpl
--- 2.1.43-2/rpm/targetcli.spec.tmpl	2016-04-08 00:43:16.000000000 +0000
+++ 2.1.48-1/rpm/targetcli.spec.tmpl	1970-01-01 00:00:00.000000000 +0000
@@ -1,39 +0,0 @@
-%define oname targetcli-fb
-
-Name:           targetcli
-License:        Apache License 2.0
-Group:          Applications/System
-Summary:        RisingTide Systems generic SCSI target CLI shell.
-Version:        VERSION
-Release:        1%{?dist}
-URL:            http://www.risingtidesystems.com/git/
-Source:         %{oname}-%{version}.tar.gz
-BuildRoot:      %{_tmppath}/%{name}-%{version}-%{release}-rpmroot
-BuildArch:      noarch
-BuildRequires:  python-devel, python-rtslib, python-configshell
-Requires:       python-rtslib, python-configshell
-Vendor:         Datera, Inc.
-
-%description
-RisingTide Systems generic SCSI target CLI shell.
-
-%prep
-%setup -q -n %{oname}-%{version}
-
-%build
-%{__python} setup.py build
-
-%install
-rm -rf %{buildroot}
-%{__python} setup.py install --skip-build --root=%{buildroot} --prefix=usr
-
-%clean
-rm -rf %{buildroot}
-
-%files
-%defattr(-,root,root,-)
-%{python_sitelib}
-%{_bindir}/targetcli
-%doc COPYING README.md
-
-%changelog
diff -pruN 2.1.43-2/scripts/targetcli 2.1.48-1/scripts/targetcli
--- 2.1.43-2/scripts/targetcli	2016-04-08 00:43:16.000000000 +0000
+++ 2.1.48-1/scripts/targetcli	2018-01-26 19:34:06.000000000 +0000
@@ -49,6 +49,7 @@ class TargetCLI(ConfigShell):
                      'auto_add_mapped_luns': True,
                      'auto_cd_after_create': False,
                      'auto_save_on_exit': True,
+                     'max_backup_files': '10',
                      'auto_add_default_portal': True,
                     }
 
@@ -63,7 +64,7 @@ def usage():
 
 def version():
     print("%s version %s" % (sys.argv[0], targetcli_version), file=err)
-    sys.exit(-1)
+    sys.exit(0)
 
 def main():
     '''
diff -pruN 2.1.43-2/setup.py 2.1.48-1/setup.py
--- 2.1.43-2/setup.py	2016-04-08 00:43:16.000000000 +0000
+++ 2.1.48-1/setup.py	2018-01-26 19:34:06.000000000 +0000
@@ -28,7 +28,7 @@ setup(
     license = 'Apache 2.0',
     maintainer = 'Andy Grover',
     maintainer_email = 'agrover@redhat.com',
-    url = 'http://github.com/agrover/targetcli-fb',
+    url = 'http://github.com/open-iscsi/targetcli-fb',
     packages = ['targetcli'],
     scripts = ['scripts/targetcli'],
     classifiers = [
diff -pruN 2.1.43-2/source/format 2.1.48-1/source/format
--- 2.1.43-2/source/format	2016-04-08 00:43:16.000000000 +0000
+++ 2.1.48-1/source/format	1970-01-01 00:00:00.000000000 +0000
@@ -1 +0,0 @@
-3.0 (native)
diff -pruN 2.1.43-2/targetcli/ui_backstore.py 2.1.48-1/targetcli/ui_backstore.py
--- 2.1.43-2/targetcli/ui_backstore.py	2016-04-08 00:43:16.000000000 +0000
+++ 2.1.48-1/targetcli/ui_backstore.py	2018-01-26 19:34:06.000000000 +0000
@@ -17,21 +17,42 @@ License for the specific language govern
 under the License.
 '''
 
+from gi.repository import Gio
 import glob
 import os
+import fcntl
+import array
+import struct
 import re
 import stat
-import dbus
 
 from configshell_fb import ExecutionError
 from rtslib_fb import BlockStorageObject, FileIOStorageObject
 from rtslib_fb import PSCSIStorageObject, RDMCPStorageObject, UserBackedStorageObject
+from rtslib_fb import ALUATargetPortGroup
 from rtslib_fb import RTSLibError
 from rtslib_fb import RTSRoot
 from rtslib_fb.utils import get_block_type
 
 from .ui_node import UINode, UIRTSLibNode
 
+alua_rw_params = ['alua_access_state', 'alua_access_status',
+                  'alua_write_metadata', 'alua_access_type', 'preferred',
+                  'nonop_delay_msecs', 'trans_delay_msecs',
+                  'implicit_trans_secs', 'alua_support_offline',
+                  'alua_support_standby', 'alua_support_transitioning',
+                  'alua_support_active_nonoptimized',
+                  'alua_support_unavailable', 'alua_support_active_optimized']
+alua_ro_params = ['tg_pt_gp_id', 'members', 'alua_support_lba_dependent']
+
+alua_state_names = { 0: 'Active/optimized',
+                     1: 'Active/non-optimized',
+                     2: 'Standby',
+                     3: 'Unavailable',
+                     4: 'LBA Dependent',
+                     14: 'Offline',
+                     15: 'Transitioning'}
+
 def human_to_bytes(hsize, kilo=1024):
     '''
     This function converts human-readable amounts of bytes to bytes.
@@ -96,6 +117,108 @@ def complete_path(path, stat_fn):
     return sorted(filtered,
                   key=lambda s: '~'+s if s.endswith('/') else s)
 
+
+class UIALUATargetPortGroup(UIRTSLibNode):
+    '''
+    A generic UI for ALUATargetPortGroup objects.
+    '''
+    def __init__(self, alua_tpg, parent):
+        name = alua_tpg.name
+        super(UIALUATargetPortGroup, self).__init__(name, alua_tpg, parent)
+        self.refresh()
+
+        for param in alua_rw_params:
+            self.define_config_group_param("alua", param, 'string')
+
+        for param in alua_ro_params:
+            self.define_config_group_param("alua", param, 'string', False)
+
+    def ui_getgroup_alua(self, alua_attr):
+        return getattr(self.rtsnode, alua_attr)
+
+    def ui_setgroup_alua(self, alua_attr, value):
+        self.assert_root()
+
+        if value is None:
+            return
+
+        setattr(self.rtsnode, alua_attr, value)
+
+    def summary(self):
+        self.rtsnode.alua_access_state
+        return ("ALUA state: %s" %
+                alua_state_names[self.rtsnode.alua_access_state], True)
+
+class UIALUATargetPortGroups(UINode):
+    '''
+    ALUA Target Port Group UI
+    '''
+    def __init__(self, parent):
+        super(UIALUATargetPortGroups, self).__init__("alua", parent)
+        self.refresh()
+
+    def summary(self):
+        return ("ALUA Groups: %d" % len(self.children), None)
+
+    def refresh(self):
+        self._children = set([])
+
+        so = self.parent.rtsnode
+        for tpg in so.alua_tpgs:
+            UIALUATargetPortGroup(tpg, self)
+
+    def ui_command_create(self, name, tag):
+        '''
+        Create a new ALUA Target Port Group attached to a storage object.
+        '''
+        self.assert_root()
+
+        so = self.parent.rtsnode
+        alua_tpg_object = ALUATargetPortGroup(so, name, int(tag))
+        self.shell.log.info("Created ALUA TPG %s." % alua_tpg_object.name)
+        ui_alua_tpg = UIALUATargetPortGroup(alua_tpg_object, self)
+        return self.new_node(ui_alua_tpg)
+
+    def ui_command_delete(self, name):
+        '''
+        Delete the ALUA Target Por Group and unmap it from a LUN if needed.
+        '''
+        self.assert_root()
+
+        so = self.parent.rtsnode
+        try:
+            alua_tpg_object = ALUATargetPortGroup(so, name)
+        except:
+            raise RTSLibError("Invalid ALUA group name")
+
+        alua_tpg_object.delete()
+        self.refresh()
+
+    def ui_complete_delete(self, parameters, text, current_param):
+        '''
+        Parameter auto-completion method for user command delete.
+        @param parameters: Parameters on the command line.
+        @type parameters: dict
+        @param text: Current text of parameter being typed by the user.
+        @type text: str
+        @param current_param: Name of parameter to complete.
+        @type current_param: str
+        @return: Possible completions
+        @rtype: list of str
+        '''
+        if current_param == 'name':
+            so = self.parent.rtsnode
+
+            tpgs = [tpg.name for tpg in so.alua_tpgs]
+            completions = [tpg for tpg in tpgs if tpg.startswith(text)]
+        else:
+            completions = []
+
+        if len(completions) == 1:
+            return [completions[0] + ' ']
+        else:
+            return completions
+
 class UIBackstores(UINode):
     '''
     The backstores container UI.
@@ -109,16 +232,26 @@ class UIBackstores(UINode):
         tcmu-runner (or other daemon providing the same service) exposes a
         DBus ObjectManager-based iface to find handlers it supports.
         '''
-        bus = dbus.SystemBus()
+        bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None)
         try:
-            mgr_obj = bus.get_object('org.kernel.TCMUService1', '/org/kernel/TCMUService1')
-            mgr_iface = dbus.Interface(mgr_obj, 'org.freedesktop.DBus.ObjectManager')
-
-            for k,v in mgr_iface.GetManagedObjects().items():
-                tcmu_obj = bus.get_object('org.kernel.TCMUService1', k)
-                tcmu_iface = dbus.Interface(tcmu_obj, dbus_interface='org.kernel.TCMUService1')
+            mgr_iface = Gio.DBusProxy.new_sync(bus,
+                                               Gio.DBusProxyFlags.NONE,
+                                               None,
+                                               'org.kernel.TCMUService1',
+                                               '/org/kernel/TCMUService1',
+                                               'org.freedesktop.DBus.ObjectManager',
+                                               None)
+
+            for k, v in mgr_iface.GetManagedObjects().items():
+                tcmu_iface = Gio.DBusProxy.new_sync(bus,
+                                                    Gio.DBusProxyFlags.NONE,
+                                                    None,
+                                                    'org.kernel.TCMUService1',
+                                                    k,
+                                                    'org.kernel.TCMUService1',
+                                                    None)
                 yield (k[k.rfind("/")+1:], tcmu_iface, v)
-        except dbus.DBusException as e:
+        except Exception as e:
             return
 
     def refresh(self):
@@ -396,6 +529,25 @@ class UIBlockBackstore(UIBackstore):
         self.so_cls = UIBlockStorageObject
         UIBackstore.__init__(self, 'block', parent)
 
+    def _ui_block_ro_check(self, dev):
+        BLKROGET=0x0000125E
+        try:
+            f = os.open(dev, os.O_RDONLY)
+        except (OSError, IOError):
+            raise ExecutionError("Could not open %s" % dev)
+        # ioctl returns an int. Provision a buffer for it
+        buf = array.array('c', [chr(0)] * 4)
+        try:
+            fcntl.ioctl(f, BLKROGET, buf)
+        except (OSError, IOError):
+            os.close(f)
+            return False
+
+        os.close(f)
+        if struct.unpack('I', buf)[0] == 0:
+            return False
+        return True
+
     def ui_command_create(self, name, dev, readonly=None, wwn=None):
         '''
         Creates an Block Storage object. I{dev} is the path to the TYPE_DISK
@@ -403,7 +555,13 @@ class UIBlockBackstore(UIBackstore):
         '''
         self.assert_root()
 
-        readonly = self.ui_eval_param(readonly, 'bool', False)
+        ro_string = self.ui_eval_param(readonly, 'string', None)
+        if ro_string == None:
+            # attempt to detect block device readonly state via ioctl
+            readonly = self._ui_block_ro_check(dev)
+        else:
+            readonly = self.ui_eval_param(readonly, 'bool', False)
+
         wwn = self.ui_eval_param(wwn, 'string', None)
 
         so = BlockStorageObject(name, dev, readonly=readonly, wwn=wwn)
@@ -439,7 +597,7 @@ class UIUserBackedBackstore(UIBackstore)
     def refresh(self):
         self._children = set([])
         for so in RTSRoot().storage_objects:
-            if so.plugin == 'user':
+            if so.plugin == 'user' and so.config:
                 idx = so.config.find("/")
                 handler = so.config[:idx]
                 if handler == self.handler:
@@ -454,7 +612,8 @@ class UIUserBackedBackstore(UIBackstore)
             print(x.get("ConfigDesc", "No description."))
             print()
 
-    def ui_command_create(self, name, size, cfgstring, wwn=None):
+    def ui_command_create(self, name, size, cfgstring, wwn=None,
+                          hw_max_sectors=None):
         '''
         Creates a User-backed storage object.
 
@@ -474,11 +633,16 @@ class UIUserBackedBackstore(UIBackstore)
 
         config = self.handler + "/" + cfgstring
 
-        ok, errmsg = self.iface.CheckConfig(config)
+        ok, errmsg = self.iface.CheckConfig('(s)', config)
         if not ok:
             raise ExecutionError("cfgstring invalid: %s" % errmsg)
 
-        so = UserBackedStorageObject(name, size=size, config=config, wwn=wwn)
+        try:
+            so = UserBackedStorageObject(name, size=size, config=config,
+                                         wwn=wwn, hw_max_sectors=hw_max_sectors)
+        except:
+            raise ExecutionError("UserBackedStorageObject creation failed.")
+
         ui_so = UIUserBackedStorageObject(so, self)
         self.shell.log.info("Created user-backed storage object %s size %d."
                             % (name, size))
@@ -529,6 +693,8 @@ class UIStorageObject(UIRTSLibNode):
         UIRTSLibNode.__init__(self, name, storage_object, parent)
         self.refresh()
 
+        UIALUATargetPortGroups(self)
+
     def ui_command_version(self):
         '''
         Displays the version of the current backstore's plugin.
diff -pruN 2.1.43-2/targetcli/ui_node.py 2.1.48-1/targetcli/ui_node.py
--- 2.1.43-2/targetcli/ui_node.py	2016-04-08 00:43:16.000000000 +0000
+++ 2.1.48-1/targetcli/ui_node.py	2018-01-26 19:34:06.000000000 +0000
@@ -46,6 +46,9 @@ class UINode(ConfigNode):
         self.define_config_group_param(
             'global', 'auto_add_default_portal', 'bool',
             'If true, adds a portal listening on all IPs to new targets.')
+        self.define_config_group_param(
+            'global', 'max_backup_files', 'string',
+            'Max no. of configurations to be backed up in /etc/target/backup/ directory.')
 
     def assert_root(self):
         '''
diff -pruN 2.1.43-2/targetcli/ui_root.py 2.1.48-1/targetcli/ui_root.py
--- 2.1.43-2/targetcli/ui_root.py	2016-04-08 00:43:16.000000000 +0000
+++ 2.1.48-1/targetcli/ui_root.py	2018-01-26 19:34:06.000000000 +0000
@@ -20,8 +20,10 @@ under the License.
 from datetime import datetime
 from glob import glob
 import os
+import re
 import shutil
 import stat
+import filecmp
 
 from configshell_fb import ExecutionError
 from rtslib_fb import RTSRoot
@@ -32,7 +34,7 @@ from .ui_node import UINode
 from .ui_target import UIFabricModule
 
 default_save_file = "/etc/target/saveconfig.json"
-kept_backups = 10
+universal_prefs_file = "/etc/target/targetcli.conf"
 
 class UIRoot(UINode):
     '''
@@ -49,6 +51,10 @@ class UIRoot(UINode):
         '''
         self._children = set([])
 
+        # Invalidate any rtslib caches
+        if 'invalidate_caches' in dir(RTSRoot):
+            self.rtsroot.invalidate_caches()
+
         UIBackstores(self)
 
         # only show fabrics present in the system
@@ -71,17 +77,47 @@ class UIRoot(UINode):
             backup_name = "saveconfig-" + \
                 datetime.now().strftime("%Y%m%d-%H:%M:%S") + ".json"
             backupfile = backup_dir + "/" + backup_name
-            with ignored(IOError):
-                shutil.copy(savefile, backupfile)
-
-            # Kill excess backups
-            backups = sorted(glob(os.path.dirname(savefile) + "/backup/*.json"))
-            files_to_unlink = list(reversed(backups))[kept_backups:]
-            for f in files_to_unlink:
-                os.unlink(f)
+            backup_error = None
 
-            self.shell.log.info("Last %d configs saved in %s." % \
-                                    (kept_backups, backup_dir))
+            if not os.path.exists(backup_dir):
+                try:
+                    os.makedirs(backup_dir);
+                except OSError as exe:
+                    raise ExecutionError("Cannot create backup directory [%s] %s." % (backup_dir, exc.strerror))
+
+            # Only save backups if savefile exits
+            if os.path.exists(savefile):
+                backed_files_list = sorted(glob(os.path.dirname(savefile) + "/backup/*.json"))
+                # Save backup if 1. backup dir is empty, or 2. savefile is differnt from recent backup copy
+                if not backed_files_list or not filecmp.cmp(backed_files_list[-1], savefile):
+                    try:
+                        shutil.copy(savefile, backupfile)
+
+                    except IOError as ioe:
+                        backup_error = ioe.strerror or "Unknown error"
+
+                    if backup_error == None:
+                        # Kill excess backups
+                        max_backup_files = int(self.shell.prefs['max_backup_files'])
+
+                        try:
+                            with open(universal_prefs_file) as prefs:
+                                backups = [line for line in prefs.read().splitlines() if re.match('^max_backup_files\s*=', line)]
+                                if max_backup_files < int(backups[0].split('=')[1].strip()):
+                                    max_backup_files = int(backups[0].split('=')[1].strip())
+                        except:
+                            self.shell.log.debug("No universal prefs file '%s'." % universal_prefs_file)
+
+                        files_to_unlink = list(reversed(backed_files_list))[max_backup_files:]
+                        for f in files_to_unlink:
+                            with ignored(IOError):
+                                os.unlink(f)
+
+                        self.shell.log.info("Last %d configs saved in %s." % \
+                                            (max_backup_files, backup_dir))
+                    else:
+                        self.shell.log.warning("Could not create backup file %s: %s." % \
+                                               (backupfile, backup_error))
 
         self.rtsroot.save_to_file(savefile)
 
diff -pruN 2.1.43-2/targetcli/ui_target.py 2.1.48-1/targetcli/ui_target.py
--- 2.1.43-2/targetcli/ui_target.py	2016-04-08 00:43:16.000000000 +0000
+++ 2.1.48-1/targetcli/ui_target.py	2018-01-26 19:34:06.000000000 +0000
@@ -1243,6 +1243,8 @@ class UILUN(UIRTSLibNode):
         super(UILUN, self).__init__(name, lun, parent)
         self.refresh()
 
+        self.define_config_group_param("alua", "alua_tg_pt_gp_name", 'string')
+
     def summary(self):
         lun = self.rtsnode
         is_healthy = True
@@ -1256,8 +1258,20 @@ class UILUN(UIRTSLibNode):
             if storage_object.udev_path:
                 description += " (%s)" % storage_object.udev_path
 
+            description += " (%s)" % lun.alua_tg_pt_gp_name
+
         return (description, is_healthy)
 
+    def ui_getgroup_alua(self, alua_attr):
+        return getattr(self.rtsnode, alua_attr)
+
+    def ui_setgroup_alua(self, alua_attr, value):
+        self.assert_root()
+
+        if value is None:
+            return
+
+        setattr(self.rtsnode, alua_attr, value)
 
 class UIPortals(UINode):
     '''
@@ -1437,6 +1451,8 @@ class UIPortal(UIRTSLibNode):
     def summary(self):
         if self.rtsnode.iser:
             return('iser', True)
+        elif self.rtsnode.offload:
+            return('offload', True)
         return ('', True)
 
     def ui_command_enable_iser(self, boolean):
@@ -1449,3 +1465,14 @@ class UIPortal(UIRTSLibNode):
         boolean = self.ui_eval_param(boolean, 'bool', False)
         self.rtsnode.iser = boolean
         self.shell.log.info("iSER enable now: %s" % self.rtsnode.iser)
+
+    def ui_command_enable_offload(self, boolean):
+        '''
+        Enables or disables offload for this NetworkPortal.
+
+        If offload is not supported by the kernel, this command will do nothing.
+        '''
+
+        boolean = self.ui_eval_param(boolean, 'bool', False)
+        self.rtsnode.offload = boolean
+        self.shell.log.info("offload enable now: %s" % self.rtsnode.offload)
diff -pruN 2.1.43-2/targetcli/version.py 2.1.48-1/targetcli/version.py
--- 2.1.43-2/targetcli/version.py	2016-04-08 00:43:16.000000000 +0000
+++ 2.1.48-1/targetcli/version.py	2018-01-26 19:34:06.000000000 +0000
@@ -15,4 +15,4 @@ License for the specific language govern
 under the License.
 '''
 
-__version__ = '2.1.fb43'
+__version__ = '2.1.fb48'
diff -pruN 2.1.43-2/targetcli.8 2.1.48-1/targetcli.8
--- 2.1.43-2/targetcli.8	2016-04-08 00:43:16.000000000 +0000
+++ 2.1.48-1/targetcli.8	2018-01-26 19:34:06.000000000 +0000
@@ -53,9 +53,9 @@ $ sudo targetcli
 .br
 /> backstores/fileio create test /tmp/test.img 100m
 .br
-/> iscsi/ create iqn.2006-04.example.com:test-target
+/> iscsi/ create iqn.2006-04.com.example:test-target
 .br
-/> cd iscsi/iqn.2006-04.example.com:test-target/tpg1/
+/> cd iscsi/iqn.2006-04.com.example:test-target/tpg1/
 .br
 tpg1/> luns/ create /backstores/fileio/test
 .br
@@ -162,6 +162,9 @@ If the hardware supports it,
 .B iSER
 (iSCSI Extensions for RDMA) may be enabled via the
 .B enable_iser
+command within each portal's node.  Or, if the hardware supports it,
+hardware offload may be enabled via the
+.B enable_offload
 command within each portal's node.
 .SS LUNS
 The kernel target exports SCSI Logical Units, also called
@@ -463,4 +466,4 @@ Man page written by Andy Grover <agrover
 .SH REPORTING BUGS
 Report bugs via <targetcli-fb-devel@lists.fedorahosted.org>
 .br
-or <https://github.com/agrover/targetcli-fb/issues>
+or <https://github.com/open-iscsi/targetcli-fb/issues>
